@@ -12,12 +12,15 @@ import {
1212 DialogContent ,
1313 DialogTitle ,
1414 FormControl ,
15+ FormControlLabel ,
1516 InputLabel ,
1617 MenuItem ,
1718 Select ,
19+ Switch ,
1820 TextField ,
1921 Typography
2022} from '@mui/material' ;
23+ import { rrulestr } from 'rrule' ;
2124import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker' ;
2225import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider' ;
2326import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment' ;
@@ -59,6 +62,7 @@ import {
5962 repeatWeeklyLabel ,
6063 repeatCountLabel ,
6164 repeatsLabel ,
65+ showPastMeetingsLabel ,
6266 scheduleMeetingLabel ,
6367 selectButtonLabel ,
6468 startsAtLabel ,
@@ -104,6 +108,27 @@ const buildRrule = (mode: RepeatMode, interval: number, count: number): string |
104108 return `FREQ=${ mode } ;INTERVAL=${ Math . max ( 1 , interval ) } ;COUNT=${ Math . max ( 1 , count ) } ` ;
105109} ;
106110
111+ const isMeetingPast = ( m : Meeting , now : number ) : boolean => {
112+ const startsAt = Number ( m . startsAt ) ;
113+ const endsAt = Number ( m . endsAt ) ;
114+ const duration = endsAt - startsAt ;
115+
116+ if ( ! m . rrule ) return endsAt < now ;
117+ try {
118+ const rule = rrulestr ( m . rrule , { dtstart : new Date ( startsAt ) } ) ;
119+ const nextStart = rule . after ( new Date ( now ) , true ) ;
120+
121+ if ( nextStart ) return false ;
122+ const lastStart = rule . before ( new Date ( now ) , true ) ;
123+
124+ if ( ! lastStart ) return true ;
125+
126+ return lastStart . getTime ( ) + duration < now ;
127+ } catch {
128+ return endsAt < now ;
129+ }
130+ } ;
131+
107132const parseRrule = ( rrule ?: string ) : { mode : RepeatMode , interval : number , count : number } => {
108133 if ( ! rrule ) return { mode : 'NEVER' , interval : 1 , count : 10 } ;
109134 const parts : Record < string , string > = { } ;
@@ -155,6 +180,7 @@ const RoomMeetingsTable = (props: RoomProp) => {
155180 const [ attendees , setAttendees ] = useState < MeetingAttendee [ ] > ( [ ] ) ;
156181 const [ occurrenceRsvps , setOccurrenceRsvps ] = useState < MeetingOccurrenceRsvp [ ] > ( [ ] ) ;
157182 const [ attendeesWithExceptions , setAttendeesWithExceptions ] = useState < Set < number > > ( new Set ( ) ) ;
183+ const [ showPastMeetings , setShowPastMeetings ] = useState ( false ) ;
158184 const [ attendeeInput , setAttendeeInput ] = useState < User | string | null > ( null ) ;
159185 const [ resolveEmail , setResolveEmail ] = useState ( '' ) ;
160186 const [ isResolving , setIsResolving ] = useState ( false ) ;
@@ -456,17 +482,30 @@ const RoomMeetingsTable = (props: RoomProp) => {
456482 }
457483 } ;
458484
485+ const visibleData = useMemo ( ( ) => {
486+ if ( showPastMeetings ) return data ;
487+ const now = Date . now ( ) ;
488+
489+ return data . filter ( ( m ) => ! isMeetingPast ( m , now ) ) ;
490+ } , [ data , showPastMeetings ] ) ;
491+
459492 return (
460493 < LocalizationProvider dateAdapter = { AdapterMoment } adapterLocale = { momentLocale } >
461494 < Box sx = { { mt : 2 } } >
462495 < Typography variant = "h6" > { meetingsLabel ( ) } </ Typography >
463- < Button variant = "outlined" onClick = { handleOpenAdd } sx = { { mt : 1 , mb : 1 } } >
464- { addNewLabel ( ) }
465- </ Button >
496+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 2 , mt : 1 , mb : 1 } } >
497+ < Button variant = "outlined" onClick = { handleOpenAdd } >
498+ { addNewLabel ( ) }
499+ </ Button >
500+ < FormControlLabel
501+ control = { < Switch checked = { showPastMeetings } onChange = { ( e ) => setShowPastMeetings ( e . target . checked ) } /> }
502+ label = { showPastMeetingsLabel ( ) }
503+ />
504+ </ Box >
466505 < MaterialReactTable
467506 localization = { localization }
468507 columns = { columns }
469- data = { data }
508+ data = { visibleData }
470509 state = { { isLoading } }
471510 initialState = { {
472511 columnVisibility : { id : false } ,
@@ -498,25 +537,25 @@ const RoomMeetingsTable = (props: RoomProp) => {
498537 value = { description }
499538 onChange = { ( e ) => setDescription ( e . target . value ) }
500539 />
501- < Box sx = { { display : 'flex' , gap : 2 , mt : 1 } } >
540+ < Box sx = { { display : 'flex' , gap : 2 , mt : 1 , flexWrap : 'wrap' } } >
502541 < DateTimePicker
503542 label = { startsAtLabel ( ) }
504543 value = { startsAt }
505544 onChange = { ( v ) => setStartsAt ( v ) }
506545 ampm = { false }
507- sx = { { flex : 1 } }
546+ sx = { { flex : '1 1 240px' } }
508547 />
509548 < DateTimePicker
510549 label = { endsAtLabel ( ) }
511550 value = { endsAt }
512551 onChange = { ( v ) => setEndsAt ( v ) }
513552 ampm = { false }
514- sx = { { flex : 1 } }
553+ sx = { { flex : '1 1 240px' } }
515554 />
516555 </ Box >
517- < Box sx = { { display : 'flex' , gap : 2 , mt : 2 } } >
556+ < Box sx = { { display : 'flex' , gap : 2 , mt : 2 , flexWrap : 'wrap' } } >
518557 < Autocomplete
519- sx = { { flex : 1 } }
558+ sx = { { flex : '1 1 240px' } }
520559 options = { timezoneOptions }
521560 value = { timezoneOptions . find ( ( o ) => o . value === timezone ) ?? undefined }
522561 onChange = { ( _e , v ) => { if ( v ) setTimezone ( v . value ) ; } }
@@ -525,7 +564,7 @@ const RoomMeetingsTable = (props: RoomProp) => {
525564 disableClearable
526565 renderInput = { ( params ) => < TextField { ...params } label = { timezoneLabel ( ) } /> }
527566 />
528- < FormControl sx = { { flex : 1 } } >
567+ < FormControl sx = { { flex : '1 1 240px' } } >
529568 < InputLabel id = "meeting-locale-label" > { inviteLanguageLabel ( ) } </ InputLabel >
530569 < Select
531570 labelId = "meeting-locale-label"
@@ -539,8 +578,8 @@ const RoomMeetingsTable = (props: RoomProp) => {
539578 </ Select >
540579 </ FormControl >
541580 </ Box >
542- < Box sx = { { display : 'flex' , gap : 2 , mt : 2 } } >
543- < FormControl sx = { { flex : 1 } } >
581+ < Box sx = { { display : 'flex' , gap : 2 , mt : 2 , flexWrap : 'wrap' } } >
582+ < FormControl sx = { { flex : '2 1 200px' } } >
544583 < InputLabel id = "meeting-repeat-label" > { repeatsLabel ( ) } </ InputLabel >
545584 < Select
546585 labelId = "meeting-repeat-label"
@@ -560,15 +599,15 @@ const RoomMeetingsTable = (props: RoomProp) => {
560599 value = { repeatInterval }
561600 onChange = { ( e ) => setRepeatInterval ( parseInt ( e . target . value , 10 ) || 1 ) }
562601 disabled = { repeatMode === 'NEVER' }
563- sx = { { width : 120 } }
602+ sx = { { flex : '1 1 120px' , minWidth : 120 } }
564603 />
565604 < TextField
566605 label = { repeatCountLabel ( ) }
567606 type = "number"
568607 value = { repeatCount }
569608 onChange = { ( e ) => setRepeatCount ( parseInt ( e . target . value , 10 ) || 1 ) }
570609 disabled = { repeatMode === 'NEVER' }
571- sx = { { width : 180 } }
610+ sx = { { flex : '1 1 160px' , minWidth : 140 } }
572611 />
573612 </ Box >
574613 < Box sx = { { mt : 3 } } >
0 commit comments