@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,531 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="calendar day-view" :style="{ height: calendarHeight }">
|
|
3
|
+
<div class="day-header">
|
|
4
|
+
<h3>{{ formatDayHeader(current) }}</h3>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<!-- Table-based Day View with Minute Grid -->
|
|
8
|
+
<div class="day-table-container">
|
|
9
|
+
<table class="day-table">
|
|
10
|
+
<colgroup>
|
|
11
|
+
<col style="width: 80px" />
|
|
12
|
+
<col style="width: calc(100% - 80px)" />
|
|
13
|
+
</colgroup>
|
|
14
|
+
<tbody>
|
|
15
|
+
<!-- Hour rows with minute subdivisions -->
|
|
16
|
+
<tr
|
|
17
|
+
v-for="timeSlot in dayTimeSlots"
|
|
18
|
+
:key="`${timeSlot.hour}-${timeSlot.minute}`"
|
|
19
|
+
class="time-row"
|
|
20
|
+
:class="{
|
|
21
|
+
'hour-boundary': timeSlot.minute === 0,
|
|
22
|
+
'quarter-hour':
|
|
23
|
+
timeSlot.minute % 15 === 0 && timeSlot.minute !== 0,
|
|
24
|
+
'five-minute':
|
|
25
|
+
timeSlot.minute % 5 === 0 && timeSlot.minute % 15 !== 0,
|
|
26
|
+
}"
|
|
27
|
+
>
|
|
28
|
+
<!-- Time cell - only show hour label on hour boundaries -->
|
|
29
|
+
<td
|
|
30
|
+
v-if="timeSlot.minute === 0"
|
|
31
|
+
class="time-cell hour-cell"
|
|
32
|
+
:rowspan="60"
|
|
33
|
+
@click="onHourClick(current, timeSlot.display)"
|
|
34
|
+
>
|
|
35
|
+
<div class="hour-label">{{ timeSlot.display }}</div>
|
|
36
|
+
</td>
|
|
37
|
+
|
|
38
|
+
<!-- Event cell -->
|
|
39
|
+
<td
|
|
40
|
+
class="event-cell minute-cell"
|
|
41
|
+
:class="{
|
|
42
|
+
'has-events': getEventForTimeSlot(
|
|
43
|
+
timeSlot.hour,
|
|
44
|
+
timeSlot.minute
|
|
45
|
+
),
|
|
46
|
+
'event-start': isEventStartAtTime(
|
|
47
|
+
timeSlot.hour,
|
|
48
|
+
timeSlot.minute
|
|
49
|
+
),
|
|
50
|
+
'event-continuation': isEventContinuationAtTime(
|
|
51
|
+
timeSlot.hour,
|
|
52
|
+
timeSlot.minute
|
|
53
|
+
),
|
|
54
|
+
}"
|
|
55
|
+
@click="onMinuteClick(current, timeSlot.hour, timeSlot.minute)"
|
|
56
|
+
>
|
|
57
|
+
<!-- Show events horizontally stacked at their start time -->
|
|
58
|
+
<div
|
|
59
|
+
v-if="isEventStartAtTime(timeSlot.hour, timeSlot.minute)"
|
|
60
|
+
class="events-horizontal-container"
|
|
61
|
+
>
|
|
62
|
+
<div
|
|
63
|
+
v-for="(event, eventIndex) in getEventsStartingAt(
|
|
64
|
+
timeSlot.hour,
|
|
65
|
+
timeSlot.minute
|
|
66
|
+
)"
|
|
67
|
+
:key="`${timeSlot.hour}-${timeSlot.minute}-${event.id}`"
|
|
68
|
+
class="table-event horizontal-event"
|
|
69
|
+
:style="{
|
|
70
|
+
backgroundColor: event.color || '#2196f3',
|
|
71
|
+
height: `${getEventHeightInPixels(event)}px`,
|
|
72
|
+
width: '150px',
|
|
73
|
+
marginRight:
|
|
74
|
+
eventIndex <
|
|
75
|
+
getEventsStartingAt(timeSlot.hour, timeSlot.minute)
|
|
76
|
+
.length -
|
|
77
|
+
1
|
|
78
|
+
? '8px'
|
|
79
|
+
: '0',
|
|
80
|
+
zIndex: 10 + eventIndex,
|
|
81
|
+
}"
|
|
82
|
+
@click.stop="onEventClick(event)"
|
|
83
|
+
>
|
|
84
|
+
<div class="table-event-title">
|
|
85
|
+
{{ event.title }}
|
|
86
|
+
</div>
|
|
87
|
+
<div class="table-event-time">
|
|
88
|
+
{{ formatEventTime(event) }}
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</td>
|
|
93
|
+
</tr>
|
|
94
|
+
</tbody>
|
|
95
|
+
</table>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<script setup>
|
|
101
|
+
import { computed } from "vue";
|
|
102
|
+
|
|
103
|
+
const props = defineProps({
|
|
104
|
+
current: {
|
|
105
|
+
type: Date,
|
|
106
|
+
required: true,
|
|
107
|
+
},
|
|
108
|
+
events: {
|
|
109
|
+
type: Array,
|
|
110
|
+
default: () => [],
|
|
111
|
+
},
|
|
112
|
+
calendarHeight: {
|
|
113
|
+
type: String,
|
|
114
|
+
default: "calc(100vh - 100px)",
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const emit = defineEmits(["hourClick", "minuteClick", "eventClick"]);
|
|
119
|
+
|
|
120
|
+
// Helper functions for event calculations
|
|
121
|
+
const isSameDay = (date1, date2) => {
|
|
122
|
+
return date1.toDateString() === date2.toDateString();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const isDateInRange = (date, startDate, endDate) => {
|
|
126
|
+
const checkDate = new Date(date);
|
|
127
|
+
checkDate.setHours(0, 0, 0, 0);
|
|
128
|
+
const start = new Date(startDate);
|
|
129
|
+
start.setHours(0, 0, 0, 0);
|
|
130
|
+
const end = new Date(endDate);
|
|
131
|
+
end.setHours(0, 0, 0, 0);
|
|
132
|
+
|
|
133
|
+
return checkDate >= start && checkDate <= end;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const getEventsForDate = (date) => {
|
|
137
|
+
return props.events.filter((event) =>
|
|
138
|
+
isDateInRange(date, event.startDate, event.endDate)
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Generate time slots for minute-based day view (6 AM to 5 AM, 12*60 = 720 minutes)
|
|
143
|
+
const dayTimeSlots = computed(() => {
|
|
144
|
+
const timeSlots = [];
|
|
145
|
+
|
|
146
|
+
// 6 AM to 11:59 PM (current day)
|
|
147
|
+
for (let hour = 6; hour <= 23; hour++) {
|
|
148
|
+
for (let minute = 0; minute < 60; minute++) {
|
|
149
|
+
const display =
|
|
150
|
+
hour === 12 ? "12 PM" : hour > 12 ? `${hour - 12} PM` : `${hour} AM`;
|
|
151
|
+
timeSlots.push({
|
|
152
|
+
hour: hour,
|
|
153
|
+
minute: minute,
|
|
154
|
+
display: display,
|
|
155
|
+
time24: hour * 60 + minute,
|
|
156
|
+
timeString: `${hour.toString().padStart(2, "0")}:${minute
|
|
157
|
+
.toString()
|
|
158
|
+
.padStart(2, "0")}`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 12 AM to 5:59 AM (next day)
|
|
164
|
+
for (let hour = 0; hour <= 5; hour++) {
|
|
165
|
+
for (let minute = 0; minute < 60; minute++) {
|
|
166
|
+
const display = hour === 0 ? "12 AM" : `${hour} AM`;
|
|
167
|
+
timeSlots.push({
|
|
168
|
+
hour: hour + 24, // Use 24+ to represent next day hours
|
|
169
|
+
minute: minute,
|
|
170
|
+
display: display,
|
|
171
|
+
time24: (hour + 24) * 60 + minute,
|
|
172
|
+
timeString: `${hour.toString().padStart(2, "0")}:${minute
|
|
173
|
+
.toString()
|
|
174
|
+
.padStart(2, "0")}`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return timeSlots;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Helper functions for table-based day view
|
|
183
|
+
const getEventDurationInMinutes = (event) => {
|
|
184
|
+
if (!event.startTime || !event.endTime) return 60; // Default 1 hour
|
|
185
|
+
|
|
186
|
+
const startMinutes = parseTimeToMinutes(event.startTime);
|
|
187
|
+
const endMinutes = parseTimeToMinutes(event.endTime);
|
|
188
|
+
|
|
189
|
+
return Math.max(15, endMinutes - startMinutes); // Minimum 15 minutes
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const parseTimeToMinutes = (timeStr) => {
|
|
193
|
+
if (!timeStr) return 0;
|
|
194
|
+
|
|
195
|
+
if (typeof timeStr === "string") {
|
|
196
|
+
// Handle 24-hour format (e.g., "08:30", "14:45")
|
|
197
|
+
const timeMatch = timeStr.match(/^(\d{1,2}):(\d{2})$/);
|
|
198
|
+
if (timeMatch) {
|
|
199
|
+
return parseInt(timeMatch[1]) * 60 + parseInt(timeMatch[2]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle 12-hour format with AM/PM (e.g., "9:30 AM", "2:45 PM")
|
|
203
|
+
const ampmMatch = timeStr.match(/(\d+):(\d+)\s*(AM|PM)/i);
|
|
204
|
+
if (ampmMatch) {
|
|
205
|
+
let hour = parseInt(ampmMatch[1]);
|
|
206
|
+
const minute = parseInt(ampmMatch[2]);
|
|
207
|
+
const period = ampmMatch[3].toUpperCase();
|
|
208
|
+
|
|
209
|
+
if (period === "PM" && hour !== 12) {
|
|
210
|
+
hour += 12;
|
|
211
|
+
} else if (period === "AM" && hour === 12) {
|
|
212
|
+
hour = 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return hour * 60 + minute;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Handle hour only format (e.g., "9 AM", "2 PM")
|
|
219
|
+
const hourMatch = timeStr.match(/(\d+)\s*(AM|PM)/i);
|
|
220
|
+
if (hourMatch) {
|
|
221
|
+
let hour = parseInt(hourMatch[1]);
|
|
222
|
+
const period = hourMatch[2].toUpperCase();
|
|
223
|
+
|
|
224
|
+
if (period === "PM" && hour !== 12) {
|
|
225
|
+
hour += 12;
|
|
226
|
+
} else if (period === "AM" && hour === 12) {
|
|
227
|
+
hour = 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return hour * 60;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (timeStr instanceof Date) {
|
|
235
|
+
return timeStr.getHours() * 60 + timeStr.getMinutes();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return 0;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Check if an event starts at a specific time
|
|
242
|
+
const isEventStartAtTime = (hour, minute) => {
|
|
243
|
+
const eventsForDay = getEventsForDate(props.current);
|
|
244
|
+
const slotTime = hour * 60 + minute;
|
|
245
|
+
|
|
246
|
+
return eventsForDay.some((event) => {
|
|
247
|
+
const startTime = parseTimeToMinutes(event.startTime);
|
|
248
|
+
|
|
249
|
+
// Handle next day slots
|
|
250
|
+
if (hour >= 24) {
|
|
251
|
+
// For slots after midnight, check if event starts at this time on next day
|
|
252
|
+
const nextDayTime = (hour - 24) * 60 + minute;
|
|
253
|
+
return (
|
|
254
|
+
startTime === nextDayTime &&
|
|
255
|
+
parseTimeToMinutes(event.endTime) < startTime
|
|
256
|
+
); // Event spans midnight
|
|
257
|
+
} else {
|
|
258
|
+
return startTime === slotTime;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Check if this time slot is a continuation of an event (not the start)
|
|
264
|
+
const isEventContinuationAtTime = (hour, minute) => {
|
|
265
|
+
const event = getEventForTimeSlot(hour, minute);
|
|
266
|
+
return event && !isEventStartAtTime(hour, minute);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Get all events that start at a specific hour and minute
|
|
270
|
+
const getEventsStartingAt = (hour, minute) => {
|
|
271
|
+
const eventsForDay = getEventsForDate(props.current);
|
|
272
|
+
const slotTime = hour * 60 + minute;
|
|
273
|
+
|
|
274
|
+
return eventsForDay.filter((event) => {
|
|
275
|
+
const startTime = parseTimeToMinutes(event.startTime);
|
|
276
|
+
|
|
277
|
+
// Handle next day slots
|
|
278
|
+
if (hour >= 24) {
|
|
279
|
+
const nextDayTime = (hour - 24) * 60 + minute;
|
|
280
|
+
return (
|
|
281
|
+
startTime === nextDayTime &&
|
|
282
|
+
parseTimeToMinutes(event.endTime) < startTime
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
return startTime === slotTime;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Calculate event height in pixels based on duration
|
|
291
|
+
const getEventHeightInPixels = (event) => {
|
|
292
|
+
const durationMinutes = getEventDurationInMinutes(event);
|
|
293
|
+
const pixelsPerMinute = 2; // Each minute row is 2px high
|
|
294
|
+
return Math.max(20, durationMinutes * pixelsPerMinute); // Minimum 20px height
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const getEventForTimeSlot = (hour, minute) => {
|
|
298
|
+
const eventsForDay = getEventsForDate(props.current);
|
|
299
|
+
const slotTime = hour * 60 + minute;
|
|
300
|
+
|
|
301
|
+
// Find event that starts exactly at this time
|
|
302
|
+
return eventsForDay.find((event) => {
|
|
303
|
+
const eventStartMinutes = parseTimeToMinutes(event.startTime);
|
|
304
|
+
return eventStartMinutes === slotTime;
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Format event time display
|
|
309
|
+
const formatEventTime = (event) => {
|
|
310
|
+
if (!event.startTime && !event.endTime) return "";
|
|
311
|
+
|
|
312
|
+
const formatTime = (timeStr) => {
|
|
313
|
+
if (!timeStr) return "";
|
|
314
|
+
|
|
315
|
+
// If it's already a formatted 12-hour string (contains AM/PM), return it as-is
|
|
316
|
+
if (typeof timeStr === "string" && /AM|PM/i.test(timeStr)) {
|
|
317
|
+
return timeStr;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// If it's a 24-hour format string, convert to 12-hour
|
|
321
|
+
if (typeof timeStr === "string" && /^\d{1,2}:\d{2}$/.test(timeStr)) {
|
|
322
|
+
const [hours, minutes] = timeStr.split(":");
|
|
323
|
+
const hour24 = parseInt(hours);
|
|
324
|
+
const hour12 = hour24 === 0 ? 12 : hour24 > 12 ? hour24 - 12 : hour24;
|
|
325
|
+
const period = hour24 >= 12 ? "PM" : "AM";
|
|
326
|
+
return `${hour12}:${minutes} ${period}`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// If it's a Date object, format it to 12-hour
|
|
330
|
+
if (timeStr instanceof Date) {
|
|
331
|
+
return timeStr.toLocaleTimeString([], {
|
|
332
|
+
hour: "numeric",
|
|
333
|
+
minute: "2-digit",
|
|
334
|
+
hour12: true,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return timeStr;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const start = formatTime(event.startTime);
|
|
342
|
+
const end = formatTime(event.endTime);
|
|
343
|
+
|
|
344
|
+
if (start && end) {
|
|
345
|
+
return `${start} - ${end}`;
|
|
346
|
+
} else if (start) {
|
|
347
|
+
return start;
|
|
348
|
+
} else if (end) {
|
|
349
|
+
return `Until ${end}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return "";
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Format day header for day view
|
|
356
|
+
const formatDayHeader = (date) => {
|
|
357
|
+
return date.toLocaleDateString("en-US", {
|
|
358
|
+
weekday: "long",
|
|
359
|
+
year: "numeric",
|
|
360
|
+
month: "long",
|
|
361
|
+
day: "numeric",
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Handle hour click for day views
|
|
366
|
+
const onHourClick = (date, hour) => {
|
|
367
|
+
emit("hourClick", {
|
|
368
|
+
date: date,
|
|
369
|
+
hour: hour,
|
|
370
|
+
});
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Handle minute click for table-based day view
|
|
374
|
+
const onMinuteClick = (date, hour, minute) => {
|
|
375
|
+
emit("minuteClick", {
|
|
376
|
+
date: date,
|
|
377
|
+
hour: hour,
|
|
378
|
+
minute: minute,
|
|
379
|
+
time: `${hour}:${minute.toString().padStart(2, "0")}`,
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Handle event click
|
|
384
|
+
const onEventClick = (event) => {
|
|
385
|
+
emit("eventClick", event);
|
|
386
|
+
};
|
|
387
|
+
</script>
|
|
388
|
+
|
|
389
|
+
<style scoped>
|
|
390
|
+
/* Day View Styles */
|
|
391
|
+
.day-view {
|
|
392
|
+
display: flex;
|
|
393
|
+
flex-direction: column;
|
|
394
|
+
height: 100%;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.day-header {
|
|
398
|
+
padding: 16px;
|
|
399
|
+
border-bottom: 1px solid #e0e0e0;
|
|
400
|
+
background-color: #f5f5f5;
|
|
401
|
+
text-align: center;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/* Table-based Day View Styles */
|
|
405
|
+
.day-table-container {
|
|
406
|
+
flex: 1;
|
|
407
|
+
overflow: auto;
|
|
408
|
+
background: white;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.day-table {
|
|
412
|
+
width: 100%;
|
|
413
|
+
border-collapse: collapse;
|
|
414
|
+
table-layout: fixed; /* Fixed layout for better control */
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.day-table colgroup col:first-child {
|
|
418
|
+
width: 80px; /* Fixed width for time column */
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.day-table colgroup col:last-child {
|
|
422
|
+
width: calc(100% - 80px); /* Remaining width for events */
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.time-row {
|
|
426
|
+
height: 2px; /* Each minute row is 2px high */
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.time-row.hour-boundary {
|
|
430
|
+
height: 2px;
|
|
431
|
+
border-top: 2px solid #d0d0d0; /* Strong border for hour boundaries */
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.time-cell {
|
|
435
|
+
border: 1px solid #e0e0e0;
|
|
436
|
+
vertical-align: top;
|
|
437
|
+
position: relative;
|
|
438
|
+
background-color: #fafafa;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.hour-cell {
|
|
442
|
+
padding: 4px 6px;
|
|
443
|
+
text-align: center;
|
|
444
|
+
font-weight: 500;
|
|
445
|
+
border-right: 2px solid #d0d0d0;
|
|
446
|
+
white-space: nowrap;
|
|
447
|
+
width: 80px; /* Fixed width for time column */
|
|
448
|
+
min-width: 80px;
|
|
449
|
+
vertical-align: middle; /* Center the hour label vertically */
|
|
450
|
+
position: sticky;
|
|
451
|
+
left: 0;
|
|
452
|
+
z-index: 10;
|
|
453
|
+
background-color: #fafafa;
|
|
454
|
+
cursor: pointer;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.hour-label {
|
|
458
|
+
font-size: 0.75em;
|
|
459
|
+
color: #666;
|
|
460
|
+
font-weight: 600;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.event-cell {
|
|
464
|
+
border: 1px solid #f8f8f8;
|
|
465
|
+
position: relative;
|
|
466
|
+
cursor: default; /* Default cursor for empty cells */
|
|
467
|
+
transition: background-color 0.2s;
|
|
468
|
+
width: auto; /* Let the cell expand to fit content */
|
|
469
|
+
min-width: 200px; /* Minimum width for events */
|
|
470
|
+
padding: 0; /* Remove padding to allow full control */
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.minute-cell {
|
|
474
|
+
border-top: none;
|
|
475
|
+
border-bottom: none;
|
|
476
|
+
border-left: 1px solid #f8f8f8;
|
|
477
|
+
padding: 0;
|
|
478
|
+
margin: 0;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.events-horizontal-container {
|
|
482
|
+
display: flex;
|
|
483
|
+
align-items: flex-start;
|
|
484
|
+
gap: 8px;
|
|
485
|
+
position: absolute;
|
|
486
|
+
top: 0;
|
|
487
|
+
left: 2px;
|
|
488
|
+
right: 2px;
|
|
489
|
+
min-height: 2px; /* Match the minute row height */
|
|
490
|
+
overflow: visible; /* Allow events to overflow for long durations */
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.table-event {
|
|
494
|
+
border-radius: 4px;
|
|
495
|
+
padding: 4px 6px;
|
|
496
|
+
color: white;
|
|
497
|
+
font-size: 0.75em;
|
|
498
|
+
cursor: pointer;
|
|
499
|
+
overflow: hidden;
|
|
500
|
+
display: flex;
|
|
501
|
+
flex-direction: column;
|
|
502
|
+
justify-content: flex-start;
|
|
503
|
+
box-sizing: border-box;
|
|
504
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
|
505
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
506
|
+
transition: transform 0.1s, box-shadow 0.1s;
|
|
507
|
+
min-height: 20px; /* Minimum height for very short events */
|
|
508
|
+
flex-shrink: 0; /* Prevent events from shrinking */
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.table-event.horizontal-event {
|
|
512
|
+
position: relative; /* Changed from absolute to relative for horizontal stacking */
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.table-event-title {
|
|
516
|
+
font-weight: 600;
|
|
517
|
+
white-space: nowrap;
|
|
518
|
+
overflow: hidden;
|
|
519
|
+
text-overflow: ellipsis;
|
|
520
|
+
line-height: 1.2;
|
|
521
|
+
margin-bottom: 2px;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.table-event-time {
|
|
525
|
+
font-size: 0.85em;
|
|
526
|
+
opacity: 0.9;
|
|
527
|
+
white-space: nowrap;
|
|
528
|
+
overflow: hidden;
|
|
529
|
+
text-overflow: ellipsis;
|
|
530
|
+
}
|
|
531
|
+
</style>
|