@@ -52,9 +52,9 @@ const RESP_ICON = {
) ,
} ;
const BELL _ICON = (
< svg xmlns = "http://www.w3.org/2000/svg" fill = "none " viewBox = "0 0 24 24" strokeWidth = { 1.5 } stroke = "#ef4444 " width = { 15 } height = { 15 } style = { { flexShrink : 0 } } >
< svg xmlns = "http://www.w3.org/2000/svg" fill = "#fbbf24 " viewBox = "0 0 24 24" strokeWidth = { 1.5 } stroke = "var(--warning-stroke) " width = { 15 } height = { 15 } style = { { flexShrink : 0 } } >
< title > Awaiting your response < / title >
< path strokeLinecap = "round" strokeLinejoin = "round" d = "M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1- 5.714 0m5.714 0a3 3 0 1 1-5.714 0 " / >
< path strokeLinecap = "round" strokeLinejoin = "round" d = "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z " / >
< / svg >
) ;
@@ -715,9 +715,12 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter
const today = new Date ( ) ; today . setHours ( 0 , 0 , 0 , 0 ) ;
const terms = parseKeywords ( filterKeyword ) ;
const hasFilters = terms . length > 0 || ! ! filterTypeId ;
// No filters: show selected month only (desktop and mobile — month nav arrows control the month).
// Filters active: show today + all future on both platforms.
const from = hasFilters ? today : new Date ( y , m , 1 ) ;
const now = new Date ( ) ; // exact now for end-time comparison
const isCurrentMonth = y === today . getFullYear ( ) && m === today . getMonth ( ) ;
// No filters: show from today (if current month) or start of month (future months) to end of month.
// Past months show nothing — navigate forward to see events.
// Filters active: today + all future events.
const from = hasFilters ? today : ( isCurrentMonth ? today : new Date ( y , m , 1 ) ) ;
const to = hasFilters ? new Date ( 9999 , 11 , 31 ) : new Date ( y , m + 1 , 0 , 23 , 59 , 59 ) ;
const filtered = events . filter ( e => {
const s = new Date ( e . start _at ) ;
@@ -729,19 +732,62 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter
}
return true ;
} ) ;
const emptyMsg = hasFilters ? 'No events match your filters' : ` No events in ${ MONTHS [ m ] } ${ y } ` ;
const emptyMsg = hasFilters
? 'No events match your filters'
: new Date ( y , m + 1 , 0 ) < today
? ` No events — ${ MONTHS [ m ] } ${ y } is in the past `
: ` No events in ${ MONTHS [ m ] } ${ y } ` ;
if ( ! filtered . length ) return < div style = { { textAlign : 'center' , padding : '60px 20px' , color : 'var(--text-tertiary)' , fontSize : 14 } } > { emptyMsg } < / div > ;
return < > { filtered . map ( e => { const s = new Date ( e . start _at ) ; const col = e . event _type ? . colour || '#9ca3af' ;
// Desktop: original pre-v0.9.64 sizes. Mobile: compact sizes from v0.9.64
const rowPad = isMobile ? '12px 14px' : '14px 20px' ;
const rowGap = isMobile ? 10 : 20 ;
const datW = isMobile ? 36 : 44 ; const datFs = isMobile ? 20 : 22 ; const datSFs = isMobile ? 10 : 11 ;
const timeW = isMobile ? 62 : 100 ; const timeGap = isMobile ? 5 : 8 ; const timeFs = isMobile ? 11 : 13 ;
const dotSz = isMobile ? 8 : 10 ;
const typeFs = isMobile ? 10 : 11 ; const titleGap = isMobile ? 6 : 8 ;
return ( < div key = { e . id } onClick = { ( ) => onSelect ( e ) } style = { { display : 'flex' , alignItems : 'center' , gap : rowGap , padding : rowPad , borderBottom : '1px solid var(--border)' , cursor : 'pointer' } } onMouseEnter = { el => el . currentTarget . style . background = 'var(--background)' } onMouseLeave = { el => el . currentTarget . style . background = '' } > < div style = { { width : datW , textAlign : 'center' , flexShrink : 0 } } > < div style = { { fontSize : datFs , fontWeight : 700 , lineHeight : 1 } } > { s . getDate ( ) } < / div > < div style = { { fontSize : datSFs , color : 'var(--text-tertiary)' , textTransform : 'uppercase' } } > { SHORT _MONTHS [ s . getMonth ( ) ] } , { DAYS [ s . getDay ( ) ] } < / div > < / div > < div style = { { width : timeW , flexShrink : 0 , display : 'flex' , alignItems : 'flex-start' , gap : timeGap , fontSize : timeFs , color : 'var(--text-secondary)' } } > < span style = { { width : dotSz , height : dotSz , borderRadius : '50%' , background : col , flexShrink : 0 , marginTop : 3 } } / > { e . all _day ? < span > All day < / span > : < span style = { { lineHeight : 1.5 } } > { fmtTime ( e . start _at ) } – < br / > { fmtTime ( e . end _at ) } < / span > } < / div > < div style = { { flex : 1 , minWidth : 0 } } > < div style = { { fontSize : 14 , fontWeight : 600 , display : 'flex' , alignItems : 'center' , gap : titleGap , flexWrap : 'nowrap' } } > { e . event _type ? . name && < span style = { { fontSize : typeFs , color : 'var(--text-tertiary)' , textTransform : 'uppercase' , letterSpacing : '0.5px' , fontWeight : 600 , flexShrink : 0 } } > { e . event _type . name } : < / span > } < span style = { { minWidth : 0 , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } > { e . title } < / span > { ! ! e . track _availability && (
e . my _response ? RESP _ICON [ e . my _response ] ( RESP _COLOR [ e . my _response ] ) : BELL _ICON
) } < / div > { e . location && < div style = { { fontSize : 12 , color : 'var(--text-tertiary)' , marginTop : 2 } } > { e . location } < / div > } < / div > < / div > ) ; } ) } < / > ;
return < > { filtered . map ( e => {
const s = new Date ( e . start _at ) ;
const end = new Date ( e . end _at ) ;
const isPast = ! e . all _day && end < now ; // event fully ended
const col = isPast ? '#9ca3af' : ( e . event _type ? . colour || '#9ca3af' ) ;
const textColor = isPast ? 'var(--text-tertiary)' : 'var(--text-primary)' ;
const subColor = isPast ? 'var(--text-tertiary)' : 'var(--text-secondary)' ;
// Use CSS media query breakpoint logic — compact below 640px regardless of isMobile prop
// so responsive desktop doesn't compact when there's plenty of room
const compact = isMobile ; // isMobile is only true on genuine mobile, not responsive desktop
const rowPad = compact ? '12px 14px' : '14px 20px' ;
const rowGap = compact ? 10 : 20 ;
const datW = compact ? 36 : 44 ; const datFs = compact ? 20 : 22 ; const datSFs = compact ? 10 : 11 ;
const timeW = compact ? 80 : 100 ; const timeGap = compact ? 5 : 8 ; const timeFs = compact ? 11 : 13 ;
const dotSz = compact ? 8 : 10 ;
const availIcon = ! ! e . track _availability && (
e . my _response
? RESP _ICON [ e . my _response ] ( isPast ? '#9ca3af' : RESP _COLOR [ e . my _response ] )
: isPast
? < svg xmlns = "http://www.w3.org/2000/svg" fill = "#d97706" viewBox = "0 0 24 24" strokeWidth = { 1.5 } stroke = "var(--warning-stroke)" width = { 15 } height = { 15 } style = { { flexShrink : 0 , opacity : 0.5 } } > < path strokeLinecap = "round" strokeLinejoin = "round" d = "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" / > < / svg >
: BELL _ICON
) ;
return (
< div key = { e . id } onClick = { ( ) => onSelect ( e ) } style = { { display : 'flex' , alignItems : 'center' , gap : rowGap , padding : rowPad , borderBottom : '1px solid var(--border)' , cursor : 'pointer' , opacity : isPast ? 0.7 : 1 } } onMouseEnter = { el => el . currentTarget . style . background = 'var(--background)' } onMouseLeave = { el => el . currentTarget . style . background = '' } >
{ /* Date column */ }
< div style = { { width : datW , textAlign : 'center' , flexShrink : 0 } } >
< div style = { { fontSize : datFs , fontWeight : 700 , lineHeight : 1 , color : textColor } } > { s . getDate ( ) } < / div >
< div style = { { fontSize : datSFs , color : 'var(--text-tertiary)' , textTransform : 'uppercase' } } > { SHORT _MONTHS [ s . getMonth ( ) ] } , { DAYS [ s . getDay ( ) ] } < / div >
< / div >
{ /* Time + dot column */ }
< div style = { { width : timeW , flexShrink : 0 , display : 'flex' , alignItems : 'flex-start' , gap : timeGap , fontSize : timeFs , color : subColor } } >
< span style = { { width : dotSz , height : dotSz , borderRadius : '50%' , background : col , flexShrink : 0 , marginTop : 3 } } / >
{ e . all _day ? < span > All day < / span > : < span style = { { lineHeight : 1.5 } } > { fmtTime ( e . start _at ) } – < br / > { fmtTime ( e . end _at ) } < / span > }
< / div >
{ /* Title + meta column */ }
< div style = { { flex : 1 , minWidth : 0 } } >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 8 , minWidth : 0 } } >
< span style = { { fontSize : 14 , fontWeight : 600 , color : textColor , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' , flex : 1 , minWidth : 0 } } > { e . title } < / span >
{ availIcon }
< / div >
{ ( e . event _type ? . name || e . location ) && (
< div style = { { fontSize : 12 , color : 'var(--text-tertiary)' , marginTop : 2 , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } >
{ e . event _type ? . name && < span style = { { textTransform : 'uppercase' , letterSpacing : '0.4px' , fontWeight : 600 , marginRight : e . location ? 6 : 0 } } > { e . event _type . name } { e . location ? ' ·' : '' } < / span > }
{ e . location && < span > { e . location } < / span > }
< / div >
) }
< / div >
< / div >
) ;
} ) } < / > ;
}
const HOUR _H = 52 ; // px per hour row