@eeplatform/nuxt-layer-common 1.2.10 → 1.3.0
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 +199 -0
- package/components/CalendarDay.vue +531 -0
- package/components/CalendarMonth.vue +452 -0
- package/components/CalendarWeek.vue +545 -0
- package/components/CalendarYear.vue +295 -0
- package/components/OfficeForm.vue +194 -0
- package/components/OfficeMain.vue +126 -0
- package/components/SchoolFormUpload.vue +7 -50
- package/components/SchoolMain.vue +3 -3
- package/composables/useOffice.ts +40 -0
- package/composables/usePlantilla.ts +52 -0
- package/package.json +1 -1
- package/plugins/API.ts +12 -0
- package/types/office.d.ts +12 -0
- package/types/plantilla.d.ts +29 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="calendar week-view" :style="{ height: calendarHeight }">
|
|
3
|
+
<div class="week-header">
|
|
4
|
+
<div class="time-column header-time-column"></div>
|
|
5
|
+
<div v-for="day in weekDays" :key="day.date" class="week-day-header">
|
|
6
|
+
<div class="day-name">{{ day.dayName }}</div>
|
|
7
|
+
<div class="day-date" :class="{ today: day.isToday }">
|
|
8
|
+
{{ day.dayNumber }}
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="week-body">
|
|
13
|
+
<div class="week-table-container">
|
|
14
|
+
<table class="week-table">
|
|
15
|
+
<colgroup>
|
|
16
|
+
<col style="width: 80px" />
|
|
17
|
+
<col
|
|
18
|
+
v-for="day in weekDays"
|
|
19
|
+
:key="day.date"
|
|
20
|
+
style="width: calc((100% - 80px) / 7)"
|
|
21
|
+
/>
|
|
22
|
+
</colgroup>
|
|
23
|
+
<tbody>
|
|
24
|
+
<!-- Generate 732 rows: 12 hours × 61 rows each (60 minutes + 1 border) -->
|
|
25
|
+
<tr
|
|
26
|
+
v-for="(timeSlot, rowIndex) in getWeekTimeSlots()"
|
|
27
|
+
:key="`${timeSlot.hour}-${timeSlot.minute}`"
|
|
28
|
+
class="week-time-row"
|
|
29
|
+
:class="{
|
|
30
|
+
'hour-boundary': timeSlot.isBoundary,
|
|
31
|
+
'minute-row': !timeSlot.isBoundary,
|
|
32
|
+
}"
|
|
33
|
+
>
|
|
34
|
+
<!-- Hour column - only show hour label on boundary rows -->
|
|
35
|
+
<td v-if="timeSlot.isBoundary" class="hour-cell" :rowspan="61">
|
|
36
|
+
<div class="hour-label">{{ timeSlot.display }}</div>
|
|
37
|
+
</td>
|
|
38
|
+
|
|
39
|
+
<!-- Event columns for each day -->
|
|
40
|
+
<td
|
|
41
|
+
v-for="day in weekDays"
|
|
42
|
+
:key="day.date"
|
|
43
|
+
v-if="
|
|
44
|
+
shouldRenderEventCell(
|
|
45
|
+
day.date,
|
|
46
|
+
timeSlot.hour,
|
|
47
|
+
timeSlot.minute,
|
|
48
|
+
rowIndex
|
|
49
|
+
)
|
|
50
|
+
"
|
|
51
|
+
class="event-cell"
|
|
52
|
+
:rowspan="
|
|
53
|
+
getEventRowspan(day.date, timeSlot.hour, timeSlot.minute)
|
|
54
|
+
"
|
|
55
|
+
@click="onMinuteClick(day.date, timeSlot.hour, timeSlot.minute)"
|
|
56
|
+
>
|
|
57
|
+
<!-- Render event or empty cell -->
|
|
58
|
+
<div
|
|
59
|
+
v-if="
|
|
60
|
+
getEventForTimeSlot(
|
|
61
|
+
day.date,
|
|
62
|
+
timeSlot.hour,
|
|
63
|
+
timeSlot.minute
|
|
64
|
+
)
|
|
65
|
+
"
|
|
66
|
+
class="week-event-content"
|
|
67
|
+
:style="{
|
|
68
|
+
backgroundColor:
|
|
69
|
+
getEventForTimeSlot(
|
|
70
|
+
day.date,
|
|
71
|
+
timeSlot.hour,
|
|
72
|
+
timeSlot.minute
|
|
73
|
+
).color || '#2196f3',
|
|
74
|
+
height: '100%',
|
|
75
|
+
}"
|
|
76
|
+
@click.stop="
|
|
77
|
+
onEventClick(
|
|
78
|
+
getEventForTimeSlot(
|
|
79
|
+
day.date,
|
|
80
|
+
timeSlot.hour,
|
|
81
|
+
timeSlot.minute
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
"
|
|
85
|
+
>
|
|
86
|
+
<div class="event-title">
|
|
87
|
+
{{
|
|
88
|
+
getEventForTimeSlot(
|
|
89
|
+
day.date,
|
|
90
|
+
timeSlot.hour,
|
|
91
|
+
timeSlot.minute
|
|
92
|
+
).title
|
|
93
|
+
}}
|
|
94
|
+
</div>
|
|
95
|
+
<div class="event-time">
|
|
96
|
+
{{
|
|
97
|
+
formatEventTime(
|
|
98
|
+
getEventForTimeSlot(
|
|
99
|
+
day.date,
|
|
100
|
+
timeSlot.hour,
|
|
101
|
+
timeSlot.minute
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
}}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<!-- Empty cell -->
|
|
108
|
+
<div v-else class="empty-cell"></div>
|
|
109
|
+
</td>
|
|
110
|
+
</tr>
|
|
111
|
+
</tbody>
|
|
112
|
+
</table>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</template>
|
|
117
|
+
|
|
118
|
+
<script setup>
|
|
119
|
+
import { computed } from "vue";
|
|
120
|
+
|
|
121
|
+
const props = defineProps({
|
|
122
|
+
current: {
|
|
123
|
+
type: Date,
|
|
124
|
+
required: true,
|
|
125
|
+
},
|
|
126
|
+
events: {
|
|
127
|
+
type: Array,
|
|
128
|
+
default: () => [],
|
|
129
|
+
},
|
|
130
|
+
calendarHeight: {
|
|
131
|
+
type: String,
|
|
132
|
+
default: "calc(100vh - 100px)",
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const emit = defineEmits(["minuteClick", "eventClick"]);
|
|
137
|
+
|
|
138
|
+
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
139
|
+
|
|
140
|
+
// Helper functions for event calculations
|
|
141
|
+
const isSameDay = (date1, date2) => {
|
|
142
|
+
return date1.toDateString() === date2.toDateString();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const isDateInRange = (date, startDate, endDate) => {
|
|
146
|
+
const checkDate = new Date(date);
|
|
147
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
148
|
+
const start = new Date(startDate);
|
|
149
|
+
start.setHours(0, 0, 0, 0);
|
|
150
|
+
const end = new Date(endDate);
|
|
151
|
+
end.setHours(0, 0, 0, 0);
|
|
152
|
+
|
|
153
|
+
return checkDate >= start && checkDate <= end;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const getEventsForDate = (date) => {
|
|
157
|
+
return props.events.filter((event) =>
|
|
158
|
+
isDateInRange(date, event.startDate, event.endDate)
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Week view computed properties
|
|
163
|
+
const weekDays = computed(() => {
|
|
164
|
+
const days = [];
|
|
165
|
+
const startOfWeek = new Date(props.current);
|
|
166
|
+
startOfWeek.setDate(props.current.getDate() - props.current.getDay());
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < 7; i++) {
|
|
169
|
+
const day = new Date(startOfWeek);
|
|
170
|
+
day.setDate(startOfWeek.getDate() + i);
|
|
171
|
+
days.push({
|
|
172
|
+
date: day,
|
|
173
|
+
dayName: weekdays[i],
|
|
174
|
+
dayNumber: day.getDate(),
|
|
175
|
+
isToday: isSameDay(day, new Date()),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return days;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Week view table-based functions
|
|
183
|
+
const getWeekTimeSlots = () => {
|
|
184
|
+
const slots = [];
|
|
185
|
+
for (let hour = 6; hour < 18; hour++) {
|
|
186
|
+
// 6 AM to 6 PM = 12 hours
|
|
187
|
+
// Add boundary row for hour
|
|
188
|
+
slots.push({
|
|
189
|
+
hour,
|
|
190
|
+
minute: 0,
|
|
191
|
+
isBoundary: true,
|
|
192
|
+
display: formatHour(hour),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Add 60 minute rows
|
|
196
|
+
for (let minute = 0; minute < 60; minute++) {
|
|
197
|
+
slots.push({
|
|
198
|
+
hour,
|
|
199
|
+
minute,
|
|
200
|
+
isBoundary: false,
|
|
201
|
+
display: `${hour}:${minute.toString().padStart(2, "0")}`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return slots;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const shouldRenderEventCell = (date, hour, minute, rowIndex) => {
|
|
209
|
+
// Don't render cells for boundary rows (they're handled by hour column)
|
|
210
|
+
const timeSlot = getWeekTimeSlots()[rowIndex];
|
|
211
|
+
if (timeSlot.isBoundary) return false;
|
|
212
|
+
|
|
213
|
+
// Check if this cell is already covered by a previous event's rowspan
|
|
214
|
+
return !isRowCoveredByPreviousEvent(date, hour, minute);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const isRowCoveredByPreviousEvent = (date, hour, minute) => {
|
|
218
|
+
const eventsForDate = getEventsForDate(date);
|
|
219
|
+
const currentTimeInMinutes = hour * 60 + minute;
|
|
220
|
+
|
|
221
|
+
// Check if any event that started before this time is still spanning this row
|
|
222
|
+
return eventsForDate.some((event) => {
|
|
223
|
+
const eventStartMinutes = parseTimeToMinutesEnhanced(event.startTime);
|
|
224
|
+
const eventEndMinutes = parseTimeToMinutesEnhanced(event.endTime);
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
eventStartMinutes < currentTimeInMinutes &&
|
|
228
|
+
currentTimeInMinutes < eventEndMinutes
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const getEventForTimeSlot = (date, hour, minute) => {
|
|
234
|
+
const eventsForDate = getEventsForDate(date);
|
|
235
|
+
const currentTimeInMinutes = hour * 60 + minute;
|
|
236
|
+
|
|
237
|
+
// Find event that starts exactly at this time
|
|
238
|
+
return eventsForDate.find((event) => {
|
|
239
|
+
const eventStartMinutes = parseTimeToMinutesEnhanced(event.startTime);
|
|
240
|
+
return eventStartMinutes === currentTimeInMinutes;
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const getEventRowspan = (date, hour, minute) => {
|
|
245
|
+
const event = getEventForTimeSlot(date, hour, minute);
|
|
246
|
+
|
|
247
|
+
if (event) {
|
|
248
|
+
// Calculate duration in minutes for the event
|
|
249
|
+
const startMinutes = parseTimeToMinutesEnhanced(event.startTime);
|
|
250
|
+
const endMinutes = parseTimeToMinutesEnhanced(event.endTime);
|
|
251
|
+
return endMinutes - startMinutes; // Each minute = 1 row
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check if we need to fill empty space until next event or end of hour
|
|
255
|
+
const currentTimeInMinutes = hour * 60 + minute;
|
|
256
|
+
const nextHourStart = (hour + 1) * 60;
|
|
257
|
+
|
|
258
|
+
// Find next event in this hour
|
|
259
|
+
const eventsForDate = getEventsForDate(date);
|
|
260
|
+
const nextEvent = eventsForDate.find((event) => {
|
|
261
|
+
const eventStartMinutes = parseTimeToMinutesEnhanced(event.startTime);
|
|
262
|
+
return (
|
|
263
|
+
eventStartMinutes > currentTimeInMinutes &&
|
|
264
|
+
eventStartMinutes < nextHourStart
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (nextEvent) {
|
|
269
|
+
const nextEventStart = parseTimeToMinutesEnhanced(nextEvent.startTime);
|
|
270
|
+
return nextEventStart - currentTimeInMinutes;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Fill until end of hour
|
|
274
|
+
return nextHourStart - currentTimeInMinutes;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Enhanced parseTimeToMinutes to handle next-day events better
|
|
278
|
+
const parseTimeToMinutesEnhanced = (timeStr) => {
|
|
279
|
+
if (!timeStr) return 0;
|
|
280
|
+
|
|
281
|
+
if (typeof timeStr === "string") {
|
|
282
|
+
// Handle 24-hour format (e.g., "08:30", "14:45")
|
|
283
|
+
const timeMatch = timeStr.match(/^(\d{1,2}):(\d{2})$/);
|
|
284
|
+
if (timeMatch) {
|
|
285
|
+
return parseInt(timeMatch[1]) * 60 + parseInt(timeMatch[2]);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Handle 12-hour format with AM/PM (e.g., "9:30 AM", "2:45 PM")
|
|
289
|
+
const ampmMatch = timeStr.match(/(\d+):(\d+)\s*(AM|PM)/i);
|
|
290
|
+
if (ampmMatch) {
|
|
291
|
+
let hour = parseInt(ampmMatch[1]);
|
|
292
|
+
const minute = parseInt(ampmMatch[2]);
|
|
293
|
+
const period = ampmMatch[3].toUpperCase();
|
|
294
|
+
|
|
295
|
+
if (period === "PM" && hour !== 12) {
|
|
296
|
+
hour += 12;
|
|
297
|
+
} else if (period === "AM" && hour === 12) {
|
|
298
|
+
hour = 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return hour * 60 + minute;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Handle hour only format (e.g., "9 AM", "2 PM")
|
|
305
|
+
const hourMatch = timeStr.match(/(\d+)\s*(AM|PM)/i);
|
|
306
|
+
if (hourMatch) {
|
|
307
|
+
let hour = parseInt(hourMatch[1]);
|
|
308
|
+
const period = hourMatch[2].toUpperCase();
|
|
309
|
+
|
|
310
|
+
if (period === "PM" && hour !== 12) {
|
|
311
|
+
hour += 12;
|
|
312
|
+
} else if (period === "AM" && hour === 12) {
|
|
313
|
+
hour = 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return hour * 60;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (timeStr instanceof Date) {
|
|
321
|
+
return timeStr.getHours() * 60 + timeStr.getMinutes();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return 0;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Format event time display
|
|
328
|
+
const formatEventTime = (event) => {
|
|
329
|
+
if (!event.startTime && !event.endTime) return "";
|
|
330
|
+
|
|
331
|
+
const formatTime = (timeStr) => {
|
|
332
|
+
if (!timeStr) return "";
|
|
333
|
+
|
|
334
|
+
// If it's already a formatted 12-hour string (contains AM/PM), return it as-is
|
|
335
|
+
if (typeof timeStr === "string" && /AM|PM/i.test(timeStr)) {
|
|
336
|
+
return timeStr;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// If it's a 24-hour format string, convert to 12-hour
|
|
340
|
+
if (typeof timeStr === "string" && /^\d{1,2}:\d{2}$/.test(timeStr)) {
|
|
341
|
+
const [hours, minutes] = timeStr.split(":");
|
|
342
|
+
const hour24 = parseInt(hours);
|
|
343
|
+
const hour12 = hour24 === 0 ? 12 : hour24 > 12 ? hour24 - 12 : hour24;
|
|
344
|
+
const period = hour24 >= 12 ? "PM" : "AM";
|
|
345
|
+
return `${hour12}:${minutes} ${period}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// If it's a Date object, format it to 12-hour
|
|
349
|
+
if (timeStr instanceof Date) {
|
|
350
|
+
return timeStr.toLocaleTimeString([], {
|
|
351
|
+
hour: "numeric",
|
|
352
|
+
minute: "2-digit",
|
|
353
|
+
hour12: true,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return timeStr;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const start = formatTime(event.startTime);
|
|
361
|
+
const end = formatTime(event.endTime);
|
|
362
|
+
|
|
363
|
+
if (start && end) {
|
|
364
|
+
return `${start} - ${end}`;
|
|
365
|
+
} else if (start) {
|
|
366
|
+
return start;
|
|
367
|
+
} else if (end) {
|
|
368
|
+
return `Until ${end}`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return "";
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Format hour number to display string (e.g., 9 -> "9:00 AM", 13 -> "1:00 PM")
|
|
375
|
+
const formatHour = (hour) => {
|
|
376
|
+
if (hour === 0) return "12:00 AM";
|
|
377
|
+
if (hour === 12) return "12:00 PM";
|
|
378
|
+
if (hour < 12) return `${hour}:00 AM`;
|
|
379
|
+
return `${hour - 12}:00 PM`;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Handle minute click for week view
|
|
383
|
+
const onMinuteClick = (date, hour, minute) => {
|
|
384
|
+
emit("minuteClick", {
|
|
385
|
+
date: date,
|
|
386
|
+
hour: hour,
|
|
387
|
+
minute: minute,
|
|
388
|
+
time: `${hour}:${minute.toString().padStart(2, "0")}`,
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Handle event click
|
|
393
|
+
const onEventClick = (event) => {
|
|
394
|
+
emit("eventClick", event);
|
|
395
|
+
};
|
|
396
|
+
</script>
|
|
397
|
+
|
|
398
|
+
<style scoped>
|
|
399
|
+
/* Week View Styles */
|
|
400
|
+
.week-view {
|
|
401
|
+
display: flex;
|
|
402
|
+
flex-direction: column;
|
|
403
|
+
height: 100%;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.week-header {
|
|
407
|
+
display: grid;
|
|
408
|
+
grid-template-columns: 60px repeat(7, 1fr);
|
|
409
|
+
border-bottom: 1px solid #e0e0e0;
|
|
410
|
+
background-color: #f5f5f5;
|
|
411
|
+
overflow-x: auto;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.time-column {
|
|
415
|
+
display: flex;
|
|
416
|
+
flex-direction: column;
|
|
417
|
+
border-right: 1px solid #e0e0e0;
|
|
418
|
+
position: sticky;
|
|
419
|
+
left: 0;
|
|
420
|
+
z-index: 10;
|
|
421
|
+
background-color: #f9f9f9;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.header-time-column {
|
|
425
|
+
background-color: #f5f5f5;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.week-day-header {
|
|
429
|
+
text-align: center;
|
|
430
|
+
padding: 8px 4px;
|
|
431
|
+
border-right: 1px solid #e0e0e0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.day-name {
|
|
435
|
+
font-size: 0.8em;
|
|
436
|
+
color: #666;
|
|
437
|
+
margin-bottom: 2px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.day-date {
|
|
441
|
+
font-size: 1.2em;
|
|
442
|
+
font-weight: 500;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.day-date.today {
|
|
446
|
+
color: #1976d2;
|
|
447
|
+
font-weight: 700;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.week-body {
|
|
451
|
+
display: flex;
|
|
452
|
+
flex-direction: column;
|
|
453
|
+
flex: 1;
|
|
454
|
+
overflow: hidden;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.week-table-container {
|
|
458
|
+
flex: 1;
|
|
459
|
+
overflow-y: auto;
|
|
460
|
+
overflow-x: auto;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.week-table {
|
|
464
|
+
width: 100%;
|
|
465
|
+
border-collapse: collapse;
|
|
466
|
+
table-layout: fixed;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.week-time-row.hour-boundary {
|
|
470
|
+
height: 3px;
|
|
471
|
+
border-bottom: 2px solid #e0e0e0;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.week-time-row.minute-row {
|
|
475
|
+
height: 2px;
|
|
476
|
+
border-bottom: 1px solid transparent;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.hour-cell {
|
|
480
|
+
background-color: #f9f9f9;
|
|
481
|
+
border-right: 1px solid #e0e0e0;
|
|
482
|
+
vertical-align: top;
|
|
483
|
+
padding: 4px;
|
|
484
|
+
text-align: center;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.hour-label {
|
|
488
|
+
font-size: 0.8em;
|
|
489
|
+
color: #666;
|
|
490
|
+
font-weight: 500;
|
|
491
|
+
writing-mode: horizontal-tb;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.event-cell {
|
|
495
|
+
border-right: 1px solid #e0e0e0;
|
|
496
|
+
border-bottom: 1px solid transparent;
|
|
497
|
+
padding: 0;
|
|
498
|
+
margin: 0;
|
|
499
|
+
vertical-align: top;
|
|
500
|
+
position: relative;
|
|
501
|
+
cursor: pointer;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.event-cell:hover {
|
|
505
|
+
background-color: rgba(25, 118, 210, 0.05);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.week-event-content {
|
|
509
|
+
display: flex;
|
|
510
|
+
flex-direction: column;
|
|
511
|
+
justify-content: flex-start;
|
|
512
|
+
padding: 2px 4px;
|
|
513
|
+
color: white;
|
|
514
|
+
font-size: 0.7em;
|
|
515
|
+
border-radius: 3px;
|
|
516
|
+
margin: 1px;
|
|
517
|
+
cursor: pointer;
|
|
518
|
+
overflow: hidden;
|
|
519
|
+
box-sizing: border-box;
|
|
520
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.week-event-content .event-title {
|
|
524
|
+
font-weight: 600;
|
|
525
|
+
line-height: 1.1;
|
|
526
|
+
margin-bottom: 1px;
|
|
527
|
+
overflow: hidden;
|
|
528
|
+
text-overflow: ellipsis;
|
|
529
|
+
white-space: nowrap;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.week-event-content .event-time {
|
|
533
|
+
font-size: 0.8em;
|
|
534
|
+
opacity: 0.9;
|
|
535
|
+
line-height: 1;
|
|
536
|
+
overflow: hidden;
|
|
537
|
+
text-overflow: ellipsis;
|
|
538
|
+
white-space: nowrap;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.empty-cell {
|
|
542
|
+
height: 100%;
|
|
543
|
+
width: 100%;
|
|
544
|
+
}
|
|
545
|
+
</style>
|