11import * as Common from "@frontend/common" ;
2- import { Box , Chip , CircularProgress , Divider , Stack , styled , Typography } from "@mui/material" ;
2+ import { Box , Chip , CircularProgress , Divider , Stack , styled , Table , TableBody , TableCell , TableRow , Typography } from "@mui/material" ;
33import { ErrorBoundary , Suspense } from "@suspensive/react" ;
4+ import { DateTime } from "luxon" ;
45import * as React from "react" ;
56import { Navigate , useParams } from "react-router-dom" ;
67
@@ -148,6 +149,31 @@ export const PresentationDetailPage: React.FC = ErrorBoundary.with(
148149 const speakersStr = language === "ko" ? "발표자" : "Speakers" ;
149150 // const slideShowStr = language === "ko" ? "발표 슬라이드" : "Presentation Slideshow";
150151
152+ const datetimeLabel = language === "ko" ? "발표 시각" : "Presentation Time" ;
153+ const datetimeSeparator = language === "ko" ? " ~ " : " - " ;
154+ const minText = language === "ko" ? "분" : "min." ;
155+
156+ // 동일 시간별로 모아서 보여줌. 단, 방은 콤마(,)로 join해서 보여줌
157+ const scheduleMap : Record < string , string [ ] > = presentation . room_schedules . reduce (
158+ ( acc , schedule ) => {
159+ const startAt = DateTime . fromISO ( schedule . start_at ) . setLocale ( language ) ;
160+ const endAt = DateTime . fromISO ( schedule . end_at ) . setLocale ( language ) ;
161+ if ( ! startAt . isValid || ! endAt . isValid ) return acc ; // 유효하지 않은 날짜는 무시
162+
163+ const duration = Number . parseInt ( endAt . diff ( startAt , [ "minutes" ] ) . minutes . toString ( ) ) ;
164+ const startAtFormatted = startAt . toLocaleString ( DateTime . DATETIME_MED ) ;
165+ // 동일 일자인 경우, 시간만 표시
166+ const endAtFormatted = endAt . toLocaleString ( startAt . hasSame ( endAt , "day" ) ? DateTime . TIME_SIMPLE : DateTime . DATETIME_MED ) ;
167+
168+ const key = `${ startAtFormatted } ${ datetimeSeparator } ${ endAtFormatted } (${ duration } ${ minText } )` ;
169+ const roomText = schedule . room_name . replace ( "\\n" , "\n" ) ;
170+ if ( ! acc [ key ] ) acc [ key ] = [ roomText ] ;
171+ else acc [ key ] . push ( roomText ) ;
172+ return acc ;
173+ } ,
174+ { } as Record < string , string [ ] >
175+ ) ;
176+
151177 React . useEffect ( ( ) => {
152178 setAppContext ( ( prev ) => ( {
153179 ...prev ,
@@ -159,28 +185,55 @@ export const PresentationDetailPage: React.FC = ErrorBoundary.with(
159185
160186 return (
161187 < PageLayout >
162- < Typography variant = "h4" fontWeight = "700" textAlign = "start" sx = { { width : "100%" , px : 2 , pt : 0 , pb : 1 } } children = { presentation . title } />
188+ < Typography
189+ variant = "h4"
190+ sx = { { width : "100%" , px : 2 , pt : 0 , pb : 1 , fontWeight : "700" , whiteSpace : "pre-wrap" } }
191+ children = { presentation . title . replace ( "\\n" , "\n" ) }
192+ />
163193 { presentation . summary && (
164194 < Typography
165195 variant = "subtitle1"
166196 sx = { { width : "100%" , px : 2 , pt : 1 , pb : 3 , fontWeight : "600" , whiteSpace : "pre-wrap" } }
167197 children = { presentation . summary }
168198 />
169199 ) }
170- < Divider flexItem />
171- { presentation . categories . length ? (
172- < >
173- < Stack direction = "row" alignItems = "center" justifyContent = "flex-start" sx = { { width : "100%" , gap : 1 , p : 2 } } >
174- < Typography variant = "subtitle1" fontWeight = "bold" children = { categoriesStr } />
175- < Stack direction = "row" spacing = { 1 } sx = { { width : "100%" } } >
176- { presentation . categories . map ( ( c ) => (
177- < Chip key = { c . id } size = "small" variant = "outlined" color = "primary" label = { c . name } />
178- ) ) }
179- </ Stack >
180- </ Stack >
181- < Divider flexItem />
182- </ >
183- ) : null }
200+ < Table sx = { { tableLayout : "auto" } } >
201+ < TableBody >
202+ { presentation . room_schedules . length
203+ ? Object . entries ( scheduleMap ) . map ( ( [ datetime , rooms ] , index ) => (
204+ < TableRow key = { datetime } >
205+ { index === 0 && (
206+ < TableCell rowSpan = { presentation . room_schedules . length } sx = { { width : "1%" , whiteSpace : "nowrap" } } >
207+ < Typography variant = "subtitle1" fontWeight = "bold" children = { datetimeLabel } />
208+ </ TableCell >
209+ ) }
210+ < TableCell >
211+ < Stack direction = "row" justifyContent = "flex-start" alignItems = "center" sx = { { width : "100%" , flexWrap : "wrap" , gap : 1 } } >
212+ < Typography variant = "subtitle1" children = { datetime } />
213+ < Stack direction = "row" sx = { { flexGrow : 0 , gap : 1 , flexWrap : "wrap" } } >
214+ { rooms . map ( ( room , index ) => (
215+ < Chip key = { index } sx = { { whiteSpace : "pre-wrap" } } label = { room . replace ( "\\n" , "\n" ) } />
216+ ) ) }
217+ </ Stack >
218+ </ Stack >
219+ </ TableCell >
220+ </ TableRow >
221+ ) )
222+ : null }
223+ { presentation . categories . length ? (
224+ < TableRow >
225+ < TableCell children = { < Typography variant = "subtitle1" fontWeight = "bold" children = { categoriesStr } /> } />
226+ < TableCell >
227+ < Stack direction = "row" spacing = { 1 } sx = { { width : "100%" } } >
228+ { presentation . categories . map ( ( c ) => (
229+ < Chip key = { c . id } size = "small" variant = "outlined" color = "primary" label = { c . name } />
230+ ) ) }
231+ </ Stack >
232+ </ TableCell >
233+ </ TableRow >
234+ ) : null }
235+ </ TableBody >
236+ </ Table >
184237 { /* {presentation.slideshow_url && (
185238 <>
186239 <Typography variant="subtitle1" fontWeight="bold" sx={{ width: "100%", p: 2, a: { color: "blue" } }}>
0 commit comments