@goweekdays/layer-common 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/components/Calendar.vue +367 -0
- package/components/Layout/NavigationDrawer.vue +3 -1
- package/components/VideoPlayer.vue +1496 -0
- package/middleware/01.auth.ts +2 -2
- package/nuxt.config.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card variant="outlined" border="thin" rounded="xl">
|
|
3
|
+
<!-- Calendar Header -->
|
|
4
|
+
<v-card-title
|
|
5
|
+
class="d-flex align-center justify-space-between flex-wrap ga-4 flex-column flex-md-row"
|
|
6
|
+
>
|
|
7
|
+
<div class="d-flex align-center ga-2 justify-center justify-md-start">
|
|
8
|
+
<v-btn
|
|
9
|
+
icon="mdi-chevron-left"
|
|
10
|
+
variant="tonal"
|
|
11
|
+
size="small"
|
|
12
|
+
@click="previousMonth"
|
|
13
|
+
/>
|
|
14
|
+
<v-chip
|
|
15
|
+
variant="text"
|
|
16
|
+
size="large"
|
|
17
|
+
label
|
|
18
|
+
class="text-h6 font-weight-medium text-center"
|
|
19
|
+
style="min-width: 180px"
|
|
20
|
+
>
|
|
21
|
+
{{ currentMonth }} {{ currentYear }}
|
|
22
|
+
</v-chip>
|
|
23
|
+
<v-btn
|
|
24
|
+
icon="mdi-chevron-right"
|
|
25
|
+
variant="tonal"
|
|
26
|
+
size="small"
|
|
27
|
+
@click="nextMonth"
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Today Button -->
|
|
32
|
+
<v-btn
|
|
33
|
+
v-if="props.hideTodayButton"
|
|
34
|
+
variant="outlined"
|
|
35
|
+
size="small"
|
|
36
|
+
prepend-icon="mdi-calendar-today"
|
|
37
|
+
@click="goToToday"
|
|
38
|
+
>
|
|
39
|
+
Today
|
|
40
|
+
</v-btn>
|
|
41
|
+
</v-card-title>
|
|
42
|
+
|
|
43
|
+
<v-divider />
|
|
44
|
+
|
|
45
|
+
<!-- Month View -->
|
|
46
|
+
<v-card-text class="pa-0">
|
|
47
|
+
<!-- Days of Week Header -->
|
|
48
|
+
<v-sheet color="surface-bright" class="calendar-weekdays pa-0">
|
|
49
|
+
<v-sheet
|
|
50
|
+
v-for="day in daysOfWeek"
|
|
51
|
+
:key="day"
|
|
52
|
+
color="transparent"
|
|
53
|
+
class="d-flex align-center justify-center pa-3 weekday-cell"
|
|
54
|
+
style="min-height: 48px"
|
|
55
|
+
>
|
|
56
|
+
<v-chip variant="text" size="small" label>{{ day }}</v-chip>
|
|
57
|
+
</v-sheet>
|
|
58
|
+
</v-sheet>
|
|
59
|
+
|
|
60
|
+
<!-- Calendar Grid -->
|
|
61
|
+
<div
|
|
62
|
+
class="calendar-grid pa-0"
|
|
63
|
+
:style="{
|
|
64
|
+
'grid-auto-rows': $vuetify.display.xs
|
|
65
|
+
? '80px'
|
|
66
|
+
: $vuetify.display.sm
|
|
67
|
+
? '100px'
|
|
68
|
+
: '120px',
|
|
69
|
+
}"
|
|
70
|
+
>
|
|
71
|
+
<v-hover
|
|
72
|
+
v-for="date in calendarDates"
|
|
73
|
+
:key="`${date.year}-${date.month}-${date.day}`"
|
|
74
|
+
v-slot="{ isHovering, props }"
|
|
75
|
+
>
|
|
76
|
+
<v-sheet
|
|
77
|
+
v-bind="props"
|
|
78
|
+
:color="getDateColor(date, isHovering || false)"
|
|
79
|
+
:variant="getDateVariant(date)"
|
|
80
|
+
class="cursor-pointer d-flex flex-column transition-all-ease calendar-day-cell"
|
|
81
|
+
:class="[
|
|
82
|
+
!date.isCurrentMonth ? 'opacity-50' : '',
|
|
83
|
+
$vuetify.display.xs
|
|
84
|
+
? 'pa-1'
|
|
85
|
+
: $vuetify.display.sm
|
|
86
|
+
? 'pa-2'
|
|
87
|
+
: 'pa-2',
|
|
88
|
+
]"
|
|
89
|
+
:style="{
|
|
90
|
+
height: $vuetify.display.xs
|
|
91
|
+
? '80px'
|
|
92
|
+
: $vuetify.display.sm
|
|
93
|
+
? '100px'
|
|
94
|
+
: '120px',
|
|
95
|
+
}"
|
|
96
|
+
@click="selectDate(date)"
|
|
97
|
+
>
|
|
98
|
+
<!-- Day Number -->
|
|
99
|
+
<v-chip
|
|
100
|
+
v-if="date.isToday"
|
|
101
|
+
:color="date.isSelected ? 'secondary' : 'primary'"
|
|
102
|
+
variant="flat"
|
|
103
|
+
size="small"
|
|
104
|
+
class="align-self-start mb-1"
|
|
105
|
+
>
|
|
106
|
+
{{ date.day }}
|
|
107
|
+
</v-chip>
|
|
108
|
+
<span
|
|
109
|
+
v-else
|
|
110
|
+
class="font-weight-semibold text-body-2 mb-1 align-self-start"
|
|
111
|
+
>{{ date.day }}</span
|
|
112
|
+
>
|
|
113
|
+
|
|
114
|
+
<!-- Events -->
|
|
115
|
+
<div
|
|
116
|
+
class="d-flex flex-column flex-1-1 ga-1"
|
|
117
|
+
style="overflow: hidden"
|
|
118
|
+
>
|
|
119
|
+
<v-chip
|
|
120
|
+
v-for="event in date.events.slice(0, 3)"
|
|
121
|
+
:key="event.id"
|
|
122
|
+
:color="event.color || 'primary'"
|
|
123
|
+
variant="flat"
|
|
124
|
+
size="x-small"
|
|
125
|
+
class="align-self-stretch text-truncate"
|
|
126
|
+
style="height: auto !important; font-size: 0.625rem !important"
|
|
127
|
+
@click.stop="openEvent(event)"
|
|
128
|
+
>
|
|
129
|
+
{{ event.title }}
|
|
130
|
+
</v-chip>
|
|
131
|
+
|
|
132
|
+
<v-chip
|
|
133
|
+
v-if="date.events.length > 3"
|
|
134
|
+
variant="outlined"
|
|
135
|
+
size="x-small"
|
|
136
|
+
class="align-self-start"
|
|
137
|
+
style="font-size: 0.625rem !important"
|
|
138
|
+
@click.stop="showMoreEvents(date)"
|
|
139
|
+
>
|
|
140
|
+
+{{ date.events.length - 3 }} more
|
|
141
|
+
</v-chip>
|
|
142
|
+
</div>
|
|
143
|
+
</v-sheet>
|
|
144
|
+
</v-hover>
|
|
145
|
+
</div>
|
|
146
|
+
</v-card-text>
|
|
147
|
+
</v-card>
|
|
148
|
+
</template>
|
|
149
|
+
|
|
150
|
+
<script setup lang="ts">
|
|
151
|
+
interface CalendarEvent {
|
|
152
|
+
id: string;
|
|
153
|
+
title: string;
|
|
154
|
+
start: Date;
|
|
155
|
+
end: Date;
|
|
156
|
+
color: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface CalendarDate {
|
|
161
|
+
day: number;
|
|
162
|
+
month: number;
|
|
163
|
+
year: number;
|
|
164
|
+
date: Date;
|
|
165
|
+
dateString: string;
|
|
166
|
+
isCurrentMonth: boolean;
|
|
167
|
+
isToday: boolean;
|
|
168
|
+
isSelected: boolean;
|
|
169
|
+
events: CalendarEvent[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Props
|
|
173
|
+
interface Props {
|
|
174
|
+
events?: CalendarEvent[];
|
|
175
|
+
selectedDate?: Date;
|
|
176
|
+
minDate?: Date;
|
|
177
|
+
maxDate?: Date;
|
|
178
|
+
hideTodayButton?: boolean;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
182
|
+
events: () => [],
|
|
183
|
+
selectedDate: () => new Date(),
|
|
184
|
+
minDate: undefined,
|
|
185
|
+
maxDate: undefined,
|
|
186
|
+
hideTodayButton: false,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Emits
|
|
190
|
+
const emit = defineEmits<{
|
|
191
|
+
"date-selected": [date: Date];
|
|
192
|
+
"event-selected": [event: CalendarEvent];
|
|
193
|
+
"month-changed": [month: number, year: number];
|
|
194
|
+
}>();
|
|
195
|
+
|
|
196
|
+
// Reactive data
|
|
197
|
+
const currentDate = ref(new Date(props.selectedDate));
|
|
198
|
+
const selectedDate = ref(new Date(props.selectedDate));
|
|
199
|
+
|
|
200
|
+
// Computed properties
|
|
201
|
+
const currentMonth = computed(() => {
|
|
202
|
+
return currentDate.value.toLocaleString("default", { month: "long" });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const currentYear = computed(() => {
|
|
206
|
+
return currentDate.value.getFullYear();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
210
|
+
|
|
211
|
+
const calendarDates = computed(() => {
|
|
212
|
+
const dates: CalendarDate[] = [];
|
|
213
|
+
const year = currentDate.value.getFullYear();
|
|
214
|
+
const month = currentDate.value.getMonth();
|
|
215
|
+
|
|
216
|
+
// First day of the month
|
|
217
|
+
const firstDay = new Date(year, month, 1);
|
|
218
|
+
const lastDay = new Date(year, month + 1, 0);
|
|
219
|
+
|
|
220
|
+
// Start from Sunday of the week containing the first day
|
|
221
|
+
const startDate = new Date(firstDay);
|
|
222
|
+
startDate.setDate(startDate.getDate() - startDate.getDay());
|
|
223
|
+
|
|
224
|
+
// End at Saturday of the week containing the last day
|
|
225
|
+
const endDate = new Date(lastDay);
|
|
226
|
+
endDate.setDate(endDate.getDate() + (6 - endDate.getDay()));
|
|
227
|
+
|
|
228
|
+
const today = new Date();
|
|
229
|
+
|
|
230
|
+
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
|
231
|
+
const dateObj = new Date(d);
|
|
232
|
+
const dateString = dateObj.toISOString().split("T")[0];
|
|
233
|
+
|
|
234
|
+
dates.push({
|
|
235
|
+
day: dateObj.getDate(),
|
|
236
|
+
month: dateObj.getMonth(),
|
|
237
|
+
year: dateObj.getFullYear(),
|
|
238
|
+
date: dateObj,
|
|
239
|
+
dateString,
|
|
240
|
+
isCurrentMonth: dateObj.getMonth() === month,
|
|
241
|
+
isToday: dateObj.toDateString() === today.toDateString(),
|
|
242
|
+
isSelected: dateObj.toDateString() === selectedDate.value.toDateString(),
|
|
243
|
+
events: props.events.filter(
|
|
244
|
+
(event) => event.start.toDateString() === dateObj.toDateString()
|
|
245
|
+
),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return dates;
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const selectedDay = computed(() => {
|
|
253
|
+
const today = new Date();
|
|
254
|
+
return {
|
|
255
|
+
day: selectedDate.value.getDate(),
|
|
256
|
+
dayName: selectedDate.value.toLocaleDateString("default", {
|
|
257
|
+
weekday: "long",
|
|
258
|
+
}),
|
|
259
|
+
fullDate: selectedDate.value.toLocaleDateString("default", {
|
|
260
|
+
year: "numeric",
|
|
261
|
+
month: "long",
|
|
262
|
+
day: "numeric",
|
|
263
|
+
}),
|
|
264
|
+
dateString: selectedDate.value.toISOString().split("T")[0],
|
|
265
|
+
isToday: selectedDate.value.toDateString() === today.toDateString(),
|
|
266
|
+
date: selectedDate.value,
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Methods
|
|
271
|
+
const previousMonth = () => {
|
|
272
|
+
const newDate = new Date(currentDate.value);
|
|
273
|
+
newDate.setMonth(newDate.getMonth() - 1);
|
|
274
|
+
currentDate.value = newDate;
|
|
275
|
+
emit("month-changed", newDate.getMonth(), newDate.getFullYear());
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const nextMonth = () => {
|
|
279
|
+
const newDate = new Date(currentDate.value);
|
|
280
|
+
newDate.setMonth(newDate.getMonth() + 1);
|
|
281
|
+
currentDate.value = newDate;
|
|
282
|
+
emit("month-changed", newDate.getMonth(), newDate.getFullYear());
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const goToToday = () => {
|
|
286
|
+
const today = new Date();
|
|
287
|
+
currentDate.value = today;
|
|
288
|
+
selectedDate.value = today;
|
|
289
|
+
emit("date-selected", today);
|
|
290
|
+
emit("month-changed", today.getMonth(), today.getFullYear());
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const selectDate = (date: CalendarDate) => {
|
|
294
|
+
selectedDate.value = date.date;
|
|
295
|
+
emit("date-selected", date.date);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const openEvent = (event: CalendarEvent) => {
|
|
299
|
+
emit("event-selected", event);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const showMoreEvents = (date: CalendarDate) => {
|
|
303
|
+
// Handle showing more events in a modal or expanded view
|
|
304
|
+
console.log("Show more events for", date.dateString, date.events);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Helper methods for Vuetify components
|
|
308
|
+
const getDateColor = (date: CalendarDate, isHovering: boolean) => {
|
|
309
|
+
if (date.isSelected) return "secondary";
|
|
310
|
+
if (date.isToday) return "primary-container";
|
|
311
|
+
if (isHovering && date.isCurrentMonth) return "primary";
|
|
312
|
+
return "transparent";
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const getDateVariant = (date: CalendarDate) => {
|
|
316
|
+
if (date.isSelected) return "tonal";
|
|
317
|
+
if (date.isToday) return "flat";
|
|
318
|
+
return "flat";
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Watch for prop changes
|
|
322
|
+
watch(
|
|
323
|
+
() => props.selectedDate,
|
|
324
|
+
(newDate) => {
|
|
325
|
+
if (newDate) {
|
|
326
|
+
selectedDate.value = new Date(newDate);
|
|
327
|
+
currentDate.value = new Date(newDate);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
</script>
|
|
332
|
+
|
|
333
|
+
<style scoped>
|
|
334
|
+
/* CSS Grid layout for calendar */
|
|
335
|
+
.calendar-weekdays {
|
|
336
|
+
display: grid;
|
|
337
|
+
grid-template-columns: repeat(7, 1fr);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.calendar-grid {
|
|
341
|
+
display: grid;
|
|
342
|
+
grid-template-columns: repeat(7, 1fr);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.weekday-cell {
|
|
346
|
+
border-right: 1px solid rgba(var(--v-theme-on-surface), 0.12);
|
|
347
|
+
border-bottom: 1px solid rgba(var(--v-theme-on-surface), 0.12);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.weekday-cell:nth-child(7n) {
|
|
351
|
+
border-right: none; /* Remove right border on last column */
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.calendar-day-cell {
|
|
355
|
+
border-right: 1px solid rgba(var(--v-theme-on-surface), 0.12);
|
|
356
|
+
border-bottom: 1px solid rgba(var(--v-theme-on-surface), 0.12);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.calendar-day-cell:nth-child(7n) {
|
|
360
|
+
border-right: none; /* Remove right border on last column */
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* Remove bottom border on last row of calendar days */
|
|
364
|
+
.calendar-day-cell:nth-last-child(-n + 7) {
|
|
365
|
+
border-bottom: none;
|
|
366
|
+
}
|
|
367
|
+
</style>
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
</template>
|
|
31
31
|
|
|
32
32
|
<script setup lang="ts">
|
|
33
|
+
import type { PropType } from "vue";
|
|
34
|
+
|
|
33
35
|
const props = defineProps({
|
|
34
36
|
navigationItems: { type: Array<TNavigationItem>, required: true },
|
|
35
37
|
rounded: {
|
|
@@ -41,7 +43,7 @@ const props = defineProps({
|
|
|
41
43
|
default: "pr-2",
|
|
42
44
|
},
|
|
43
45
|
location: {
|
|
44
|
-
type: String
|
|
46
|
+
type: String as PropType<"left" | "right">,
|
|
45
47
|
default: "left",
|
|
46
48
|
},
|
|
47
49
|
});
|