import classNames from 'classnames' ; import type { Dayjs } from 'dayjs' ; import { Fragment , memo , useMemo } from 'react' ; import { inRange } from '../../../utils/inRange' ; import { isOverlapping } from '../../../utils/overlap' ; import type { OrderedSchedule , Schedule , Segment } from './models' ; import { Spell } from './Spell' ; import { TableData } from './TableData' ; import { useTimetable } from './useTimetable' ; export interface ScheduleListProps { readonly schedules : readonly Schedule [ ] ; } const orderSchedule = ( schedules , ordered = [ ] , row = 0 ) : OrderedSchedule [ ] => { if ( schedules . length === 0 ) return ordered . concat ( ) ; const uniques = schedules . reduce < readonly OrderedSchedule [ ] > ( ( xs , a ) => { const overlapped = xs . some ( ( b ) => { return isOverlapping ( [ a . startAt , a . endAt ] , [ b . schedule . startAt , b . schedule . endAt ] ) ; } ) ; if ( overlapped ) return xs ; return xs . concat ( { schedule : a , row } ) ; } , [ ] ) ; const dupes = schedules . filter ( ( v ) => ! uniques . map ( ( o ) => o . schedule ) . includes ( v ) ) ; return orderSchedule ( dupes , ordered . concat ( uniques ) , row + 1 ) ; } ; const createDateSequence = ( startAt , endAt , interval ) : readonly Readonly < Dayjs > [ ] => { const length = endAt . diff ( startAt , 'minute' ) / interval + 1 ; return Array . from ( { length } , ( _ , i ) => { const basis = startAt . clone ( ) ; return basis . add ( i * interval , 'minute' ) ; } ) ; } ; const chunkByInterval = ( schedules , interval ) : Segment [ ] => { const dates = schedules . length !== 0 ? createDateSequence ( schedules [ 0 ] . schedule . startAt , schedules [ schedules . length - 1 ] . schedule . endAt , interval ) : [ ] ; return dates . flatMap ( ( date ) => { return { date : date , schedules : schedules . filter ( ( schedule ) => inRange ( date , date . clone ( ) . add ( interval , 'minute' ) , schedule . schedule . startAt ) ) , } ; } ) as Segment [ ] ; } ; interface EmptyListProps { readonly date : Readonly < Dayjs > ; } const EmptyList = ( props ) : JSX . Element => { const { date } = props ; const { getItemX } = useTimetable ( ) ; const style = { transform : `translateX(${ getItemX ( date ) }px)` , } ; return ( < div className = { classNames ( 'absolute' , 'top-0' , 'left-0' ) } style = { style } > < p className = { classNames ( 'sr-only' ) } > 配信予定はありません < /p> < /div>); } ; interface TableDataListProps { readonly segment : readonly OrderedSchedule [ ] ; } const TableDataList = ( props ) : JSX . Element => { const { segment } = props ; const herald = segment [ 0 ] . schedule . startAt ; const { getWidth , getItemX , getItemY } = useTimetable ( ) ; return ( < ul className = { classNames ( 'absolute' , 'top-0' , 'left-0' , 'z-50' , 'mt-14' ) } style = { { transform : `translateX(${ getItemX ( herald ) }px)` , } } > { segment . map ( ( { schedule , row } , i ) => ( < li key = { `item-${ i }-${ row }` } data - row = { row } className = { classNames ( 'absolute' , 'top-0' , 'left-0' ) } style = { { transform : `translate(
              ${ getWidth ( schedule . startAt . diff ( herald , 'millisecond' ) ) }px,
              ${ getItemY ( row ) }px
            )` , } } > < TableData schedule = { schedule } /> < /li>))} < /ul>); } ; const ScheduleListPure = ( props ) : JSX . Element => { const { schedules } = props ; const { interval } = useTimetable ( ) ; const segments = useMemo ( ( ) => { const ordered = orderSchedule ( schedules ) . sort ( ( a , b ) => a . schedule . startAt . diff ( b . schedule . startAt , 'millisecond' ) ) ; return chunkByInterval ( ordered , interval ) ; } , [ schedules , interval ] ) ; const largestSegmentSize = useMemo ( ( ) => segments . flatMap ( ( segment ) => segment . schedules ) . reduce ( ( last , schedule ) => Math . max ( schedule . row , last ) , 0 ) + 1 , [ segments ] ) ; return ( < > { segments . map ( ( segment , i ) => ( < Fragment key = { `segment-${ i }` } > < Spell key = { segment . date . toISOString ( ) } date = { segment . date } size = { largestSegmentSize } / > { segment . schedules . length > 0 ? ( < TableDataList segment = { segment . schedules } / > ) : ( < EmptyList key = { `message-${ i }` } date = { segment . date } / > ) } < /Fragment>))} < />); } ; export const ScheduleList = memo ( ScheduleListPure ) ;