Skip to content

Commit 0fdd6b4

Browse files
committed
feat: session list component 1차 작업
1 parent f27e4cf commit 0fdd6b4

File tree

5 files changed

+303
-0
lines changed

5 files changed

+303
-0
lines changed

apps/pyconkr/src/components/Session/detail.tsx

Whitespace-only changes.
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
import styled from "@emotion/styled";
2+
import * as Common from "@frontend/common";
3+
import { CircularProgress } from "@mui/material";
4+
import { ErrorBoundary, Suspense } from "@suspensive/react";
5+
import React, { useState } from "react";
6+
import { useNavigate } from "react-router-dom";
7+
import * as R from "remeda";
8+
9+
import SessionAPIs from "../../../../../packages/common/src/apis/session";
10+
import BackendSessionAPISchemas from "../../../../../packages/common/src/schemas/backendSessionAPI";
11+
12+
const SessionItem: React.FC<{ session: BackendSessionAPISchemas.SessionSchema }> = ({ session }) => {
13+
const navigate = useNavigate();
14+
15+
const speakerImageSrc =
16+
session.presentationSpeaker[0].image ||
17+
(R.isArray(session.presentationSpeaker[0].image) && !R.isEmpty(session.presentationSpeaker[0].image)) ||
18+
"";
19+
const urlSafeTitle = session.name
20+
.replace(/ /g, "-")
21+
.replace(/([.])/g, "_")
22+
.replace(/(?![0-9A-Za-z---_])./g, "");
23+
24+
return (
25+
<SessionItemEl>
26+
<SessionItemImgContainer>
27+
<ErrorBoundary
28+
fallback={
29+
<Common.Components.CenteredPage>문제가 발생했습니다, 새로고침을 해주세요.</Common.Components.CenteredPage>
30+
}
31+
>
32+
<Suspense
33+
fallback={
34+
<Common.Components.CenteredPage>
35+
<CircularProgress />
36+
</Common.Components.CenteredPage>
37+
}
38+
>
39+
{speakerImageSrc}
40+
</Suspense>
41+
</ErrorBoundary>
42+
{<Common.Components.CenteredPage>문제가 발생했습니다, 새로고침을 해주세요.</Common.Components.CenteredPage>}
43+
</SessionItemImgContainer>
44+
<SessionItemInfoContainer>
45+
<h4 onClick={() => navigate(`/session/${session.presentationType.id}#${urlSafeTitle}`)}>{session.name}</h4>
46+
<p>{session.name}</p>
47+
<SessionSpeakerContainer>
48+
by{" "}
49+
{session.presentationSpeaker.map((speaker) => (
50+
<kbd key={speaker.id}>{speaker.name}</kbd>
51+
))}
52+
</SessionSpeakerContainer>
53+
<TagContainer>
54+
{session.presentationCategories.map((tag) => (
55+
<Tag key={tag.id}>{tag.name}</Tag>
56+
))}
57+
{session.doNotRecord && <Tag>{"녹화 불가"}</Tag>}
58+
</TagContainer>
59+
</SessionItemInfoContainer>
60+
</SessionItemEl>
61+
);
62+
};
63+
64+
const backendAdminAPIClient = Common.Hooks.BackendAdminAPI.useBackendAdminClient();
65+
66+
const SessionListInner = async () => {
67+
const data = await (async () => {
68+
return await SessionAPIs.sessionList(backendAdminAPIClient)();
69+
})();
70+
const [currentTag, setTag] = useState<string | null>(null);
71+
const setOrUnsetTag = (tag: string) => setTag(currentTag === tag ? null : tag);
72+
const currentTagNames = data.map((d) => d.presentationType.name);
73+
const sessionOnlyData = data
74+
.filter((d) => d.presentationType.name === "Session")
75+
.filter((d) => currentTag === null || currentTagNames.includes(d.presentationType.name));
76+
const tags = Array.from(new Set(data.flatMap((session) => session.presentationType.name))).sort();
77+
return (
78+
<>
79+
<hr style={{ margin: 0 }} />
80+
<TagFilterBtnContainer>
81+
<div>
82+
{tags.map((tag) => (
83+
<TagFilterBtn key={tag} onClick={() => setOrUnsetTag(tag)} className={tag === currentTag ? "selected" : ""}>
84+
{tag}
85+
</TagFilterBtn>
86+
))}
87+
</div>
88+
</TagFilterBtnContainer>
89+
{sessionOnlyData.map((session) => (
90+
<SessionItem key={session.presentationType.id} session={session} />
91+
))}
92+
</>
93+
);
94+
};
95+
96+
export const SessionListPage = () => {
97+
const SessionList = () => {
98+
return (
99+
<ErrorBoundary fallback={<h4>{"세션 목록을 불러오는 중 에러가 발생했습니다."}</h4>}>
100+
<Suspense fallback={<h4>{"세션 목록을 불러오는 중 입니다."}</h4>}>
101+
<SessionListInner />
102+
</Suspense>
103+
</ErrorBoundary>
104+
);
105+
};
106+
107+
return <SessionList />;
108+
};
109+
110+
const SessionItemEl = styled.div`
111+
display: flex;
112+
align-items: center;
113+
justify-content: flex-start;
114+
padding: 0.75rem;
115+
gap: 1rem;
116+
117+
color: var(--pico-h6-color);
118+
119+
border-top: 1px solid var(--pico-muted-border-color);
120+
border-bottom: 1px solid var(--pico-muted-border-color);
121+
122+
@media only screen and (max-width: 809px) {
123+
padding: 0rem;
124+
gap: 0.5rem;
125+
}
126+
`;
127+
128+
const SessionItemImgContainer = styled.div`
129+
width: 6rem;
130+
height: 6rem;
131+
margin: 0.6rem;
132+
flex-shrink: 0;
133+
flex-grow: 0;
134+
135+
border-radius: 50%;
136+
border: 1px solid var(--pico-muted-border-color);
137+
138+
* {
139+
width: 100%;
140+
height: 100%;
141+
min-width: 100%;
142+
min-height: 100%;
143+
max-width: 100%;
144+
max-height: 100%;
145+
border-radius: 50%;
146+
}
147+
148+
@media only screen and (max-width: 809px) {
149+
width: 5rem;
150+
height: 5rem;
151+
margin: 0.25rem;
152+
}
153+
`;
154+
155+
const SessionItemInfoContainer = styled.div`
156+
padding-top: 0.5rem;
157+
padding-bottom: 0.5rem;
158+
159+
flex-grow: 1;
160+
161+
h4 {
162+
color: #febd99;
163+
margin-bottom: 0.2rem;
164+
cursor: pointer;
165+
}
166+
167+
p {
168+
margin-bottom: 0.3rem;
169+
color: var(--pico-h3-color);
170+
font-size: 0.8rem;
171+
font-weight: bold;
172+
}
173+
174+
@media only screen and (max-width: 809px) {
175+
h3 {
176+
font-size: 1.5rem;
177+
}
178+
179+
p {
180+
font-size: 0.8rem;
181+
font-weight: bold;
182+
}
183+
}
184+
`;
185+
186+
const SessionSpeakerContainer = styled.div`
187+
display: flex;
188+
align-items: center;
189+
justify-content: flex-start;
190+
gap: 0.5rem;
191+
color: var(--pico-h6-color);
192+
font-size: 0.6rem;
193+
194+
img {
195+
width: 0.75rem;
196+
height: 0.75rem;
197+
min-width: 0.75rem;
198+
min-height: 0.75rem;
199+
max-width: 0.75rem;
200+
max-height: 0.75rem;
201+
border-radius: 50%;
202+
background-color: #f0f0f0;
203+
}
204+
205+
kbd {
206+
background-color: #def080;
207+
padding: 0.2rem 0.4rem;
208+
border-radius: 0.25rem;
209+
210+
font-size: 0.6rem;
211+
}
212+
`;
213+
214+
const TagContainer = styled.div`
215+
display: flex;
216+
align-items: center;
217+
justify-content: flex-start;
218+
padding: 0.25rem 0;
219+
gap: 0.25rem;
220+
`;
221+
222+
const Tag = styled.kbd`
223+
background-color: #b0a8fe;
224+
padding: 0.2rem 0.4rem;
225+
border-radius: 0.25rem;
226+
227+
font-size: 0.6rem;
228+
`;
229+
230+
const TagFilterBtnContainer = styled.div`
231+
display: flex;
232+
align-items: center;
233+
justify-content: center;
234+
`;
235+
236+
const TagFilterBtn = styled.button`
237+
background-color: rgba(0, 0, 0, 0);
238+
border: none;
239+
outline: none;
240+
padding: 0.25rem 0.5rem;
241+
margin: 0.25rem;
242+
font-size: 0.8rem;
243+
244+
&:focus,
245+
button::-moz-focus-inner {
246+
outline: none !important;
247+
}
248+
249+
&.selected {
250+
background-color: #b0a8fe;
251+
color: black;
252+
font-weight: bold;
253+
}
254+
`;

