11import { useMemo } from "react"
22import type { Dayjs } from "dayjs"
3- import { type DateType , DateUtils , type TimeType } from "../utils"
3+ import { twMerge } from "tailwind-merge"
4+ import type { TimeType } from "../utils"
45
56export interface EventObject {
67 key : string
8+ title ?: string
9+ subtitle ?: string
710 startDate : Dayjs | undefined
811 endDate : Dayjs | undefined
912}
@@ -20,24 +23,29 @@ export interface EventListProps<T extends EventObject> {
2023 maxEndTime : Dayjs
2124 dayStart : TimeType
2225 dayEnd : TimeType
23- renderEvent : (
26+ renderEvent ? : (
2427 event : T ,
2528 startDate ?: Dayjs ,
2629 endDate ?: Dayjs ,
2730 ) => React . JSX . Element
28- renderTimeHeader ?: ( dateString : string ) => React . JSX . Element
31+ renderTimeHeader ?: ( date : Dayjs ) => React . JSX . Element
32+ className ?: string
33+ style ?: React . CSSProperties
2934}
3035
3136function useOrderByDateBookings < T extends EventObject > (
3237 items : T [ ] ,
33- minStartTime : Dayjs ,
38+ _minStartTime : Dayjs ,
3439 maxEndTime : Dayjs ,
3540 dayStart : TimeType ,
3641 dayEnd : TimeType ,
3742) {
3843 return useMemo ( ( ) => {
3944 const dayStartTime = dayStart . split ( ":" ) . map ( Number )
4045 const dayEndTime = dayEnd . split ( ":" ) . map ( Number )
46+ const minStartTime = _minStartTime
47+ . set ( "hour" , dayStartTime [ 0 ] )
48+ . set ( "minute" , dayStartTime [ 1 ] )
4149
4250 const withinTimeRange = ( items as T [ ] ) . filter ( ( it ) => {
4351 if ( ! it . endDate ) return true
@@ -53,26 +61,34 @@ function useOrderByDateBookings<T extends EventObject>(
5361 )
5462 } )
5563
56- const datesMap : {
57- [ date : DateType ] : EventWrapper < T > [ ]
58- } = { }
64+ const datesMap : Map < Dayjs , EventWrapper < T > [ ] > = new Map ( )
65+ let currentStartDate = minStartTime
5966 for ( const it of sortedItems ) {
6067 let startDate = it . startDate ?? minStartTime
6168 const endDate = it . endDate ?? maxEndTime
69+ if ( startDate . isBefore ( minStartTime ) ) {
70+ startDate = minStartTime
71+ }
6272
6373 const timelineStartDay = startDate
6474 . hour ( dayStartTime [ 0 ] )
6575 . minute ( dayStartTime [ 1 ] )
6676
6777 while ( startDate . isBefore ( endDate ) ) {
68- const dt = DateUtils . toDateType ( startDate )
78+ //const dt = DateUtils.toDateType(startDate)
79+ const startOfDay = startDate
80+ . hour ( dayStartTime [ 0 ] )
81+ . minute ( dayStartTime [ 1 ] )
82+ if ( ! startOfDay . isSame ( currentStartDate ) ) {
83+ currentStartDate = startDate . add ( 1 , "day" )
84+ }
6985 const bookingOfThisDay = {
7086 booking : it ,
7187 renderStartDate : startDate ,
7288 renderEndDate : endDate ,
7389 }
7490
75- if ( it . startDate ?. isBefore ( timelineStartDay ) === true ) {
91+ if ( it . startDate ?. isBefore ( timelineStartDay ) ) {
7692 bookingOfThisDay . renderStartDate = startDate
7793 . hour ( dayStartTime [ 0 ] )
7894 . minute ( dayStartTime [ 1 ] )
@@ -88,12 +104,15 @@ function useOrderByDateBookings<T extends EventObject>(
88104 if ( ! it . endDate || currEndDate . isBefore ( it . endDate ) ) {
89105 bookingOfThisDay . renderEndDate = currEndDate
90106 }
91- datesMap [ dt ] = [ ...( datesMap [ dt ] ?? [ ] ) , bookingOfThisDay ]
107+ datesMap . set ( currentStartDate , [
108+ ...( datesMap . get ( currentStartDate ) ?? [ ] ) ,
109+ bookingOfThisDay ,
110+ ] )
92111 startDate = startDate . add ( 1 , "day" )
93112 }
94113 }
95114 return datesMap
96- } , [ items , dayEnd , dayStart , maxEndTime , minStartTime ] )
115+ } , [ items , dayEnd , dayStart , maxEndTime , _minStartTime ] )
97116}
98117
99118const dateFormat = Intl . DateTimeFormat ( undefined , {
@@ -103,47 +122,82 @@ const dateFormat = Intl.DateTimeFormat(undefined, {
103122 year : "numeric" ,
104123} )
105124
125+ function defaultRenderEvent < T extends EventObject > (
126+ booking : T ,
127+ startDate : Dayjs | undefined ,
128+ endDate : Dayjs | undefined ,
129+ ) {
130+ return (
131+ < div
132+ data-id = { booking . key }
133+ className = "flex justify-between py-1 cursor-pointer border-solid border-l-8 border-l-border-bold overflow-hidden bg-surface-sunken"
134+ >
135+ < div className = "flex pl-2.5 flex-col overflow-hidden" >
136+ < div className = "text-text-subtle text-xl flex-0 truncate" >
137+ < span > { booking . title ?? "no title" } </ span >
138+ </ div >
139+ { booking . subtitle && (
140+ < div className = "text-text-subtle text-sm flex-0 truncate" >
141+ < span > { booking . subtitle } </ span >
142+ </ div >
143+ ) }
144+ < div className = "text-text" >
145+ { startDate ?. format ( "HH:mm" ) } - { endDate ?. format ( "HH:mm" ) }
146+ </ div >
147+ </ div >
148+ </ div >
149+ )
150+ }
151+
106152export function EventList < T extends EventObject > ( {
107153 items,
108- renderEvent,
154+ renderEvent = defaultRenderEvent ,
109155 renderTimeHeader,
110156 minStartTime,
111157 maxEndTime,
112158 dayStart,
113159 dayEnd,
160+ className,
161+ style,
114162} : EventListProps < T > ) {
115- const datesMap : { [ p : DateType ] : EventWrapper < T > [ ] } =
116- useOrderByDateBookings (
117- items ,
118- minStartTime ,
119- maxEndTime ,
120- dayStart || "00:00" ,
121- dayEnd || "00:00" ,
122- )
163+ const datesMap : Map < Dayjs , EventWrapper < T > [ ] > = useOrderByDateBookings (
164+ items ,
165+ minStartTime ,
166+ maxEndTime ,
167+ dayStart || "00:00" ,
168+ dayEnd || "00:00" ,
169+ )
123170
124- return (
125- < div className = "min-h-0 overflow-auto" >
126- { Object . entries ( datesMap ) . map ( ( [ date , eventObjects ] ) => {
127- const dateStr = dateFormat . format (
128- DateUtils . dateFromString ( date , true ) ,
129- )
130- return (
131- < div key = { date } >
171+ const content = useMemo ( ( ) => {
172+ const content : JSX . Element [ ] = [ ]
173+ for ( const [ date , eventObjects ] of datesMap ) {
174+ const dateStr = dateFormat . format ( date . toDate ( ) )
175+ content . push (
176+ < div key = { dateStr } className = "mt-4 first:mt-0" >
177+ < div className = "text-text-subtle text-sm flex items-center font-bold mt-4" >
132178 { renderTimeHeader ? renderTimeHeader ( date ) : dateStr }
133- < div className = "flex flex-1 flex-col gap-1" >
134- { eventObjects . map (
135- ( eventObject : EventWrapper < T > ) => {
136- return renderEvent (
137- eventObject . booking ,
138- eventObject . renderStartDate ,
139- eventObject . renderEndDate ,
140- )
141- } ,
142- ) }
143- </ div >
144179 </ div >
145- )
146- } ) }
180+ < div className = "flex flex-1 flex-col gap-1" >
181+ { eventObjects . map ( ( eventObject : EventWrapper < T > ) => {
182+ return renderEvent (
183+ eventObject . booking ,
184+ eventObject . renderStartDate ,
185+ eventObject . renderEndDate ,
186+ )
187+ } ) }
188+ </ div >
189+ </ div > ,
190+ )
191+ }
192+ return content
193+ } , [ datesMap , renderTimeHeader , renderEvent ] )
194+
195+ return (
196+ < div
197+ className = { twMerge ( "min-h-0 overflow-auto" , className ) }
198+ style = { style }
199+ >
200+ { content }
147201 </ div >
148202 )
149203}
0 commit comments