@asd20/ui 3.2.987 → 3.2.988

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-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui",
3
- "version": "3.2.964",
3
+ "version": "3.2.987",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
@@ -1285,7 +1285,6 @@
1285
1285
  "version": "7.27.0",
1286
1286
  "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
1287
1287
  "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
1288
- "dev": true,
1289
1288
  "requires": {
1290
1289
  "regenerator-runtime": "^0.14.0"
1291
1290
  }
@@ -1423,6 +1422,12 @@
1423
1422
  "restore-cursor": "^1.0.1"
1424
1423
  }
1425
1424
  },
1425
+ "date-fns": {
1426
+ "version": "1.30.1",
1427
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
1428
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
1429
+ "dev": true
1430
+ },
1426
1431
  "figures": {
1427
1432
  "version": "1.7.0",
1428
1433
  "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
@@ -11548,9 +11553,12 @@
11548
11553
  }
11549
11554
  },
11550
11555
  "date-fns": {
11551
- "version": "1.30.1",
11552
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
11553
- "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
11556
+ "version": "2.30.0",
11557
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
11558
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
11559
+ "requires": {
11560
+ "@babel/runtime": "^7.21.0"
11561
+ }
11554
11562
  },
11555
11563
  "dateformat": {
11556
11564
  "version": "3.0.3",
@@ -21394,6 +21402,12 @@
21394
21402
  "figures": "^2.0.0"
21395
21403
  },
21396
21404
  "dependencies": {
21405
+ "date-fns": {
21406
+ "version": "1.30.1",
21407
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
21408
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
21409
+ "dev": true
21410
+ },
21397
21411
  "figures": {
21398
21412
  "version": "2.0.0",
21399
21413
  "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -21833,6 +21847,12 @@
21833
21847
  "restore-cursor": "^1.0.1"
21834
21848
  }
21835
21849
  },
