picocrank 1.6.3 → 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picocrank",
3
- "version": "1.6.3",
3
+ "version": "1.7.0",
4
4
  "description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
5
5
  "main": "vite.config.js",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  "@hugeicons/core-free-icons": "^1.0.16",
22
22
  "@hugeicons/vue": "^1.0.3",
23
23
  "@vitejs/plugin-vue": "^6.0.1",
24
- "femtocrank": "^2.4.3",
24
+ "femtocrank": "^2.4.4",
25
25
  "unplugin-vue-components": "^29.0.0",
26
26
  "vite": "^7.1.3",
27
27
  "vue": "^3.5.19",
package/styles.css ADDED
@@ -0,0 +1,3 @@
1
+ /* Import femtocrank styles */
2
+ @import 'femtocrank/style.css';
3
+ @import 'femtocrank/dark.css';
@@ -57,6 +57,21 @@ const currentDate = computed(() => {
57
57
  const currentMonth = computed(() => currentDate.value.getMonth())
58
58
  const currentYear = computed(() => currentDate.value.getFullYear())
59
59
 
60
+ // Compute the first visible day (Monday) for the current month view
61
+ function getStartOfGrid(date: Date): Date {
62
+ const firstOfMonth = new Date(date.getFullYear(), date.getMonth(), 1)
63
+ const dayOfWeek = (firstOfMonth.getDay() + 6) % 7 // Monday=0
64
+ const start = new Date(firstOfMonth)
65
+ start.setDate(firstOfMonth.getDate() - dayOfWeek)
66
+ start.setHours(0, 0, 0, 0)
67
+ return start
68
+ }
69
+
70
+ // Anchor date for the 6x7 grid; we will shift this by weeks without re-rendering the grid structure
71
+ const gridStartDate = ref<Date>(getStartOfGrid(currentDate.value))
72
+ // Track if we're actively scrolling to prevent watcher from resetting grid anchor
73
+ const isScrolling = ref(false)
74
+
60
75
  // Helper function to check if a time is midnight (00:00)
61
76
  function isMidnight(dateValue: any): boolean {
62
77
  if (!dateValue) return false
@@ -72,6 +87,15 @@ function formatTimeOrNoTime(dateValue: any): string {
72
87
  return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
73
88
  }
74
89
 
90
+ // Determine if a date is in the past (before today)
91
+ function isPastDay(date: Date): boolean {
92
+ const day = new Date(date)
93
+ day.setHours(0, 0, 0, 0)
94
+ const today = new Date()
95
+ today.setHours(0, 0, 0, 0)
96
+ return day.getTime() < today.getTime()
97
+ }
98
+
75
99
  // Get event date range
76
100
  function getEventDateRange(event: CalendarEvent): { start: Date | null; end: Date | null } {
77
101
  if (props.getEventDateRange) {
@@ -179,55 +203,18 @@ function formatEventTimeDefault(event: CalendarEvent, date: Date): string {
179
203
  return 'All day'
180
204
  }
181
205
 
182
- // Calendar generation
206
+ // Calendar generation from persistent grid start
183
207
  const calendarDays = computed(() => {
184
- const year = currentYear.value
185
- const month = currentMonth.value
186
-
187
- const firstDay = new Date(year, month, 1)
188
- const lastDay = new Date(year, month + 1, 0)
189
- const daysInMonth = lastDay.getDate()
190
-
191
- // Get starting day of week (Monday as first day)
192
- const startDay = (firstDay.getDay() + 6) % 7
193
-
194
- const days = []
195
-
196
- // Add days from previous month to fill the first row
197
- const prevMonth = month === 0 ? 11 : month - 1
198
- const prevYear = month === 0 ? year - 1 : year
199
- const prevMonthLastDay = new Date(prevYear, prevMonth + 1, 0).getDate()
200
-
201
- for (let i = 0; i < startDay; i++) {
202
- const day = prevMonthLastDay - startDay + i + 1
203
- const date = new Date(prevYear, prevMonth, day)
204
- days.push({
205
- date,
206
- events: getEventsForDate(date)
207
- })
208
- }
209
-
210
- // Add days of the month
211
- for (let day = 1; day <= daysInMonth; day++) {
212
- const date = new Date(year, month, day)
213
- days.push({
214
- date,
215
- events: getEventsForDate(date)
216
- })
217
- }
218
-
219
- // Add days from next month to complete the last row
220
- const totalCells = 42
221
- const remainingCells = totalCells - days.length
222
-
223
- for (let day = 1; day <= remainingCells; day++) {
224
- const date = new Date(year, month + 1, day)
208
+ const days = [] as { date: Date; events: CalendarEvent[] }[]
209
+ const start = gridStartDate.value
210
+ for (let i = 0; i < 42; i++) {
211
+ const date = new Date(start)
212
+ date.setDate(start.getDate() + i)
225
213
  days.push({
226
214
  date,
227
215
  events: getEventsForDate(date)
228
216
  })
229
217
  }
230
-
231
218
  return days
232
219
  })
233
220
 
@@ -237,8 +224,11 @@ function previousMonth() {
237
224
  const newMonth = currentMonth.value === 0 ? 11 : currentMonth.value - 1
238
225
  const newYear = currentMonth.value === 0 ? currentYear.value - 1 : currentYear.value
239
226
  emit('month-change', newMonth, newYear)
227
+ // Optimistically update grid anchor for smooth transition
228
+ gridStartDate.value = getStartOfGrid(new Date(newYear, newMonth, 1))
240
229
  } else {
241
230
  internalCurrentDate.value = new Date(currentYear.value, currentMonth.value - 1, 1)
231
+ gridStartDate.value = getStartOfGrid(internalCurrentDate.value)
242
232
  }
243
233
  }
244
234
 
@@ -247,8 +237,10 @@ function nextMonth() {
247
237
  const newMonth = currentMonth.value === 11 ? 0 : currentMonth.value + 1
248
238
  const newYear = currentMonth.value === 11 ? currentYear.value + 1 : currentYear.value
249
239
  emit('month-change', newMonth, newYear)
240
+ gridStartDate.value = getStartOfGrid(new Date(newYear, newMonth, 1))
250
241
  } else {
251
242
  internalCurrentDate.value = new Date(currentYear.value, currentMonth.value + 1, 1)
243
+ gridStartDate.value = getStartOfGrid(internalCurrentDate.value)
252
244
  }
253
245
  }
254
246
 
@@ -256,11 +248,53 @@ function goToToday() {
256
248
  if (props.currentMonth !== undefined && props.currentYear !== undefined) {
257
249
  const today = new Date()
258
250
  emit('month-change', today.getMonth(), today.getFullYear())
251
+ gridStartDate.value = getStartOfGrid(today)
259
252
  } else {
260
253
  internalCurrentDate.value = new Date()
254
+ gridStartDate.value = getStartOfGrid(internalCurrentDate.value)
255
+ }
256
+ }
257
+
258
+ // Week navigation (mouse wheel)
259
+ const lastWheelAt = ref(0)
260
+ const wheelThrottleMs = 180
261
+ function getVisibleMonthYear(start: Date): { month: number; year: number } {
262
+ const mid = new Date(start)
263
+ mid.setDate(start.getDate() + 21) // middle of 6x7 grid
264
+ return { month: mid.getMonth(), year: mid.getFullYear() }
265
+ }
266
+
267
+ function adjustByWeeks(weeks: number): void {
268
+ const newStart = new Date(gridStartDate.value)
269
+ newStart.setDate(newStart.getDate() + weeks * 7)
270
+ gridStartDate.value = newStart
271
+
272
+ if (props.currentMonth !== undefined && props.currentYear !== undefined) {
273
+ const { month, year } = getVisibleMonthYear(newStart)
274
+ if (month !== currentMonth.value || year !== currentYear.value) {
275
+ emit('month-change', month, year)
276
+ // Set flag to prevent watcher from resetting our scroll position
277
+ isScrolling.value = true
278
+ // Clear the flag after a short delay to allow future prop-driven updates
279
+ setTimeout(() => { isScrolling.value = false }, 100)
280
+ }
281
+ } else {
282
+ // Keep internal anchor and current date roughly aligned (set to first of visible month)
283
+ const { month, year } = getVisibleMonthYear(newStart)
284
+ internalCurrentDate.value = new Date(year, month, 1)
261
285
  }
262
286
  }
263
287
 
288
+ function handleWheel(event: WheelEvent): void {
289
+ // Prevent page scrolling while using calendar scroll
290
+ event.preventDefault()
291
+ const now = (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now()
292
+ if (now - lastWheelAt.value < wheelThrottleMs) return
293
+ lastWheelAt.value = now
294
+ const direction = event.deltaY > 0 ? 1 : -1
295
+ adjustByWeeks(direction)
296
+ }
297
+
264
298
  // Event handlers
265
299
  function handleEventClick(event: CalendarEvent) {
266
300
  emit('event-click', event)
@@ -275,6 +309,15 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
275
309
  mouseEvent.stopPropagation()
276
310
  emit('event-context-menu', event, mouseEvent)
277
311
  }
312
+ import { watch } from 'vue'
313
+
314
+ // Sync grid start when controlled month/year props change (but not during scrolling)
315
+ watch([currentMonth, currentYear], () => {
316
+ if (isScrolling.value) return
317
+ const anchor = new Date(currentYear.value, currentMonth.value, 1)
318
+ gridStartDate.value = getStartOfGrid(anchor)
319
+ })
320
+
278
321
  </script>
279
322
 
280
323
  <template>
@@ -290,10 +333,10 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
290
333
  </div>
291
334
  </div>
292
335
 
293
- <div v-if="error" class="calendar-error">{{ error }}</div>
294
- <div v-else-if="loading" class="calendar-loading">Loading…</div>
295
- <div v-else class="calendar-container">
296
- <div class="calendar-grid">
336
+ <div class="calendar-container">
337
+ <div v-if="error" class="calendar-error">{{ error }}</div>
338
+ <div v-if="loading" class="calendar-loading-overlay">Loading…</div>
339
+ <div class="calendar-grid" @wheel.prevent="handleWheel">
297
340
  <!-- Day headers -->
298
341
  <div v-for="day in dayNames" :key="day" class="day-header">{{ day }}</div>
299
342
 
@@ -306,7 +349,8 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
306
349
  'today': day && day.date.toDateString() === new Date().toDateString(),
307
350
  'weekend': day && (day.date.getDay() === 0 || day.date.getDay() === 6),
308
351
  'prev-month': day && day.date.getMonth() !== currentMonth && day.date.getMonth() !== (currentMonth + 1) % 12,
309
- 'next-month': day && day.date.getMonth() !== currentMonth && day.date.getMonth() === (currentMonth + 1) % 12
352
+ 'next-month': day && day.date.getMonth() !== currentMonth && day.date.getMonth() === (currentMonth + 1) % 12,
353
+ 'past': day && isPastDay(day.date)
310
354
  }"
311
355
  >
312
356
  <div v-if="day" class="day-content" @click="handleDateClick(day.date)">
@@ -397,11 +441,23 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
397
441
  text-align: center;
398
442
  }
399
443
 
444
+ .calendar-loading-overlay {
445
+ position: absolute;
446
+ inset: 0;
447
+ display: flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ background: rgba(255, 255, 255, 0.6);
451
+ z-index: 1;
452
+ font-weight: 600;
453
+ }
454
+
400
455
  .calendar-container {
401
456
  background: white;
402
457
  border: 1px solid #e0e0e0;
403
458
  overflow: hidden;
404
459
  border-radius: 8px;
460
+ position: relative;
405
461
  }
406
462
 
407
463
  .calendar-grid {
@@ -427,10 +483,11 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
427
483
  .calendar-day {
428
484
  border-right: 1px solid #e0e0e0;
429
485
  border-bottom: 1px solid #e0e0e0;
430
- min-height: 120px;
486
+ height: 160px;
431
487
  position: relative;
432
488
  transition: background-color 0.2s ease;
433
489
  cursor: pointer;
490
+ overflow: hidden;
434
491
  }
435
492
 
436
493
  .calendar-day:hover {
@@ -452,12 +509,18 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
452
509
  .calendar-day.next-month,
453
510
  .calendar-day.prev-month {
454
511
  background: #f8f9fa;
455
- opacity: 0.6;
456
512
  }
457
513
 
458
514
  .calendar-day.next-month:hover,
459
515
  .calendar-day.prev-month:hover {
460
516
  background: #e9ecef;
517
+ }
518
+
519
+ .calendar-day.past {
520
+ opacity: 0.6;
521
+ }
522
+
523
+ .calendar-day.past:hover {
461
524
  opacity: 0.8;
462
525
  }
463
526
 
@@ -503,7 +566,7 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
503
566
 
504
567
  .day-events {
505
568
  flex: 1;
506
- overflow: hidden;
569
+ overflow: auto;
507
570
  }
508
571
 
509
572
  .calendar-event {
@@ -587,7 +650,7 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
587
650
  }
588
651
 
589
652
  .calendar-day {
590
- min-height: 80px;
653
+ height: 80px;
591
654
  }
592
655
 
593
656
  .day-number {
@@ -609,7 +672,7 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
609
672
  }
610
673
 
611
674
  .calendar-day {
612
- min-height: 60px;
675
+ height: 60px;
613
676
  }
614
677
 
615
678
  .day-header {
@@ -692,5 +755,13 @@ function handleContextMenu(event: CalendarEvent, mouseEvent: MouseEvent) {
692
755
  .calendar-day.weekend:hover {
693
756
  background: #374151;
694
757
  }
758
+
759
+ .calendar-day.past {
760
+ opacity: 0.6;
761
+ }
762
+
763
+ .calendar-day.past:hover {
764
+ opacity: 0.8;
765
+ }
695
766
  }
696
767
  </style>
package/vue/views/App.vue CHANGED
@@ -19,7 +19,7 @@
19
19
  </main>
20
20
 
21
21
  <footer>
22
- <span>PicoCrank</span>
22
+ <span><a href = "https://github.com/jamesread/picocrank">PicoCrank</a></span>
23
23
  </footer>
24
24
  </div>
25
25
  </div>
@@ -31,7 +31,7 @@
31
31
  import { Pin02Icon } from '@hugeicons/core-free-icons'
32
32
 
33
33
  import QuickSearch from './../components/QuickSearch.vue'
34
- import '../../src/styles.css'
34
+ import '../../styles.css'
35
35
  import logoUrl from '/logo.png';
36
36
 
37
37
  const quickSearchRef = ref(null)
@@ -44,37 +44,48 @@ const currentMonth = ref('?')
44
44
  const currentMonthIndex = ref(new Date().getMonth())
45
45
  const currentYear = ref(new Date().getFullYear())
46
46
 
47
+ // Cache events by month/year key (e.g., "2024-10")
48
+ const eventsCache = new Map()
49
+
47
50
  // Date picker state - using native HTML5 month input
48
51
  const selectedDate = ref('')
49
52
 
50
- // Generate dummy events for the current month
51
- function generateDummyEvents() {
52
- const now = new Date()
53
- const currentMonth = now.getMonth()
54
- const currentYear = now.getFullYear()
55
-
56
- // Get the number of days in the current month
57
- const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate()
53
+ // Simple seeded random number generator for consistent event generation
54
+ function seededRandom(seed) {
55
+ let value = seed
56
+ return function() {
57
+ value = (value * 9301 + 49297) % 233280
58
+ return value / 233280
59
+ }
60
+ }
61
+
62
+ // Generate dummy events for a specific month
63
+ function generateDummyEventsForMonth(monthIndex, year) {
64
+ // Get the number of days in the month
65
+ const daysInMonth = new Date(year, monthIndex + 1, 0).getDate()
58
66
 
59
67
  const dummyEvents = []
60
68
 
69
+ // Create a seeded random generator for consistent events per month
70
+ const random = seededRandom(year * 12 + monthIndex)
71
+
61
72
  // Add some recurring events
62
73
  for (let day = 1; day <= daysInMonth; day++) {
63
- const date = new Date(currentYear, currentMonth, day)
74
+ const date = new Date(year, monthIndex, day)
64
75
  const dayOfWeek = date.getDay()
65
76
 
66
77
  // Add weekend events (Saturday and Sunday)
67
78
  if (dayOfWeek === 0 || dayOfWeek === 6) {
68
79
  dummyEvents.push({
69
- id: `weekend-${day}`,
80
+ id: `weekend-${year}-${monthIndex}-${day}`,
70
81
  title: 'Weekend Activity',
71
- date: new Date(currentYear, currentMonth, day, 10, 0),
82
+ date: new Date(year, monthIndex, day, 10, 0),
72
83
  description: 'Relaxing weekend activity'
73
84
  })
74
85
  }
75
86
 
76
87
  // Add some random events
77
- if (Math.random() > 0.7) {
88
+ if (random() > 0.7) {
78
89
  const eventTypes = [
79
90
  { title: 'Team Meeting', time: [9, 0], duration: 1 },
80
91
  { title: 'Client Call', time: [14, 30], duration: 0.5 },
@@ -83,13 +94,13 @@ function generateDummyEvents() {
83
94
  { title: 'Code Review', time: [10, 0], duration: 1.5 }
84
95
  ]
85
96
 
86
- const eventType = eventTypes[Math.floor(Math.random() * eventTypes.length)]
97
+ const eventType = eventTypes[Math.floor(random() * eventTypes.length)]
87
98
  const [hours, minutes] = eventType.time
88
99
 
89
100
  dummyEvents.push({
90
- id: `event-${day}-${Math.random().toString(36).substr(2, 9)}`,
101
+ id: `event-${year}-${monthIndex}-${day}-${Math.random().toString(36).substr(2, 9)}`,
91
102
  title: eventType.title,
92
- date: new Date(currentYear, currentMonth, day, hours, minutes),
103
+ date: new Date(year, monthIndex, day, hours, minutes),
93
104
  description: `Scheduled ${eventType.title.toLowerCase()}`
94
105
  })
95
106
  }
@@ -97,34 +108,34 @@ function generateDummyEvents() {
97
108
 
98
109
  // Add some multi-day events
99
110
  dummyEvents.push({
100
- id: 'conference-1',
111
+ id: `conference-${year}-${monthIndex}`,
101
112
  title: 'Tech Conference',
102
- startDate: new Date(currentYear, currentMonth, 5, 9, 0),
103
- endDate: new Date(currentYear, currentMonth, 7, 17, 0),
113
+ startDate: new Date(year, monthIndex, 5, 9, 0),
114
+ endDate: new Date(year, monthIndex, 7, 17, 0),
104
115
  description: 'Annual technology conference'
105
116
  })
106
117
 
107
118
  dummyEvents.push({
108
- id: 'vacation-1',
119
+ id: `vacation-${year}-${monthIndex}`,
109
120
  title: 'Vacation',
110
- startDate: new Date(currentYear, currentMonth, 15, 0, 0),
111
- endDate: new Date(currentYear, currentMonth, 18, 23, 59),
121
+ startDate: new Date(year, monthIndex, 15, 0, 0),
122
+ endDate: new Date(year, monthIndex, 18, 23, 59),
112
123
  description: 'Family vacation time'
113
124
  })
114
125
 
115
126
  // Add some all-day events
116
127
  dummyEvents.push({
117
- id: 'holiday-1',
128
+ id: `holiday-${year}-${monthIndex}`,
118
129
  title: 'Public Holiday',
119
- date: new Date(currentYear, currentMonth, 1, 0, 0),
130
+ date: new Date(year, monthIndex, 1, 0, 0),
120
131
  description: 'National holiday - office closed'
121
132
  })
122
133
 
123
134
  if (daysInMonth >= 25) {
124
135
  dummyEvents.push({
125
- id: 'deadline-1',
136
+ id: `deadline-${year}-${monthIndex}`,
126
137
  title: 'Project Deadline',
127
- date: new Date(currentYear, currentMonth, 25, 0, 0),
138
+ date: new Date(year, monthIndex, 25, 0, 0),
128
139
  description: 'Important project deadline'
129
140
  })
130
141
  }
@@ -132,9 +143,47 @@ function generateDummyEvents() {
132
143
  return dummyEvents
133
144
  }
134
145
 
146
+ // Get or generate events for a specific month/year
147
+ function getEventsForMonth(monthIndex, year) {
148
+ const cacheKey = `${year}-${monthIndex}`
149
+
150
+ // Return cached events if available
151
+ if (eventsCache.has(cacheKey)) {
152
+ return eventsCache.get(cacheKey)
153
+ }
154
+
155
+ // Generate new events for this month
156
+ const dummyEvents = generateDummyEventsForMonth(monthIndex, year)
157
+
158
+ // Cache the events
159
+ eventsCache.set(cacheKey, dummyEvents)
160
+
161
+ return dummyEvents
162
+ }
163
+
164
+ // Merge events for previous, current, and next months so overflow days show events
165
+ function getEventsForSurroundingMonths(monthIndex, year) {
166
+ const prevMonthIndex = monthIndex === 0 ? 11 : monthIndex - 1
167
+ const prevYear = monthIndex === 0 ? year - 1 : year
168
+ const nextMonthIndex = monthIndex === 11 ? 0 : monthIndex + 1
169
+ const nextYear = monthIndex === 11 ? year + 1 : year
170
+
171
+ return [
172
+ ...getEventsForMonth(prevMonthIndex, prevYear),
173
+ ...getEventsForMonth(monthIndex, year),
174
+ ...getEventsForMonth(nextMonthIndex, nextYear)
175
+ ]
176
+ }
177
+
178
+ // Generate dummy events for the current month (backward compatibility wrapper)
179
+ function generateDummyEvents() {
180
+ const now = new Date()
181
+ return getEventsForMonth(now.getMonth(), now.getFullYear())
182
+ }
183
+
135
184
  function handleEventClick(event) {
136
- console.log('Event clicked:', event)
137
- alert(`Event: ${event.title}\nDescription: ${event.description || 'No description'}`)
185
+ console.log('Event clicked:', event)
186
+ alert(`Event: ${event.title}\nDescription: ${event.description || 'No description'}`)
138
187
  }
139
188
 
140
189
  function handleDateClick(date) {
@@ -166,6 +215,9 @@ function previousMonth() {
166
215
 
167
216
  // Update date picker
168
217
  updateDatePicker(newMonth, newYear)
218
+
219
+ // Update events for surrounding months
220
+ events.value = getEventsForSurroundingMonths(newMonth, newYear)
169
221
  }
170
222
 
171
223
  function nextMonth() {
@@ -185,6 +237,9 @@ function nextMonth() {
185
237
 
186
238
  // Update date picker
187
239
  updateDatePicker(newMonth, newYear)
240
+
241
+ // Update events for surrounding months
242
+ events.value = getEventsForSurroundingMonths(newMonth, newYear)
188
243
  }
189
244
 
190
245
  function goToToday() {
@@ -204,6 +259,9 @@ function goToToday() {
204
259
 
205
260
  // Update date picker
206
261
  updateDatePicker(newMonth, newYear)
262
+
263
+ // Update events for surrounding months
264
+ events.value = getEventsForSurroundingMonths(newMonth, newYear)
207
265
  }
208
266
 
209
267
  function goToSelectedDate() {
@@ -223,6 +281,9 @@ function goToSelectedDate() {
223
281
  month: 'long',
224
282
  year: 'numeric'
225
283
  })
284
+
285
+ // Update events for surrounding months
286
+ events.value = getEventsForSurroundingMonths(monthIndex, year)
226
287
  }
227
288
 
228
289
  function handleMonthChange(month, year) {
@@ -245,18 +306,20 @@ function handleMonthChange(month, year) {
245
306
  updateDatePicker(month, year)
246
307
 
247
308
  setTimeout(() => {
248
- // Simulate loading delay
249
- events.value = generateDummyEvents()
309
+ // Load events for surrounding months (will use cache if available)
310
+ events.value = getEventsForSurroundingMonths(month, year)
250
311
  loading.value = false
251
312
  }, 500)
252
313
  }
253
314
 
254
315
  onMounted(() => {
255
- // Generate initial events for current month
256
- events.value = generateDummyEvents()
316
+ // Generate initial events for current, previous, and next months
317
+ const now = new Date()
318
+ const initMonth = now.getMonth()
319
+ const initYear = now.getFullYear()
320
+ events.value = getEventsForSurroundingMonths(initMonth, initYear)
257
321
 
258
322
  // Set initial month title using native JavaScript
259
- const now = new Date()
260
323
  currentMonthIndex.value = now.getMonth()
261
324
  currentYear.value = now.getFullYear()
262
325
  currentMonth.value = now.toLocaleDateString('en-US', {