@asd20/ui 3.2.945 → 3.2.947
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/package.json +1 -1
- package/src/components/atoms/Asd20CalendarEventButton/index.vue +22 -11
- package/src/components/organisms/Asd20Calendar/index.vue +164 -17
- package/src/data/events/district-events.json +22 -22
- package/src/helpers/expandEvents.js +67 -64
- package/src/helpers/mapEventsToDays.js +60 -58
package/package.json
CHANGED
|
@@ -6,18 +6,29 @@
|
|
|
6
6
|
target="_blank"
|
|
7
7
|
:style="{ '--accent-color': event.calendarColor }"
|
|
8
8
|
>
|
|
9
|
-
<span class="title"
|
|
10
|
-
|
|
11
|
-
>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<span v-if="event.
|
|
9
|
+
<span class="title">
|
|
10
|
+
<span>{{ sanitizedSummary }}</span>
|
|
11
|
+
</span>
|
|
12
|
+
|
|
13
|
+
<!-- Show time if NOT all-day -->
|
|
14
|
+
<span v-if="!event.allDay" class="time"> {{ start }}{{ end }} </span>
|
|
15
|
+
|
|
16
|
+
<!-- Show start-time if NOT all-day -->
|
|
17
|
+
<span v-if="!event.allDay" class="start-time">
|
|
18
|
+
{{ startTime }}
|
|
19
|
+
</span>
|
|
20
|
+
|
|
21
|
+
<!-- Show location if NOT all-day and location exists -->
|
|
22
|
+
<span v-if="!event.allDay && event.location.length > 0" class="location">
|
|
15
23
|
{{ event.location }}
|
|
16
24
|
</span>
|
|
25
|
+
|
|
26
|
+
<!-- Optional: show description if you want -->
|
|
17
27
|
<span class="description">{{ event.description }}</span>
|
|
18
28
|
</button>
|
|
19
29
|
</template>
|
|
20
30
|
|
|
31
|
+
|
|
21
32
|
<script>
|
|
22
33
|
import { format, parse } from 'date-fns'
|
|
23
34
|
|
|
@@ -32,8 +43,7 @@ export default {
|
|
|
32
43
|
return {
|
|
33
44
|
'asd20-calendar-event-button': true,
|
|
34
45
|
'asd20-calendar-event-button--large': this.large,
|
|
35
|
-
'asd20-calendar-event-button--all-day':
|
|
36
|
-
this.event.allday || this.event.multiDay,
|
|
46
|
+
'asd20-calendar-event-button--all-day': this.event.allDay,
|
|
37
47
|
}
|
|
38
48
|
},
|
|
39
49
|
startTime() {
|
|
@@ -45,7 +55,7 @@ export default {
|
|
|
45
55
|
start() {
|
|
46
56
|
return format(
|
|
47
57
|
parse(this.event.start, "yyyy-MM-dd'T'HH:mm:ssX", new Date()),
|
|
48
|
-
'h:mm aa, MMMM
|
|
58
|
+
'h:mm aa, MMMM dd yyyy'
|
|
49
59
|
)
|
|
50
60
|
},
|
|
51
61
|
end() {
|
|
@@ -53,7 +63,7 @@ export default {
|
|
|
53
63
|
' to ' +
|
|
54
64
|
format(
|
|
55
65
|
parse(this.event.end, "yyyy-MM-dd'T'HH:mm:ssX", new Date()),
|
|
56
|
-
'h:mm aa, MMMM
|
|
66
|
+
'h:mm aa, MMMM dd yyyy'
|
|
57
67
|
)
|
|
58
68
|
)
|
|
59
69
|
},
|
|
@@ -62,7 +72,6 @@ export default {
|
|
|
62
72
|
},
|
|
63
73
|
},
|
|
64
74
|
methods: {
|
|
65
|
-
// specifically for rSchool events that have extraneous school names in the summary
|
|
66
75
|
sanitizeSummary(summary) {
|
|
67
76
|
if (!summary) return summary
|
|
68
77
|
const pattern = /vs\.[^>]*>Multiple Schools/
|
|
@@ -70,6 +79,8 @@ export default {
|
|
|
70
79
|
},
|
|
71
80
|
},
|
|
72
81
|
}
|
|
82
|
+
|
|
83
|
+
|
|
73
84
|
</script>
|
|
74
85
|
|
|
75
86
|
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="classes">
|
|
3
|
+
<!-- Weekday header -->
|
|
4
|
+
<div class="asd20-calendar__weekdays">
|
|
5
|
+
<div
|
|
6
|
+
v-for="(dayName, index) in weekdayNames"
|
|
7
|
+
:key="index"
|
|
8
|
+
class="asd20-calendar__weekday"
|
|
9
|
+
>
|
|
10
|
+
{{ shortWeekdays ? dayName.charAt(0) : dayName }}
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<!-- Days -->
|
|
3
14
|
<ol class="asd20-calendar__days">
|
|
4
15
|
<li
|
|
5
16
|
class="asd20-calendar__day"
|
|
@@ -14,18 +25,23 @@
|
|
|
14
25
|
@click="onClickDay($event, day)"
|
|
15
26
|
>
|
|
16
27
|
<div class="asd20-calendar__date">
|
|
17
|
-
<span class="weekday">{{ day.weekday }}</span>
|
|
18
28
|
<span class="month">{{ day.month }}</span>
|
|
19
29
|
<span class="day">{{ day.number }}</span>
|
|
20
30
|
<span class="year">{{ day.year }}</span>
|
|
21
31
|
</div>
|
|
22
|
-
<div
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
<div>
|
|
33
|
+
<div
|
|
34
|
+
class="asd20-calendar__events"
|
|
35
|
+
v-if="!annualView || !day.outsideOfCurrentMonth"
|
|
36
|
+
>
|
|
37
|
+
<Asd20CalendarEventButton
|
|
38
|
+
v-for="(event, index) in day.events"
|
|
39
|
+
:key="index"
|
|
40
|
+
:event="event"
|
|
41
|
+
:class="day.events.length > 1 ? 'small-dot' : ''"
|
|
42
|
+
@click.native="onEventClick(event)"
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
29
45
|
</div>
|
|
30
46
|
</li>
|
|
31
47
|
</ol>
|
|
@@ -67,6 +83,8 @@ export default {
|
|
|
67
83
|
month: { type: Number, default: new Date().getMonth() },
|
|
68
84
|
year: { type: Number, default: new Date().getFullYear() },
|
|
69
85
|
absolute: { type: Boolean, default: true },
|
|
86
|
+
shortWeekdays: { type: Boolean, default: false },
|
|
87
|
+
annualView: { type: Boolean, default: false },
|
|
70
88
|
},
|
|
71
89
|
|
|
72
90
|
data: () => ({
|
|
@@ -92,10 +110,18 @@ export default {
|
|
|
92
110
|
return {
|
|
93
111
|
'asd20-calendar': true,
|
|
94
112
|
'asd20-calendar--absolute': this.absolute,
|
|
113
|
+
'asd20-calendar--annual': this.annualView,
|
|
95
114
|
}
|
|
96
115
|
},
|
|
97
116
|
days() {
|
|
98
|
-
return mapEventsToDays(
|
|
117
|
+
return mapEventsToDays(
|
|
118
|
+
expandEvents(this.events, this.annualView),
|
|
119
|
+
this.year,
|
|
120
|
+
this.month,
|
|
121
|
+
)
|
|
122
|
+
},
|
|
123
|
+
weekdayNames() {
|
|
124
|
+
return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
99
125
|
},
|
|
100
126
|
},
|
|
101
127
|
methods: {
|
|
@@ -131,6 +157,22 @@ $accent: var(--color__accent);
|
|
|
131
157
|
right: 0;
|
|
132
158
|
}
|
|
133
159
|
|
|
160
|
+
&__weekdays {
|
|
161
|
+
display: flex;
|
|
162
|
+
justify-content: space-around;
|
|
163
|
+
align-items: center;
|
|
164
|
+
flex-grow: 0;
|
|
165
|
+
flex-shrink: 0;
|
|
166
|
+
width: 100%;
|
|
167
|
+
height: space(0.875);
|
|
168
|
+
margin-bottom: space(0.125);
|
|
169
|
+
padding: 0 space(0.125);
|
|
170
|
+
background: var(--color__accent-t80);
|
|
171
|
+
color: var(--color__primary);
|
|
172
|
+
font-size: 0.875rem;
|
|
173
|
+
line-height: 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
134
176
|
&__day-events {
|
|
135
177
|
flex-grow: 1;
|
|
136
178
|
&::v-deep .asd20-calendar-event-button:not(:last-of-type) {
|
|
@@ -154,7 +196,6 @@ $accent: var(--color__accent);
|
|
|
154
196
|
height: 40%;
|
|
155
197
|
width: 100%;
|
|
156
198
|
flex-shrink: 0;
|
|
157
|
-
// margin-right: space(-0.25);
|
|
158
199
|
}
|
|
159
200
|
&__day {
|
|
160
201
|
position: relative;
|
|
@@ -246,6 +287,111 @@ $accent: var(--color__accent);
|
|
|
246
287
|
}
|
|
247
288
|
}
|
|
248
289
|
}
|
|
290
|
+
&--annual {
|
|
291
|
+
.asd20-calendar__weekdays {
|
|
292
|
+
padding: 0;
|
|
293
|
+
}
|
|
294
|
+
.asd20-calendar__days {
|
|
295
|
+
position: relative;
|
|
296
|
+
list-style: none;
|
|
297
|
+
margin: 0 0 0 0;
|
|
298
|
+
padding: 0;
|
|
299
|
+
position: relative;
|
|
300
|
+
flex-grow: 0;
|
|
301
|
+
display: flex;
|
|
302
|
+
justify-content: flex-start;
|
|
303
|
+
align-items: stretch;
|
|
304
|
+
flex-wrap: wrap;
|
|
305
|
+
height: 40%;
|
|
306
|
+
width: 100%;
|
|
307
|
+
flex-shrink: 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.asd20-calendar__day {
|
|
311
|
+
position: relative;
|
|
312
|
+
font-size: 1rem;
|
|
313
|
+
margin-bottom: 0;
|
|
314
|
+
padding: 0;
|
|
315
|
+
width: calc(100% / 7);
|
|
316
|
+
flex-grow: 1;
|
|
317
|
+
display: flex;
|
|
318
|
+
flex-direction:column;
|
|
319
|
+
align-items: flex-start;
|
|
320
|
+
background-color: var(--website-page__alternate-background-t70);
|
|
321
|
+
min-height: 2rem;
|
|
322
|
+
outline: 1px solid var(--color__tertiary);
|
|
323
|
+
border: none;
|
|
324
|
+
box-shadow: none;
|
|
325
|
+
&.today {
|
|
326
|
+
&::v-deep .day::after {
|
|
327
|
+
content: none;
|
|
328
|
+
display: none;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
.asd20-calendar__day-events {
|
|
333
|
+
display: none !important;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.asd20-calendar__date {
|
|
337
|
+
position: static;
|
|
338
|
+
font-size: 0.875rem;
|
|
339
|
+
margin: 0 0 0.125rem 0.125rem;
|
|
340
|
+
|
|
341
|
+
.day {
|
|
342
|
+
display: inline;
|
|
343
|
+
width: auto;
|
|
344
|
+
height: auto;
|
|
345
|
+
padding: 0;
|
|
346
|
+
font-size: 0.875rem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.weekday,
|
|
350
|
+
.month,
|
|
351
|
+
.year {
|
|
352
|
+
display: none;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.asd20-calendar__events {
|
|
357
|
+
position: static;
|
|
358
|
+
display: flex;
|
|
359
|
+
flex-direction: row;
|
|
360
|
+
width: 100%;
|
|
361
|
+
justify-content: center;
|
|
362
|
+
|
|
363
|
+
&::v-deep .asd20-calendar-event-button {
|
|
364
|
+
background: transparent;
|
|
365
|
+
padding: 0;
|
|
366
|
+
width: space(0.375);
|
|
367
|
+
height: space(0.375);
|
|
368
|
+
// margin-right: space(0.0625);
|
|
369
|
+
.title::before {
|
|
370
|
+
width: 10px;
|
|
371
|
+
height: 10px;
|
|
372
|
+
margin: 0;
|
|
373
|
+
|
|
374
|
+
}
|
|
375
|
+
.title span {
|
|
376
|
+
display: none;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
&::v-deep .small-dot {
|
|
380
|
+
width: space(0.25);
|
|
381
|
+
height: space(0.25);
|
|
382
|
+
.title::before {
|
|
383
|
+
width: 6px;
|
|
384
|
+
height: 6px;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
.outside {
|
|
389
|
+
.asd20-calendar__date .day {
|
|
390
|
+
display: none;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
249
395
|
}
|
|
250
396
|
|
|
251
397
|
@media (max-width: 767px) {
|
|
@@ -282,10 +428,10 @@ $accent: var(--color__accent);
|
|
|
282
428
|
// min-height: calc(100% / 5);
|
|
283
429
|
|
|
284
430
|
&:nth-child(n + 1):nth-child(-n + 7) {
|
|
285
|
-
margin-top: space(1);
|
|
431
|
+
// margin-top: space(1);
|
|
286
432
|
|
|
287
|
-
box-shadow: 0 -1px 0 0 var(--color__tertiary),
|
|
288
|
-
|
|
433
|
+
// box-shadow: 0 -1px 0 0 var(--color__tertiary),
|
|
434
|
+
// 0 1px 0 0 var(--color__tertiary), -1px 0 0 0 var(--color__tertiary);
|
|
289
435
|
|
|
290
436
|
&::v-deep .asd20-calendar__date {
|
|
291
437
|
width: 100%;
|
|
@@ -309,10 +455,10 @@ $accent: var(--color__accent);
|
|
|
309
455
|
}
|
|
310
456
|
}
|
|
311
457
|
|
|
312
|
-
&:nth-child(n + 1):nth-child(1) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
458
|
+
// &:nth-child(n + 1):nth-child(1) {
|
|
459
|
+
// box-shadow: 0 -1px 0 0 var(--color__tertiary),
|
|
460
|
+
// 0 1px 0 0 var(--color__tertiary);
|
|
461
|
+
// }
|
|
316
462
|
|
|
317
463
|
&.selected {
|
|
318
464
|
// outline: none;
|
|
@@ -376,4 +522,5 @@ $accent: var(--color__accent);
|
|
|
376
522
|
}
|
|
377
523
|
}
|
|
378
524
|
}
|
|
525
|
+
|
|
379
526
|
</style>
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"summary": "Winter Break - No School",
|
|
30
30
|
"description": "No School",
|
|
31
31
|
"location": "AAHS",
|
|
32
|
-
"start": "
|
|
33
|
-
"end": "
|
|
32
|
+
"start": "2025-12-19T07:00:00Z",
|
|
33
|
+
"end": "2026-01-05T07:00:00Z",
|
|
34
34
|
"dtstamp": "2024-12-19T07:00:00Z",
|
|
35
35
|
"class": "",
|
|
36
36
|
"priority": "",
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"id": null,
|
|
54
54
|
"uid": "MDQwMDAwMDA4MjAwRTAwMDc0QzVCNzEwMUE4MkUwMDgwMDAwMDAwMERFRTE5NzcxNThBQ0RBMDEwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDBFOEVBMjc5NkM0MDI3RjRGODFFNzY1QzQ0M0VGMDA0Mw==",
|
|
55
55
|
"type": "event",
|
|
56
|
-
"summary": "
|
|
57
|
-
"description": "",
|
|
56
|
+
"summary": "Choir Performance",
|
|
57
|
+
"description": "There will be a Choir Performance on December 25... If you can believe that.",
|
|
58
58
|
"location": "AAHS",
|
|
59
|
-
"start": "
|
|
60
|
-
"end": "
|
|
59
|
+
"start": "2025-12-25T17:00:00Z",
|
|
60
|
+
"end": "2025-12-25T17:30:00Z",
|
|
61
61
|
"dtstamp": "2024-12-25T17:00:00Z",
|
|
62
62
|
"class": "",
|
|
63
63
|
"priority": "",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"summary": "All State Choir",
|
|
84
84
|
"description": "",
|
|
85
85
|
"location": "Denver Center for the Performing Arts (1101 13th St, Denver, CO 80204, United States)",
|
|
86
|
-
"start": "
|
|
87
|
-
"end": "
|
|
86
|
+
"start": "2026-01-09T17:00:00Z",
|
|
87
|
+
"end": "2026-01-11T17:30:00Z",
|
|
88
88
|
"dtstamp": "2025-01-09T17:00:00Z",
|
|
89
89
|
"class": "",
|
|
90
90
|
"priority": "",
|
|
@@ -110,8 +110,8 @@
|
|
|
110
110
|
"summary": "First day of second semester",
|
|
111
111
|
"description": "<ul><li>In-person attendance is limited to boardroom capacity of 70 audience members per the Colorado Springs Fire Marshall.</li><li>Note, those who attend in person may be captured on camera during the live feed and recorded versions of the meeting.</li><li>The meeting link will be available the week of the meeting.</li></ul><br/>We invite public comment at our meetings.<br/><br/><ul><li>You must sign-up to speak between 7 a.m. and 1 p.m., the day of the board meeting by completing our Public <a href=\"https://forms.office.com/r/4nRw9yhyeS\" target=\"_blank\">Comment Form</a>.</li><li>You must be present and available to speak in-person at the podium when called upon.</li><li>You cannot provide virtual public comment.</li><li>If you cannot attend in-person, we invite you to submit your comments in writing via the Public <a href=\"https://forms.office.com/r/4nRw9yhyeS\" target=\"_blank\">Comment Form</a>. Written comments submitted through the Public Comment Form will not be read publicly at the board meeting, but they will be included as part of the public record in the minutes for the meeting.</li></ul>",
|
|
112
112
|
"location": "AAHS",
|
|
113
|
-
"start": "
|
|
114
|
-
"end": "
|
|
113
|
+
"start": "2026-01-05T07:00:00Z",
|
|
114
|
+
"end": "2026-01-06T07:00:00Z",
|
|
115
115
|
"dtstamp": "2025-01-05T07:00:00Z",
|
|
116
116
|
"class": "",
|
|
117
117
|
"priority": "",
|
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
"summary": "Boys Varsity vs. CoronadoDohertyLibertyMullenPalmer RidgePine CreekPueblo WestRampartRock CanyonThunderRidge'>Multiple Schools (Home)",
|
|
138
138
|
"description": "Type: Kadet Kickoff Tournament Opponent: CoronadoDohertyLibertyMullenPalmer RidgePine CreekPueblo WestRampartRock CanyonThunderRidge'>Multiple Schools Comments: Confirmed",
|
|
139
139
|
"location": "AAHS Media Center",
|
|
140
|
-
"start": "
|
|
141
|
-
"end": "
|
|
140
|
+
"start": "2026-01-19T08:20:00Z",
|
|
141
|
+
"end": "2026-01-19T08:20:00Z",
|
|
142
142
|
"dtstamp": "2025-01-19T20:20:00Z",
|
|
143
143
|
"class": "",
|
|
144
144
|
"priority": "",
|
|
@@ -164,8 +164,8 @@
|
|
|
164
164
|
"summary": "Choice Night",
|
|
165
165
|
"description": "",
|
|
166
166
|
"location": "AAHS Media Center",
|
|
167
|
-
"start": "
|
|
168
|
-
"end": "
|
|
167
|
+
"start": "2026-01-20T01:00:00Z",
|
|
168
|
+
"end": "2026-01-20T03:00:00Z",
|
|
169
169
|
"dtstamp": "2025-01-20T01:00:00Z",
|
|
170
170
|
"class": "",
|
|
171
171
|
"priority": "",
|
|
@@ -191,8 +191,8 @@
|
|
|
191
191
|
"summary": "8th Grade Information Night",
|
|
192
192
|
"description": "",
|
|
193
193
|
"location": "AAHS Gyms",
|
|
194
|
-
"start": "
|
|
195
|
-
"end": "
|
|
194
|
+
"start": "2026-02-10T01:00:00Z",
|
|
195
|
+
"end": "2026-02-10T03:00:00Z",
|
|
196
196
|
"dtstamp": "2025-02-10T01:00:00Z",
|
|
197
197
|
"class": "",
|
|
198
198
|
"priority": "",
|
|
@@ -218,8 +218,8 @@
|
|
|
218
218
|
"summary": "Parent-teacher Conferences",
|
|
219
219
|
"description": "",
|
|
220
220
|
"location": "",
|
|
221
|
-
"start": "
|
|
222
|
-
"end": "
|
|
221
|
+
"start": "2026-03-22T21:30:00Z",
|
|
222
|
+
"end": "2026-03-23T01:30:00Z",
|
|
223
223
|
"dtstamp": "2025-03-22T21:30:00Z",
|
|
224
224
|
"class": "",
|
|
225
225
|
"priority": "",
|
|
@@ -245,8 +245,8 @@
|
|
|
245
245
|
"summary": "Parent-Teacher Conferences",
|
|
246
246
|
"description": "",
|
|
247
247
|
"location": "",
|
|
248
|
-
"start": "
|
|
249
|
-
"end": "
|
|
248
|
+
"start": "2026-03-23T22:00:00Z",
|
|
249
|
+
"end": "2026-03-24T01:00:00Z",
|
|
250
250
|
"dtstamp": "2025-03-23T22:00:00Z",
|
|
251
251
|
"class": "",
|
|
252
252
|
"priority": "",
|
|
@@ -300,7 +300,7 @@
|
|
|
300
300
|
"description": "",
|
|
301
301
|
"location": "",
|
|
302
302
|
"start": "2025-03-27T06:00:00Z",
|
|
303
|
-
"end": "2025-03-
|
|
303
|
+
"end": "2025-03-31T06:00:00Z",
|
|
304
304
|
"dtstamp": "2025-03-27T06:00:00Z",
|
|
305
305
|
"class": "",
|
|
306
306
|
"priority": "",
|
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
"id": null,
|
|
324
324
|
"uid": "MDQwMDAwMDA4MjAwRTAwMDc0QzVCNzEwMUE4MkUwMDgwMDAwMDAwMDIyQzE3RTIwRjYwQkQ5MDEwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAzRjdENDg0MzUyQjdEQTQ1ODc1QjQwRDE2MEQ2NkEwOA==",
|
|
325
325
|
"type": "event",
|
|
326
|
-
"summary": "No school - Spring Break",
|
|
326
|
+
"summary": "No school - Spring Break Intermission",
|
|
327
327
|
"description": "",
|
|
328
328
|
"location": "",
|
|
329
329
|
"start": "2025-03-28T06:00:00Z",
|
|
@@ -3,93 +3,96 @@ import format from 'date-fns/format'
|
|
|
3
3
|
import addDays from 'date-fns/add_days'
|
|
4
4
|
import isSameDay from 'date-fns/is_same_day'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* Expands an array of events, creating new events for those
|
|
8
|
-
* that span multiple days, and adding metadata.
|
|
9
|
-
*
|
|
10
|
-
* @export
|
|
11
|
-
* @param {*} events
|
|
12
|
-
* @returns An array of expanded events
|
|
13
|
-
*/
|
|
14
|
-
export default function expandEvents(events) {
|
|
6
|
+
export default function expandEvents(events, includePastEvents = false) {
|
|
15
7
|
return events
|
|
16
|
-
.reduce((
|
|
17
|
-
// Parse the event start and end times
|
|
8
|
+
.reduce((acc, event) => {
|
|
18
9
|
const start = parse(event.start)
|
|
19
10
|
const end = event.end ? parse(event.end) : start
|
|
20
11
|
|
|
21
|
-
// Normalize today's date to ignore time
|
|
22
12
|
const today = new Date()
|
|
23
13
|
today.setHours(0, 0, 0, 0)
|
|
24
|
-
// console.log('Today:', today)
|
|
25
14
|
|
|
26
|
-
const diffTime =
|
|
27
|
-
const diffHours =
|
|
28
|
-
|
|
29
|
-
const multiDay = !isSameDay(start, end)
|
|
30
|
-
const allDay = !event.end || (diffHours > 23 && diffHours < 25)
|
|
31
|
-
const endTimeUndetermined = event.end && event.end === event.start
|
|
15
|
+
const diffTime = end.getTime() - start.getTime()
|
|
16
|
+
const diffHours = diffTime / (1000 * 60 * 60)
|
|
17
|
+
let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
|
|
19
|
+
// Safety net for zero-day events
|
|
20
|
+
if (diffDays === 0) diffDays = 1
|
|
21
|
+
|
|
22
|
+
const isAllDay = event.allDay || diffHours >= 23
|
|
23
|
+
|
|
24
|
+
// Normalize for date-only comparisons
|
|
25
|
+
const normalizedStart = new Date(start)
|
|
26
|
+
normalizedStart.setHours(0, 0, 0, 0)
|
|
27
|
+
|
|
28
|
+
const normalizedEnd = new Date(end)
|
|
29
|
+
normalizedEnd.setHours(0, 0, 0, 0)
|
|
30
|
+
|
|
31
|
+
// let isMultiDay
|
|
32
|
+
|
|
33
|
+
// if (isAllDay && diffDays === 1) {
|
|
34
|
+
// // All-day event lasting exactly one day → NOT multi-day
|
|
35
|
+
// isMultiDay = false
|
|
36
|
+
// } else {
|
|
37
|
+
// isMultiDay = !isSameDay(normalizedStart, normalizedEnd)
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
const isMultiDay =
|
|
41
|
+
diffDays > 1 || !isSameDay(normalizedStart, normalizedEnd)
|
|
42
|
+
|
|
43
|
+
const expandedEvents = []
|
|
44
|
+
|
|
45
|
+
if (isMultiDay) {
|
|
46
|
+
// Multi-day event expansion
|
|
35
47
|
for (let d = 0; d < diffDays; d++) {
|
|
36
|
-
const
|
|
37
|
-
// Normalize newStart to ignore time
|
|
38
|
-
newStart.setHours(0, 0, 0, 0)
|
|
39
|
-
// console.log('newStart:', newStart)
|
|
48
|
+
const currentDay = addDays(normalizedStart, d)
|
|
40
49
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
50
|
+
if (includePastEvents || currentDay >= today) {
|
|
51
|
+
expandedEvents.push({
|
|
43
52
|
...event,
|
|
44
|
-
start:
|
|
45
|
-
end:
|
|
53
|
+
start: currentDay,
|
|
54
|
+
end: normalizedEnd,
|
|
46
55
|
originalStart: start,
|
|
47
|
-
allDay:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
year: format(newStart, 'YYYY'),
|
|
56
|
+
allDay: true,
|
|
57
|
+
multiDay: true,
|
|
58
|
+
weekday: format(currentDay, 'dddd'),
|
|
59
|
+
day: format(currentDay, 'DD'),
|
|
60
|
+
number: format(currentDay, 'D'),
|
|
61
|
+
month: format(currentDay, 'MMMM'),
|
|
62
|
+
year: format(currentDay, 'YYYY')
|
|
55
63
|
})
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
} else {
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// console.log('normailizedStart:', normalizedStart)
|
|
63
|
-
|
|
64
|
-
if (normalizedStart >= today) {
|
|
65
|
-
a.push({
|
|
67
|
+
// Single-day event
|
|
68
|
+
if (includePastEvents || normalizedStart >= today) {
|
|
69
|
+
expandedEvents.push({
|
|
66
70
|
...event,
|
|
67
|
-
start: start,
|
|
71
|
+
start: start, // keep original start WITH time
|
|
68
72
|
end: end,
|
|
69
73
|
originalStart: null,
|
|
70
|
-
allDay,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
year: format(start, 'YYYY'),
|
|
74
|
+
allDay: isAllDay,
|
|
75
|
+
multiDay: false,
|
|
76
|
+
weekday: format(normalizedStart, 'dddd'),
|
|
77
|
+
day: format(normalizedStart, 'DD'),
|
|
78
|
+
number: format(normalizedStart, 'D'),
|
|
79
|
+
month: format(normalizedStart, 'MMMM'),
|
|
80
|
+
year: format(normalizedStart, 'YYYY')
|
|
78
81
|
})
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
|
-
|
|
84
|
+
|
|
85
|
+
return acc.concat(expandedEvents)
|
|
82
86
|
}, [])
|
|
83
|
-
.reduce((
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
.reduce((acc, e) => {
|
|
88
|
+
const hasDuplicate = acc.some(
|
|
89
|
+
ae =>
|
|
90
|
+
ae.summary === e.summary &&
|
|
91
|
+
ae.start.getTime() === e.start.getTime() &&
|
|
92
|
+
ae.end.getTime() === e.end.getTime()
|
|
87
93
|
)
|
|
88
|
-
if (!hasDuplicate)
|
|
89
|
-
return
|
|
94
|
+
if (!hasDuplicate) acc.push(e)
|
|
95
|
+
return acc
|
|
90
96
|
}, [])
|
|
91
|
-
.sort((a, b) =>
|
|
92
|
-
// Sort events by start date
|
|
93
|
-
return a.start.getTime() - b.start.getTime()
|
|
94
|
-
})
|
|
97
|
+
.sort((a, b) => a.start.getTime() - b.start.getTime())
|
|
95
98
|
}
|
|
@@ -5,14 +5,14 @@ import isSameDay from 'date-fns/is_same_day'
|
|
|
5
5
|
import getDaysInMonth from 'date-fns/get_days_in_month'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Given an array of events
|
|
8
|
+
* Given an array of events, returns an array of days with events grouped by date.
|
|
9
9
|
*
|
|
10
10
|
* @export
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {number}
|
|
13
|
-
* @param {number}
|
|
14
|
-
* @param {boolean}
|
|
15
|
-
* @returns
|
|
11
|
+
* @param {Array} events - Array of events (should already be expanded if needed)
|
|
12
|
+
* @param {number} year - Target year (used when showAll is false)
|
|
13
|
+
* @param {number} month - Target month (zero-indexed, used when showAll is false)
|
|
14
|
+
* @param {boolean} showAll - If true, returns only days with events (useful for agenda views)
|
|
15
|
+
* @returns {Array} Array of day objects { date, events, ... }
|
|
16
16
|
*/
|
|
17
17
|
export default function mapEventsToDays(
|
|
18
18
|
events,
|
|
@@ -20,67 +20,69 @@ export default function mapEventsToDays(
|
|
|
20
20
|
month = 0,
|
|
21
21
|
showAll = false
|
|
22
22
|
) {
|
|
23
|
-
|
|
23
|
+
const today = new Date()
|
|
24
|
+
today.setHours(0, 0, 0, 0)
|
|
25
|
+
|
|
24
26
|
let days = []
|
|
25
27
|
|
|
26
|
-
// If we are supposed to show all days, even outside of current month (whole start-end range)
|
|
27
28
|
if (showAll) {
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
// Get unique dates
|
|
31
|
-
.reduce((results, e) => {
|
|
32
|
-
let d = parse(e.start)
|
|
33
|
-
let f = format(d, 'YYYY-MM-DD')
|
|
34
|
-
if (results.indexOf(f) === -1) {
|
|
35
|
-
results.push(f)
|
|
36
|
-
}
|
|
37
|
-
return results
|
|
38
|
-
}, [])
|
|
39
|
-
// parse unique dates
|
|
40
|
-
dates.map(f => parse(f))
|
|
29
|
+
// Collect unique event dates
|
|
30
|
+
const uniqueDatesMap = new Map()
|
|
41
31
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
unix: d.valueOf(),
|
|
48
|
-
today: isSameDay(d, parse(new Date())),
|
|
49
|
-
events: events.filter(event => isSameDay(d, parse(event.start))),
|
|
50
|
-
weekday: format(d, 'dddd'),
|
|
51
|
-
day: format(d, 'DD'),
|
|
52
|
-
shortDay: format(d, 'D'),
|
|
53
|
-
number: format(d, 'D'),
|
|
54
|
-
month: format(d, 'MMMM'),
|
|
55
|
-
year: format(d, 'YYYY'),
|
|
32
|
+
events.forEach(event => {
|
|
33
|
+
const eventDate = parse(event.start)
|
|
34
|
+
const dateKey = format(eventDate, 'YYYY-MM-DD')
|
|
35
|
+
if (!uniqueDatesMap.has(dateKey)) {
|
|
36
|
+
uniqueDatesMap.set(dateKey, eventDate)
|
|
56
37
|
}
|
|
57
38
|
})
|
|
39
|
+
|
|
40
|
+
days = Array.from(uniqueDatesMap.values()).map(date => createDayObject(date, events, false, today))
|
|
58
41
|
} else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
// Month view: create a full calendar grid
|
|
43
|
+
const firstDayOfMonth = new Date(year, month, 1)
|
|
44
|
+
const lastDayOfMonth = new Date(year, month + 1, 0)
|
|
45
|
+
const daysInMonth = getDaysInMonth(firstDayOfMonth)
|
|
46
|
+
|
|
47
|
+
const prevMonthDays = firstDayOfMonth.getDay() // How many days to show before the 1st of the month (0=Sunday)
|
|
48
|
+
const nextMonthDays = 6 - lastDayOfMonth.getDay() // How many days to show after the last day of the month
|
|
49
|
+
const totalDays = daysInMonth + prevMonthDays + nextMonthDays
|
|
66
50
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
days.push(
|
|
71
|
-
outsideOfCurrentMonth: i < 0 || i > daysInMonth - 1 ? true : false,
|
|
72
|
-
date: d,
|
|
73
|
-
unix: d.valueOf(),
|
|
74
|
-
today: isSameDay(d, parse(new Date())),
|
|
75
|
-
events: events.filter(event => isSameDay(d, parse(event.start))),
|
|
76
|
-
weekday: format(d, 'dddd'),
|
|
77
|
-
day: format(d, 'DD'),
|
|
78
|
-
number: format(d, 'D'),
|
|
79
|
-
month: format(d, 'MMMM'),
|
|
80
|
-
year: format(d, 'YYYY'),
|
|
81
|
-
})
|
|
51
|
+
for (let i = -prevMonthDays; i < totalDays - prevMonthDays; i++) {
|
|
52
|
+
const date = addDays(firstDayOfMonth, i)
|
|
53
|
+
const outsideOfCurrentMonth = i < 0 || i >= daysInMonth
|
|
54
|
+
days.push(createDayObject(date, events, outsideOfCurrentMonth, today))
|
|
82
55
|
}
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
|
|
58
|
+
// Sort by unix timestamp (probably unnecessary unless something weird happens)
|
|
59
|
+
return days.sort((a, b) => a.unix - b.unix)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Builds a day object with events attached.
|
|
64
|
+
*
|
|
65
|
+
* @param {Date} date - The date this day represents.
|
|
66
|
+
* @param {Array} events - The full events list.
|
|
67
|
+
* @param {boolean} outsideOfCurrentMonth - Whether this date is outside the target month.
|
|
68
|
+
* @param {Date} today - Today's date (normalized to 00:00).
|
|
69
|
+
* @returns {Object} The day object.
|
|
70
|
+
*/
|
|
71
|
+
function createDayObject(date, events, outsideOfCurrentMonth = false, today) {
|
|
72
|
+
const normalizedDate = new Date(date)
|
|
73
|
+
normalizedDate.setHours(0, 0, 0, 0)
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
outsideOfCurrentMonth,
|
|
77
|
+
date: normalizedDate,
|
|
78
|
+
unix: normalizedDate.getTime(),
|
|
79
|
+
today: isSameDay(normalizedDate, today),
|
|
80
|
+
events: events.filter(event => isSameDay(parse(event.start), normalizedDate)),
|
|
81
|
+
weekday: format(normalizedDate, 'dddd'),
|
|
82
|
+
day: format(normalizedDate, 'DD'),
|
|
83
|
+
shortDay: format(normalizedDate, 'D'),
|
|
84
|
+
number: format(normalizedDate, 'D'),
|
|
85
|
+
month: format(normalizedDate, 'MMMM'),
|
|
86
|
+
year: format(normalizedDate, 'YYYY'),
|
|
87
|
+
}
|
|
86
88
|
}
|