apps/pyconkr/src/components/Session/timetable.tsx

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { BackendAPIClient } from "./client";
2+
import BackendSessionAPISchemas from "../schemas/backendSessionAPI";
3+
4+
namespace SessionAPIs {
5+
export const sessionList = (client: BackendAPIClient) => async () => {
6+
return await client.get<BackendSessionAPISchemas.SessionSchema[]>("v1/event/presentation/");
7+
};
8+
9+
export const sessionFilteredList = (client: BackendAPIClient, categoryName: string) => async () => {
10+
return await client.get<BackendSessionAPISchemas.SessionSchema[]>(
11+
`v1/event/presentation/?category=${categoryName}`
12+
);
13+
};
14+
}
15+
16+
export default SessionAPIs;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace BackendSessionAPISchemas {
2+
export type SessionTypeSchema = {
3+
id: string;
4+
event: string;
5+
name: string;
6+
};
7+
8+
export type SessionCategorySchema = {
9+
id: string;
10+
presentationType: string;
11+
name: string;
12+
};
13+
14+
export type SessionSpeakerSchema = {
15+
id: string;
16+
presentation: string;
17+
user: string;
18+
name: string;
19+
biography: string;
20+
image: string; // DB 반영 필요
21+
};
22+
23+
export type SessionSchema = {
24+
id: string;
25+
name: string; // DB 반영 필요
26+
doNotRecord: boolean; // DB 반영 필요
27+
presentationType: SessionTypeSchema;
28+
presentationCategories: SessionCategorySchema[];
29+
presentationSpeaker: SessionSpeakerSchema[];
30+
};
31+
}
32+
33+
export default BackendSessionAPISchemas;

0 commit comments

Comments
 (0)