@@ -47,7 +47,7 @@ layout: layouts/base.vto
4747 <div class="flex justify-center mb-8">
4848 <div class="flex gap-2">
4949 <template x-for="(yearData, idx) in yearNavigation" :key="idx">
50- <button
50+ <button
5151 x-show="yearData.show"
5252 @click="navigateToYear(yearData.year)"
5353 class="px-4 py-2 bg-white text-gray-800 rounded-lg hover:bg-gray-100 transition-colors font-semibold"
@@ -58,24 +58,29 @@ layout: layouts/base.vto
5858 </div>
5959
6060 {{# Organization Filter #}}
61- <div class="mb-8">
61+ <div class="mb-8" x-cloak >
6262 <div class="flex flex-col sm:flex-row sm:items-center gap-4">
6363 <label class="text-white font-semibold">Filter by Organization:</label>
6464 <div class="flex flex-wrap gap-2">
6565 {{ for org of organizations .sort ((a , b ) => a .name .localeCompare (b .name )) }}
66- <button
67- @click="toggleOrganization('{{ org.id }}')"
68- :class="selectedOrgs.includes('{{ org.id }}') ? 'bg-blue-600 text-white' : ' bg-white text-gray-800' "
69- class=" px-3 py-1 rounded-lg transition-colors text-sm hover:opacity-90"
70- x-show="hasEventsInYear('{{ org.id }}')"
71- >
72- {{ org .name }}
73- </button>
66+ {{# Check if org has events in any year #}}
67+ {{ set hasEvents = events .some (e => e .by === org .id ) }}
68+ {{ if hasEvents }}
69+ <button
70+ @click="toggleOrganization('{{ org.id }}')"
71+ :class="selectedOrgs.includes('{{ org.id }}') ? 'bg-blue-600 text-white' : ' bg-white text-gray-800' "
72+ class=" px-3 py-1 rounded-lg transition-colors text-sm hover:opacity-90"
73+ x-show="hasEventsInYear('{{ org.id }}')"
74+ >
75+ {{ org .name }}
76+ </button>
77+ {{ /if }}
7478 {{ /for }}
7579 <button
7680 x-show="selectedOrgs.length > 0"
7781 @click="clearFilters()"
78- class="px-3 py-1 bg-red-600 text-white rounded-lg transition-colors text-sm hover:bg-red-700"
82+ class="px-3 py-1 bg-red-600 text-white rounded-lg transition-colors text-sm hover:bg-red-700 hidden"
83+ :class="{'hidden' : selectedOrgs.length === 0}"
7984 >
8085 Clear All
8186 </button>
@@ -91,18 +96,18 @@ layout: layouts/base.vto
9196 {{ set daysInMonth = new Date (year, monthIndex + 1 , 0 ).getDate () }}
9297 {{ set firstDayRaw = new Date (year, monthIndex, 1 ).getDay () }}
9398 {{ set firstDay = (firstDayRaw + 6 ) % 7 }} {{# Convert Sunday=0 to Monday=0 #}}
94-
95- < div
96- class = " bg-white rounded-lg shadow-md overflow-hidden month-card"
99+
100+ < div
101+ class = " bg-white rounded-lg shadow-md overflow-hidden month-card flex flex-col "
97102 data- year= " {{ year }}"
98103 data- month= " {{ monthIndex }}"
99104 x- show= " currentYear === {{ year }}"
100105 >
101- < div class = " bg-gray-800 text-white px-4 py-3" >
106+ < div class = " bg-gray-800 text-white px-4 py-3 relative " >
102107 < h2 class = " text-xl font-semibold" > {{ monthName }}< / h2>
103108 < / div>
104109
105- < div class = " p-4" >
110+ < div class = " p-4 bg-white flex-1 " >
106111 < div class = " grid grid-cols-8 gap-1 text-xs" >
107112 {{# Week day headers #}}
108113 < div>< / div>
@@ -111,17 +116,17 @@ layout: layouts/base.vto
111116 {{ /for }}
112117
113118 {{# Generate calendar with proper week structure #}}
114-
119+
115120 {{# Calculate total weeks needed #}}
116121 {{ set totalDays = firstDay + daysInMonth }}
117122 {{ set totalWeeks = Math .ceil (totalDays / 7 ) }}
118-
123+
119124 {{# Generate each week row #}}
120125 {{ for weekIndex of [... Array (totalWeeks).keys ()] }}
121126 {{# Calculate first day of this week row #}}
122127 {{ set weekStartDay = weekIndex * 7 - firstDay + 1 }}
123128 {{ set weekEndDay = Math .min (weekStartDay + 6 , daysInMonth) }}
124-
129+
125130 {{# Add week number for this row #}}
126131 {{ if weekStartDay <= daysInMonth }}
127132 {{# Get the Monday date for this week row to calculate correct ISO week #}}
@@ -143,11 +148,11 @@ layout: layouts/base.vto
143148 {{ else }}
144149 < div>< / div>
145150 {{ /if }}
146-
151+
147152 {{# Generate 7 day cells for this week #}}
148153 {{ for dayIndex of [... Array (7 ).keys ()] }}
149154 {{ set day = weekIndex * 7 + dayIndex - firstDay + 1 }}
150-
155+
151156 {{ if day < 1 || day > daysInMonth }}
152157 {{# Empty cell for days outside the month #}}
153158 < div>< / div>
@@ -159,33 +164,33 @@ layout: layouts/base.vto
159164 const endDate = e .end_date ? (typeof e .end_date === ' string' ? e .end_date : e .end_date .toISOString ().split (' T' )[0 ]) : startDate;
160165 return startDate <= dateStr && endDate >= dateStr;
161166 }) }}
162-
167+
163168 {{ set eventOrgs = dayEvents .map (e => ` '${ e .by } '` ) }}
164169 {{ set eventOrgsStr = eventOrgs .length > 0 ? ` [${ eventOrgs .join (' ,' )} ]` : ' []' }}
165-
170+
166171 < div class = " relative calendar-cell" data- date= " {{ dateStr }}" >
167172 {{ if dayEvents .length > 0 }}
168173 < button
169174 type= " button"
170175 @click= " showEventModal( {{ year }}, {{ monthIndex }}, {{ day }}, $event)"
171176 class=" event - day w- full text- center p- 1 rounded font- semibold cursor- pointer transition- colors bg- blue- 100 text- blue- 900 hover: bg- blue- 200 "
172- :class=" isPastDate( {{ year }}, {{ monthIndex }}, {{ day }})
173- ? ' bg-gray-100 text-gray-500 hover:bg-gray-200'
177+ :class=" isPastDate( {{ year }}, {{ monthIndex }}, {{ day }})
178+ ? ' bg-gray-100 text-gray-500 hover:bg-gray-200'
174179 : ' bg-blue-100 text-blue-900 hover:bg-blue-200' "
175180 x-show=" shouldShowEvents( {{ eventOrgsStr }})"
176181 data- events= ' {{ JSON .stringify (dayEvents) }}'
177182 >
178183 {{ day }}
179184 < / button>
180- < div
185+ < div
181186 x- show= " !shouldShowEvents( {{ eventOrgsStr }})"
182187 class=" calendar- day text- center p- 1 rounded text- gray- 700 "
183188 :class=" isPastDate( {{ year }}, {{ monthIndex }}, {{ day }}) ? 'text-gray-400' : 'text-gray-700'"
184189 >
185190 {{ day }}
186191 < / div>
187192 {{ else }}
188- < div
193+ < div
189194 class = " calendar-day text-center p-1 rounded text-gray-700"
190195 : class = " isPastDate( {{ year }}, {{ monthIndex }}, {{ day }}) ? 'text-gray-400' : 'text-gray-700'"
191196 >
@@ -208,7 +213,7 @@ layout: layouts/base.vto
208213 <h2 class=" text- 2xl font- bold text- white mb- 6 " >
209214 <span x-text=" currentYear === new Date ().getFullYear () ? ' Upcoming Events' : ' Events' " ></span>
210215 </h2>
211-
216+
212217 <div class=" space- y- 4 " >
213218 {{# Sort events by start_date #}}
214219 {{ set sortedEvents = events .sort ((a , b ) => {
@@ -223,8 +228,8 @@ layout: layouts/base.vto
223228 {{ set eventDay = parseInt (startDateStr .substring (8 , 10 )) }}
224229 {{ set eventWeek = getISOWeek (startDateStr) }}
225230 {{ set org = organizations .find (o => o .id === event .by ) }}
226-
227- <div
231+
232+ <div
228233 class=" event - card bg- white rounded- lg shadow- md p- 6 hover: shadow- lg transition- shadow"
229234 x-show=" shouldShowEvent (' {{ event .by }}' , {{ eventYear }}, ' {{ startDateStr }}' )"
230235 data-event-org=" {{ event .by }}"
@@ -263,11 +268,15 @@ layout: layouts/base.vto
263268 {{ set endMonth = parseInt (endDateStr .substring (5 , 7 )) - 1 }}
264269 {{ set endDay = parseInt (endDateStr .substring (8 , 10 )) }}
265270 {{ set endYear = parseInt (endDateStr .substring (0 , 4 )) }}
266- {{ shortMonthNames[eventMonth] }} {{ eventDay }} -
267- {{ if eventYear !== endYear }}
268- {{ shortMonthNames[endMonth] }} {{ endDay }}, {{ endYear }}
271+ {{ if eventMonth === endMonth && eventYear === endYear }}
272+ {{# Same month and year #}}
273+ {{ monthNames[eventMonth] }} {{ eventDay }}- {{ endDay }}, {{ eventYear }}
274+ {{ else if eventYear === endYear }}
275+ {{# Different months, same year #}}
276+ {{ monthNames[eventMonth] }} {{ eventDay }} - {{ monthNames[endMonth] }} {{ endDay }}, {{ eventYear }}
269277 {{ else }}
270- {{ shortMonthNames[endMonth] }} {{ endDay }}, {{ eventYear }}
278+ {{# Different years #}}
279+ {{ monthNames[eventMonth] }} {{ eventDay }}, {{ eventYear }} - {{ monthNames[endMonth] }} {{ endDay }}, {{ endYear }}
271280 {{ /if }}
272281 {{ /if }}
273282 < / p>
@@ -285,7 +294,7 @@ layout: layouts/base.vto
285294 {{# Event Modal - Must be inside x-data scope #}}
286295 < div id= " eventModal" class = " fixed inset-0 z-50" x- show= " modalOpen" x- cloak>
287296 < div class = " fixed inset-0 bg-black bg-opacity-50" @click= " closeModal()" >< / div>
288-
297+
289298 < div class = " fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto mx-4" >
290299 < div class = " sticky top-0 bg-white border-b px-6 py-4 flex justify-between items-center" >
291300 < h2 class = " text-xl font-semibold text-gray-900" x- text= " modalTitle" >< / h2>
@@ -332,7 +341,7 @@ function eventsPage() {
332341 }))) }},
333342 organizations: {{ JSON .stringify (organizations) }},
334343 eventYears: {{ JSON .stringify (eventYears) }},
335-
344+
336345 init () {
337346 // Read URL parameters for filters
338347 const params = new URLSearchParams (window .location .search );
@@ -345,35 +354,35 @@ function eventsPage() {
345354 this .currentYear = parseInt (year);
346355 }
347356 },
348-
357+
349358 get yearNavigation () {
350359 const currentIdx = this .eventYears .indexOf (this .currentYear );
351360 const nav = [];
352-
361+
353362 if (currentIdx > 0 ) {
354363 nav .push ({
355364 show: true ,
356365 year: this .eventYears [currentIdx - 1 ],
357366 label: ` ← ${ this .eventYears [currentIdx - 1 ]} `
358367 });
359368 }
360-
369+
361370 if (currentIdx < this .eventYears .length - 1 ) {
362371 nav .push ({
363372 show: true ,
364373 year: this .eventYears [currentIdx + 1 ],
365374 label: ` ${ this .eventYears [currentIdx + 1 ]} →`
366375 });
367376 }
368-
377+
369378 return nav;
370379 },
371-
380+
372381 navigateToYear (year ) {
373382 this .currentYear = year;
374383 this .updateURL ();
375384 },
376-
385+
377386 toggleOrganization (orgId ) {
378387 const idx = this .selectedOrgs .indexOf (orgId);
379388 if (idx > - 1 ) {
@@ -383,12 +392,12 @@ function eventsPage() {
383392 }
384393 this .updateURL ();
385394 },
386-
395+
387396 clearFilters () {
388397 this .selectedOrgs = [];
389398 this .updateURL ();
390399 },
391-
400+
392401 updateURL () {
393402 const params = new URLSearchParams ();
394403 if (this .selectedOrgs .length > 0 ) {
@@ -400,19 +409,19 @@ function eventsPage() {
400409 const url = params .toString () ? ` ?${ params .toString ()} ` : window .location .pathname ;
401410 window .history .replaceState ({}, ' ' , url);
402411 },
403-
412+
404413 hasEventsInYear (orgId ) {
405- return this .events .some (e =>
406- e .by === orgId &&
414+ return this .events .some (e =>
415+ e .by === orgId &&
407416 parseInt (e .start_date .substring (0 , 4 )) === this .currentYear
408417 );
409418 },
410-
419+
411420 shouldShowEvents (eventOrgs ) {
412421 if (this .selectedOrgs .length === 0 ) return true ;
413422 return eventOrgs .some (org => this .selectedOrgs .includes (org));
414423 },
415-
424+
416425 shouldShowEvent (orgId , eventYear , startDate ) {
417426 if (eventYear !== this .currentYear ) return false ;
418427 if (this .selectedOrgs .length === 0 ) {
@@ -427,33 +436,33 @@ function eventsPage() {
427436 }
428437 return this .selectedOrgs .includes (orgId);
429438 },
430-
439+
431440 isPastDate (year , month , day ) {
432441 if (year !== new Date ().getFullYear ()) return false ;
433442 const today = new Date ();
434443 today .setHours (0 , 0 , 0 , 0 );
435444 const date = new Date (year, month, day);
436445 return date < today;
437446 },
438-
447+
439448 getEventsForDate (dateStr ) {
440449 return this .events .filter (event => {
441450 const startDate = event .start_date ;
442451 const endDate = event .end_date || startDate;
443-
452+
444453 // Apply organization filter
445454 if (this .selectedOrgs .length > 0 && ! this .selectedOrgs .includes (event .by )) {
446455 return false ;
447456 }
448-
457+
449458 return startDate <= dateStr && endDate >= dateStr;
450459 });
451460 },
452-
461+
453462 showEventModal (year , month , day , evt ) {
454463 const monthNames = [" January" , " February" , " March" , " April" , " May" , " June" , " July" , " August" , " September" , " October" , " November" , " December" ];
455464 const dateStr = ` ${ year} -${ String (month + 1 ).padStart (2 , ' 0' )} -${ String (day).padStart (2 , ' 0' )} ` ;
456-
465+
457466 // Get events from the button's data attribute or fetch them
458467 const button = evt ? evt .currentTarget : null ;
459468 let events = [];
@@ -462,7 +471,7 @@ function eventsPage() {
462471 } else {
463472 events = this .getEventsForDate (dateStr);
464473 }
465-
474+
466475 this .modalTitle = ` ${ monthNames[month]} ${ day} , ${ year} ` ;
467476 this .modalEvents = events .map (e => {
468477 const org = this .organizations .find (o => o .id === e .by );
@@ -474,7 +483,7 @@ function eventsPage() {
474483 this .modalOpen = true ;
475484 document .body .style .overflow = ' hidden' ;
476485 },
477-
486+
478487 closeModal () {
479488 this .modalOpen = false ;
480489 document .body .style .overflow = ' ' ;
@@ -485,4 +494,14 @@ function eventsPage() {
485494
486495< style>
487496 [x- cloak] { display: none ! important; }
488- < / style>
497+
498+ /* Hide elements before Alpine loads */
499+ [x- show] {
500+ display: none;
501+ }
502+
503+ /* Show elements once Alpine is ready */
504+ [x- data] [x- show] {
505+ display: revert;
506+ }
507+ < / style>
0 commit comments