21850
+ "date-fns": {
21851
+ "version": "1.30.1",
21852
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
21853
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
21854
+ "dev": true
21855
+ },
21836
21856
  "figures": {
21837
21857
  "version": "1.7.0",
21838
21858
  "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
@@ -27520,8 +27540,7 @@
27520
27540
  "regenerator-runtime": {
27521
27541
  "version": "0.14.1",
27522
27542
  "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
27523
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
27524
- "dev": true
27543
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
27525
27544
  },
27526
27545
  "regenerator-transform": {
27527
27546
  "version": "0.15.2",
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "*.scss",
6
6
  "*.vue"
7
7
  ],
8
- "version": "3.2.987",
8
+ "version": "3.2.988",
9
9
  "private": false,
10
10
  "license": "MIT",
11
11
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "axios": "^0.19.2",
31
31
  "basicscroll": "^3.0.2",
32
32
  "countup.js": "^2.0.0",
33
- "date-fns": "^1.30.1",
33
+ "date-fns": "^2.30.0",
34
34
  "focus-trap": "^6.9.4",
35
35
  "focus-trap-vue": "^1.1.1",
36
36
  "js-cookie": "^2.2.1",
@@ -14,7 +14,7 @@ export default {
14
14
  formattedDate() {
15
15
  // let apStyle = ['Jan.', 'Feb.', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.']
16
16
  // let shortMonths = /January|February|August|September|October|November|December /
17
- let formatted = format(new Date(this.dateTime), ' MMMM D, YYYY')
17
+ let formatted = format(new Date(this.dateTime), ' MMMM d, yyyy')
18
18
  // for (let month of apStyle) {
19
19
  // if (formatted.includes(month)) {
20
20
  // formatted = month
@@ -343,8 +343,9 @@ export default {
343
343
  font-family: var(--website-typography__font-family-headlines);
344
344
  display: flex;
345
345
  align-items: flex-start;
346
- margin-top: space(0.5);
347
- margin-bottom: space(0.5);
346
+ margin-top: space(0.25);
347
+ margin-bottom: space(0.25);
348
+ align-items: center;
348
349
  .asd20-icon {
349
350
  --line-color: var(--website-icon__line-color);
350
351
  margin-left: -0.125em;
@@ -356,6 +357,7 @@ export default {
356
357
  }
357
358
  &__time + &__location {
358
359
  margin-top: space(0);
360
+ margin-bottom: space(0.25);
359
361
  }
360
362
  // &__date {
361
363
  // margin-right: space(0.5);
@@ -17,7 +17,6 @@
17
17
  </template>
18
18
 
19
19
  <script>
20
- import parse from 'date-fns/parse'
21
20
  import format from 'date-fns/format'
22
21
 
23
22
  export default {
@@ -28,27 +27,27 @@ export default {
28
27
  computed: {
29
28
  startDay() {
30
29
  if (!this.event) return ''
31
- return format(parse(this.event.start.dateTime), 'DD')
30
+ return format(new Date(this.event.start.dateTime), 'dd')
32
31
  },
33
32
  startMonthName() {
34
33
  if (!this.event) return ''
35
- return format(parse(this.event.start.dateTime), 'MMM')
34
+ return format(new Date(this.event.start.dateTime), 'MMM')
36
35
  },
37
36
  startDate() {
38
37
  if (!this.event) return ''
39
- return format(parse(this.event.start.dateTime), 'dddd MMM D, YYYY')
38
+ return format(new Date(this.event.start.dateTime), 'EEEE MMM d, yyyy')
40
39
  },
41
40
  endDate() {
42
41
  if (!this.event) return ''
43
- return format(parse(this.event.end.dateTime), 'dddd MMM D, YYYY')
42
+ return format(new Date(this.event.end.dateTime), 'EEEE MMM d, yyyy')
44
43
  },
45
44
  startTime() {
46
45
  if (!this.event) return ''
47
- return format(parse(this.event.start.dateTime), 'h:mm A')
46
+ return format(new Date(this.event.start.dateTime), 'h:mm aa')
48
47
  },
49
48
  endTime() {
50
49
  if (!this.event) return ''
51
- return format(parse(this.event.end.dateTime), 'h:mm A')
50
+ return format(new Date(this.event.end.dateTime), 'h:mm aa')
52
51
  },
53
52
  },
54
53
  }
@@ -29,8 +29,8 @@
29
29
  "summary": "Winter Break - No School",
30
30
  "description": "No School",
31
31
  "location": "AAHS",
32
- "start": "2025-12-19T07:00:00Z",
33
- "end": "2026-01-05T07:00:00Z",
32
+ "start": "2025-12-20T07:00:00Z",
33
+ "end": "2026-12-25T07:00:00Z",
34
34
  "dtstamp": "2024-12-19T07:00:00Z",
35
35
  "class": "",
36
36
  "priority": "",
@@ -56,8 +56,8 @@
56
56
  "summary": "Register for Summer School",
57
57
  "description": "Ok. We're actually registeriing for Summer School really, really, really early.",
58
58
  "location": "EAC",
59
- "start": "2025-12-24T07:01:00Z",
60
- "end": "2025-12-24T07:01:00Z",
59
+ "start": "2025-12-24T16:01:00Z",
60
+ "end": "2025-12-24T16:01:00Z",
61
61
  "dtstamp": "2024-12-23T17:00:00Z",
62
62
  "class": "",
63
63
  "priority": "",
@@ -1,13 +1,13 @@
1
- import parse from 'date-fns/parse'
2
1
  import format from 'date-fns/format'
3
- import addDays from 'date-fns/add_days'
4
- import isSameDay from 'date-fns/is_same_day'
2
+ import addDays from 'date-fns/addDays'
3
+ import isSameDay from 'date-fns/isSameDay'
5
4
 
6
5
  export default function expandEvents(events, includePastEvents = false) {
7
6
  return events
8
7
  .reduce((acc, event) => {
9
- const start = parse(event.start)
10
- const end = event.end ? parse(event.end) : start
8
+ // Use new Date() for ISO date strings
9
+ const start = new Date(event.start)
10
+ const end = event.end ? new Date(event.end) : start
11
11
 
12
12
  const today = new Date()
13
13
  today.setHours(0, 0, 0, 0)
@@ -16,7 +16,6 @@ export default function expandEvents(events, includePastEvents = false) {
16
16
  const diffHours = diffTime / (1000 * 60 * 60)
17
17
  let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
18
18
 
19
- // Safety net for zero-day events
20
19
  if (diffDays === 0) diffDays = 1
21
20
 
22
21
  const isAllDay = event.allDay || diffHours >= 23
@@ -28,22 +27,12 @@ export default function expandEvents(events, includePastEvents = false) {
28
27
  const normalizedEnd = new Date(end)
29
28
  normalizedEnd.setHours(0, 0, 0, 0)
30
29
 
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
30
  const isMultiDay =
41
31
  diffDays > 1 || !isSameDay(normalizedStart, normalizedEnd)
42
32
 
43
33
  const expandedEvents = []
44
34
 
45
35
  if (isMultiDay) {
46
- // Multi-day event expansion
47
36
  for (let d = 0; d < diffDays; d++) {
48
37
  const currentDay = addDays(normalizedStart, d)
49
38
 
@@ -55,16 +44,15 @@ export default function expandEvents(events, includePastEvents = false) {
55
44
  originalStart: start,
56
45
  allDay: true,
57
46
  multiDay: true,
58
- weekday: format(currentDay, 'dddd'),
59
- day: format(currentDay, 'DD'),
60
- number: format(currentDay, 'D'),
47
+ weekday: format(currentDay, 'EEEE'),
48
+ day: format(currentDay, 'dd'),
49
+ number: format(currentDay, 'd'),
61
50
  month: format(currentDay, 'MMMM'),
62
- year: format(currentDay, 'YYYY')
51
+ year: format(currentDay, 'yyyy'),
63
52
  })
64
53
  }
65
54
  }
66
55
  } else {
67
- // Single-day event
68
56
  if (includePastEvents || normalizedStart >= today) {
69
57
  expandedEvents.push({
70
58
  ...event,
@@ -73,11 +61,11 @@ export default function expandEvents(events, includePastEvents = false) {
73
61
  originalStart: null,
74
62
  allDay: isAllDay,
75
63
  multiDay: false,
76
- weekday: format(normalizedStart, 'dddd'),
77
- day: format(normalizedStart, 'DD'),
78
- number: format(normalizedStart, 'D'),
64
+ weekday: format(normalizedStart, 'EEEE'),
65
+ day: format(normalizedStart, 'dd'),
66
+ number: format(normalizedStart, 'd'),
79
67
  month: format(normalizedStart, 'MMMM'),
80
- year: format(normalizedStart, 'YYYY')
68
+ year: format(normalizedStart, 'yyyy'),
81
69
  })
82
70
  }
83
71
  }
@@ -110,7 +98,6 @@ export default function expandEvents(events, includePastEvents = false) {
110
98
  if (isAHighPriority && !isBHighPriority) return -1
111
99
  if (!isAHighPriority && isBHighPriority) return 1
112
100
 
113
- // Fall back to alphabetical order if both have same priority
114
101
  return aSummary.localeCompare(bSummary)
115
102
  })
116
103
  }
@@ -1,47 +1,39 @@
1
1
  import format from 'date-fns/format'
2
- import parse from 'date-fns/parse'
2
+
3
+ function safeFormatDate(dateObj, fmt) {
4
+ if (!dateObj || isNaN(dateObj)) return ''
5
+ try {
6
+ return format(dateObj, fmt)
7
+ } catch {
8
+ return ''
9
+ }
10
+ }
3
11
 
4
12
  export default function formattedTime(event) {
5
- if (!event) return
13
+ if (!event) return ''
6
14
 
7
15
  if (event.allDay) return 'All Day'
8
16
 
9
17
  if (event.multiDay) {
10
- return `<b>MultiDay Event:</b><br/>Starts: ${format(
11
- parse(event.originalStart, "yyyy-MM-dd'T'HH:mm:ssX", new Date()),
12
- '<b>h:mm aa</b>, ddd, MMM DD, YYYY'
13
- )
14
- .replace(/#/g, '&nbsp;')
15
- .replace(/:00/g, '')}<br>Ends: ${format(
16
- parse(event.end, "yyyy-MM-dd'T'HH:mm:ssX", new Date()),
17
- '<b>h:mm aa</b>, ddd, MM DD, YYYY'
18
- )
19
- .replace(/#/g, '&nbsp;')
20
- .replace(/:00/g, '')}`
18
+ const startStr = safeFormatDate(event.originalStart, 'h:mm aa, EEE, MMM dd, yyyy')
19
+ const endStr = safeFormatDate(event.end, 'h:mm aa, EEE, MM dd, yyyy')
20
+ if (startStr && endStr) {
21
+ return `<b>MultiDay Event:</b><br/>Starts: ${startStr}<br>Ends: ${endStr}`
22
+ } else if (startStr) {
23
+ return `<b>MultiDay Event:</b><br/>Starts: ${startStr}`
24
+ } else if (endStr) {
25
+ return `<b>MultiDay Event:</b><br/>Ends: ${endStr}`
26
+ }
27
+ return 'MultiDay Event'
21
28
  }
22
29
 
23
- const startTime = parse(event.start, "yyyy-MM-dd'T'HH:mm:ssX", new Date())
24
- const endTime = parse(event.end, "yyyy-MM-dd'T'HH:mm:ssX", new Date())
25
- // const startHour = startTime.getHours()
26
-
27
- // if (
28
- // startTime.getTime() === endTime.getTime() &&
29
- // (startHour >= 23 || startHour < 3)
30
- // ) {
31
- // return 'TBD'
32
- // }
33
-
34
- let time = `${format(startTime, '<b>h:mm aa</b>').replace(/:00/g, '')}`
30
+ const start = safeFormatDate(event.start, 'h:mm aa')
31
+ const end = safeFormatDate(event.end, 'h:mm aa')
35
32
 
36
- if (startTime.getTime() === endTime.getTime()) {
37
- time += ''
38
- } else if (event.endTimeUndetermined) {
39
- time += `${format(endTime).replace(/:00/g, '')}`
40
- } else {
41
- time += ` - ${format(endTime, '<b>h:mm aa</b>')
42
- .replace(/#/g, '&nbsp;')
43
- .replace(/:00/g, '')}`
33
+ if (start && end && event.start.getTime() !== event.end.getTime()) {
34
+ return `${start} - ${end}`
44
35
  }
36
+ if (start) return start
45
37
 
46
- return time
38
+ return ''
47
39
  }
@@ -1,11 +1,10 @@
1
1
  import format from 'date-fns/format'
2
- import parse from 'date-fns/parse'
2
+ import parseISO from 'date-fns/parseISO'
3
3
  import formattedTime from './formatEventTime'
4
4
 
5
5
  // Function to sanitize the summary
6
6
  const sanitizeSummary = summary => {
7
7
  if (!summary) return summary
8
-
9
8
  const pattern = /vs\.[^>]*>Multiple Schools/
10
9
  return summary.replace(pattern, 'vs. Multiple Schools')
11
10
  }
@@ -16,15 +15,12 @@ const sanitizeDescription = description => {
16
15
 
17
16
  // Check if the description contains ">Multiple Schools"
18
17
  if (description.includes('>Multiple Schools')) {
19
- // Remove text between the ">" backwards to the first "." or ":" and replace '>Multiple Schools' with '.'
20
18
  description = description.replace(
21
19
  /([.:][^>]*?)>([^>]*Multiple Schools)/,
22
20
  '$1.'
23
21
  )
24
-
25
22
  // Separate capital letters preceded by lowercase letters with ', '
26
23
  description = description.replace(/([a-z])([A-Z])/g, '$1, $2')
27
-
28
24
  // Remove unmatched quotes between "." or ":" and ">"
29
25
  const quotePattern = /[.:][^>]*['"][^'"]*$|[^'"]*['"][^>]*['"]>Multiple Schools/g
30
26
  description = description.replace(quotePattern, match => {
@@ -32,7 +28,6 @@ const sanitizeDescription = description => {
32
28
  return quotes.length % 2 !== 0 ? match.replace(/['"]/g, '') : match
33
29
  })
34
30
  }
35
-
36
31
  return description
37
32
  }
38
33
 
@@ -40,11 +35,31 @@ export default function mapEventToCard(event) {
40
35
  const sanitizedSummary = sanitizeSummary(event.summary)
41
36
  const sanitizedDescription = sanitizeDescription(event.description)
42
37
 
38
+ let dateObj = null
39
+ if (event.start) {
40
+ try {
41
+ dateObj = parseISO(event.start)
42
+ if (isNaN(dateObj)) {
43
+ // fallback
44
+ const { parse } = require('date-fns')
45
+ dateObj = parse(event.start, "yyyy-MM-dd'T'HH:mm:ssX", new Date())
46
+ }
47
+ } catch (e) {
48
+ dateObj = null
49
+ }
50
+ }
51
+
52
+ // Always calculate these fields, not just rely on input
53
+ // const weekday = dateObj && !isNaN(dateObj) ? format(dateObj, 'EEEE') : ''
54
+ // const day = dateObj && !isNaN(dateObj) ? format(dateObj, 'dd') : ''
55
+ // const month = dateObj && !isNaN(dateObj) ? format(dateObj, 'MMMM') : ''
56
+ // const year = dateObj && !isNaN(dateObj) ? format(dateObj, 'yyyy') : ''
57
+
43
58
  return {
44
59
  title: sanitizedSummary,
45
60
  description: sanitizedDescription,
46
61
  categories: event.calendarCategories.concat([event.calendarName]),
47
- date: format(parse(event.start, "yyyy-MM-dd'T'HH:mm:ssX", new Date())),
62
+ date: dateObj && !isNaN(dateObj) ? format(dateObj, 'MMMM d, yyyy') : '',
48
63
  time: formattedTime(event),
49
64
  weekday: event.weekday,
50
65
  day: event.day,
@@ -1,18 +1,10 @@
1
- import parse from 'date-fns/parse'
2
1
  import format from 'date-fns/format'
3
- import addDays from 'date-fns/add_days'
4
- import isSameDay from 'date-fns/is_same_day'
5
- import getDaysInMonth from 'date-fns/get_days_in_month'
2
+ import addDays from 'date-fns/addDays'
3
+ import isSameDay from 'date-fns/isSameDay'
4
+ import getDaysInMonth from 'date-fns/getDaysInMonth'
6
5
 
7
6
  /**
8
7
  * Given an array of events, returns an array of days with events grouped by date.
9
- *
10
- * @export
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
8
  */
17
9
  export default function mapEventsToDays(
18
10
  events,
@@ -30,8 +22,8 @@ export default function mapEventsToDays(
30
22
  const uniqueDatesMap = new Map()
31
23
 
32
24
  events.forEach(event => {
33
- const eventDate = parse(event.start)
34
- const dateKey = format(eventDate, 'YYYY-MM-DD')
25
+ const eventDate = new Date(event.start)
26
+ const dateKey = format(eventDate, 'yyyy-MM-dd')
35
27
  if (!uniqueDatesMap.has(dateKey)) {
36
28
  uniqueDatesMap.set(dateKey, eventDate)
37
29
  }
@@ -44,8 +36,8 @@ export default function mapEventsToDays(
44
36
  const lastDayOfMonth = new Date(year, month + 1, 0)
45
37
  const daysInMonth = getDaysInMonth(firstDayOfMonth)
46
38
 
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
39
+ const prevMonthDays = firstDayOfMonth.getDay()
40
+ const nextMonthDays = 6 - lastDayOfMonth.getDay()
49
41
  const totalDays = daysInMonth + prevMonthDays + nextMonthDays
50
42
 
51
43
  for (let i = -prevMonthDays; i < totalDays - prevMonthDays; i++) {
@@ -61,12 +53,6 @@ export default function mapEventsToDays(
61
53
 
62
54
  /**
63
55
  * 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
56
  */
71
57
  function createDayObject(date, events, outsideOfCurrentMonth = false, today) {
72
58
  const normalizedDate = new Date(date)
@@ -77,12 +63,12 @@ function createDayObject(date, events, outsideOfCurrentMonth = false, today) {
77
63
  date: normalizedDate,
78
64
  unix: normalizedDate.getTime(),
79
65
  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'),
66
+ events: events.filter(event => isSameDay(new Date(event.start), normalizedDate)),
67
+ weekday: format(normalizedDate, 'EEEE'),
68
+ day: format(normalizedDate, 'dd'),
69
+ shortDay: format(normalizedDate, 'd'),
70
+ number: format(normalizedDate, 'd'),
85
71
  month: format(normalizedDate, 'MMMM'),
86
- year: format(normalizedDate, 'YYYY'),
72
+ year: format(normalizedDate, 'yyyy'),
87
73
  }
88
74
  }
@@ -1,4 +1,5 @@
1
1
  import format from 'date-fns/format'
2
+ import parseISO from 'date-fns/parseISO'
2
3
 
3
4
  export default function mapMessageToCard(
4
5
  message,
@@ -48,14 +49,43 @@ export default function mapMessageToCard(
48
49
  }
49
50
  }
50
51
 
52
+ // Safely format dates (handle missing/null)
53
+ let date = ''
54
+ let modifiedDate = ''
55
+ let time = ''
56
+
57
+ if (message.publishDateTime) {
58
+ try {
59
+ const parsed = parseISO(message.publishDateTime)
60
+ if (!isNaN(parsed)) {
61
+ date = format(parsed, 'MMM. d, yyyy')
62
+ time = format(parsed, 'h:mm aa')
63
+ }
64
+ } catch (e) {
65
+ date = ''
66
+ time = ''
67
+ }
68
+ }
69
+
70
+ if (message.modifiedDateTime) {
71
+ try {
72
+ const parsed = parseISO(message.modifiedDateTime)
73
+ if (!isNaN(parsed)) {
74
+ modifiedDate = format(parsed, 'MMM. d, yyyy')
75
+ }
76
+ } catch (e) {
77
+ modifiedDate = ''
78
+ }
79
+ }
80
+
51
81
  return Object.assign(
52
82
  {
53
83
  title: message.title,
54
84
  description: message.description || message.summary,
55
85
  categories: message.categories,
56
- date: format(message.publishDateTime, 'MMM. D, YYYY'),
57
- modifiedDate: format(message.modifiedDateTime, 'MMM. D, YYYY'),
58
- time: format(message.publishDateTime, 'h:mm A'),
86
+ date,
87
+ modifiedDate,
88
+ time,
59
89
  pinned: message.isFeatured || message.isDistrictFeatured,
60
90
  image: bannerImageOrDefault ? bannerImageOrDefault.url : '',
61
91
  alt: bannerImageOrDefault ? coverImageAlt : '',