Skip to content

Commit 035eaad

Browse files
committed
feat: 발표 상세에 시간 데이터 추가
1 parent 7c3b7ac commit 035eaad

File tree

1 file changed

+69
-16
lines changed

1 file changed

+69
-16
lines changed

apps/pyconkr/src/components/pages/presentation_detail.tsx

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * 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";
33
import { ErrorBoundary, Suspense } from "@suspensive/react";
4+
import { DateTime } from "luxon";
45
import * as React from "react";
56
import { 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

Comments
 (0)