|
| 1 | +import styled from "@emotion/styled"; |
| 2 | +import { Button, CircularProgress, Divider } from "@mui/material"; |
| 3 | +import { styled as muiStyled } from "@mui/material/styles"; |
| 4 | +import { Suspense } from "@suspensive/react"; |
| 5 | +import React, { useEffect, useState } from "react"; |
| 6 | +import { useNavigate } from "react-router-dom"; |
| 7 | +import * as R from "remeda"; |
| 8 | + |
| 9 | +import BackendSessionAPISchemas from "../../../../../packages/common/src/schemas/backendSessionAPI"; |
| 10 | + |
| 11 | +const SessionItem: React.FC<{ session: BackendSessionAPISchemas.SessionSchema }> = Suspense.with( |
| 12 | + { fallback: <CircularProgress /> }, |
| 13 | + ({ session }) => { |
| 14 | + const navigate = useNavigate(); |
| 15 | + |
| 16 | + const speakerImageSrc = |
| 17 | + session.presentationSpeaker[0].image || |
| 18 | + (R.isArray(session.presentationSpeaker[0].image) && !R.isEmpty(session.presentationSpeaker[0].image)) || |
| 19 | + ""; |
| 20 | + const urlSafeTitle = session.name |
| 21 | + .replace(/ /g, "-") |
| 22 | + .replace(/([.])/g, "_") |
| 23 | + .replace(/(?![0-9A-Za-zㄱ-ㅣ가-힣-_])./g, ""); |
| 24 | + |
| 25 | + return ( |
| 26 | + <> |
| 27 | + <SessionItemEl> |
| 28 | + <SessionItemImgContainer> |
| 29 | + {speakerImageSrc && ( |
| 30 | + <img src={session.presentationSpeaker[0].image} alt={session.presentationSpeaker[0].name} /> |
| 31 | + )} |
| 32 | + </SessionItemImgContainer> |
| 33 | + <SessionItemInfoContainer> |
| 34 | + <TagContainer> |
| 35 | + {session.presentationCategories.map((tag) => ( |
| 36 | + <Tag key={tag.id}>{tag.name}</Tag> |
| 37 | + ))} |
| 38 | + {session.doNotRecord && <Tag>{"녹화 불가"}</Tag>} |
| 39 | + </TagContainer> |
| 40 | + <SessionTitleContainer> |
| 41 | + <kbd onClick={() => navigate(`/session/${session.id}#${urlSafeTitle}`)}>{session.name}</kbd> |
| 42 | + </SessionTitleContainer> |
| 43 | + <SessionSpeakerContainer> |
| 44 | + {session.presentationSpeaker.map((speaker) => ( |
| 45 | + <kbd key={speaker.id}>{speaker.name}</kbd> |
| 46 | + ))} |
| 47 | + </SessionSpeakerContainer> |
| 48 | + </SessionItemInfoContainer> |
| 49 | + </SessionItemEl> |
| 50 | + <CategoryButtonDivider /> |
| 51 | + </> |
| 52 | + ); |
| 53 | + } |
| 54 | +); |
| 55 | + |
| 56 | +export const SessionListPage: React.FC = () => { |
| 57 | + const [selectedCategory, setSelectedCategory] = useState<string>("전체"); |
| 58 | + |
| 59 | + const [sessions, setSessions] = useState<BackendSessionAPISchemas.SessionSchema[]>(sessionDummyData); |
| 60 | + const [filteredSessions, setFilteredSessions] = useState<BackendSessionAPISchemas.SessionSchema[]>([]); |
| 61 | + |
| 62 | + useEffect(() => { |
| 63 | + setSessions(sessionDummyData); |
| 64 | + }); |
| 65 | + |
| 66 | + useEffect(() => { |
| 67 | + const newFilteredSessions = sessions.filter((session) => { |
| 68 | + const sessionCategoryNames: string[] = session.presentationCategories.map((category) => category.name); |
| 69 | + return sessionCategoryNames.includes(selectedCategory); |
| 70 | + }); |
| 71 | + setFilteredSessions(newFilteredSessions); |
| 72 | + }, [selectedCategory]); |
| 73 | + |
| 74 | + const CategoryButton: React.FC<{ category: string; isSelected: boolean }> = ({ category, isSelected }) => { |
| 75 | + return isSelected ? ( |
| 76 | + <CategoryButtonSelectedStyle>{category}</CategoryButtonSelectedStyle> |
| 77 | + ) : ( |
| 78 | + <CategoryButtonStyle |
| 79 | + onClick={() => { |
| 80 | + setSelectedCategory(category); |
| 81 | + }} |
| 82 | + > |
| 83 | + {category} |
| 84 | + </CategoryButtonStyle> |
| 85 | + ); |
| 86 | + }; |
| 87 | + |
| 88 | + const CategoryButtons: React.FC = () => { |
| 89 | + return ( |
| 90 | + <> |
| 91 | + {categoriesDummyData.map((category) => { |
| 92 | + return <CategoryButton category={category} isSelected={category === selectedCategory} />; |
| 93 | + })} |
| 94 | + </> |
| 95 | + ); |
| 96 | + }; |
| 97 | + |
| 98 | + return ( |
| 99 | + <SessionContainer> |
| 100 | + <SessionCategoryButtonContainer> |
| 101 | + <InfoTextContainer>{"* 발표 목록은 발표자 사정에 따라 변동될 수 있습니다."}</InfoTextContainer> |
| 102 | + <CategoryButtonDivider /> |
| 103 | + <ButtonGroupContainer> |
| 104 | + <CategoryButtons /> |
| 105 | + </ButtonGroupContainer> |
| 106 | + <CategoryButtonDivider /> |
| 107 | + </SessionCategoryButtonContainer> |
| 108 | + {(selectedCategory === "전체" ? sessions : filteredSessions).map((session) => ( |
| 109 | + <SessionItem key={session.presentationType.id} session={session} /> |
| 110 | + ))} |
| 111 | + </SessionContainer> |
| 112 | + ); |
| 113 | +}; |
| 114 | + |
| 115 | +const sessionDummyData: BackendSessionAPISchemas.SessionSchema[] = [ |
| 116 | + { |
| 117 | + id: "presentationId", |
| 118 | + name: "django ORM 관리하기", |
| 119 | + doNotRecord: false, |
| 120 | + presentationType: { |
| 121 | + id: "presentationTypeId", |
| 122 | + event: "eventId", |
| 123 | + name: "poetry 의존성 관리", |
| 124 | + }, |
| 125 | + presentationCategories: [ |
| 126 | + { |
| 127 | + id: "presentationCategories1", |
| 128 | + presentationType: "presentationTypeId", |
| 129 | + name: "블록체인", |
| 130 | + }, |
| 131 | + { |
| 132 | + id: "presentationCategories2", |
| 133 | + presentationType: "presentationTypeId", |
| 134 | + name: "라이브러리 / 코어", |
| 135 | + }, |
| 136 | + ], |
| 137 | + presentationSpeaker: [ |
| 138 | + { |
| 139 | + id: "presentationSpeakerId1", |
| 140 | + presentation: "presentationId", |
| 141 | + user: "ksy0526", |
| 142 | + name: "강소영", |
| 143 | + biography: "다양한 언어를 공부합니다.", |
| 144 | + image: "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI", |
| 145 | + }, |
| 146 | + ], |
| 147 | + }, |
| 148 | + { |
| 149 | + id: "presentationId", |
| 150 | + name: "uv로 python 프로젝트 관리하기", |
| 151 | + doNotRecord: true, |
| 152 | + presentationType: { |
| 153 | + id: "presentationTypeId", |
| 154 | + event: "eventId", |
| 155 | + name: "uv 의존성 관리", |
| 156 | + }, |
| 157 | + presentationCategories: [ |
| 158 | + { |
| 159 | + id: "presentationCategories1", |
| 160 | + presentationType: "presentationTypeId", |
| 161 | + name: "보안", |
| 162 | + }, |
| 163 | + { |
| 164 | + id: "presentationCategories2", |
| 165 | + presentationType: "presentationTypeId", |
| 166 | + name: "실무", |
| 167 | + }, |
| 168 | + ], |
| 169 | + presentationSpeaker: [ |
| 170 | + { |
| 171 | + id: "presentationSpeakerId1", |
| 172 | + presentation: "presentationId", |
| 173 | + user: "ksy0526", |
| 174 | + name: "강소영", |
| 175 | + biography: "다양한 언어를 공부합니다.", |
| 176 | + image: "https://fastly.picsum.photos/id/737/200/200.jpg?hmac=YPktyFzukhcmeW3VgULbam5iZTWOMXfwf6WIBPpJD50", |
| 177 | + }, |
| 178 | + ], |
| 179 | + }, |
| 180 | +]; |
| 181 | + |
| 182 | +const categoriesDummyData: string[] = [ |
| 183 | + "전체", |
| 184 | + "교육", |
| 185 | + "데이터 과학", |
| 186 | + "라이브러리 / 코어", |
| 187 | + "보안", |
| 188 | + "블록체인", |
| 189 | + "실무", |
| 190 | + "오픈소스 / 커뮤니티", |
| 191 | + "웹 서비스", |
| 192 | + "인공지능", |
| 193 | + "일상 / 사회", |
| 194 | + "자동화", |
| 195 | + "컴퓨터 비전", |
| 196 | +]; |
| 197 | + |
| 198 | +const CategoryButtonStyle = muiStyled(Button)(({ theme }) => ({ |
| 199 | + color: theme.palette.primary.light, |
| 200 | + "&:hover": { |
| 201 | + color: theme.palette.primary.dark, |
| 202 | + }, |
| 203 | +})); |
| 204 | + |
| 205 | +const CategoryButtonSelectedStyle = muiStyled(Button)(({ theme }) => ({ |
| 206 | + color: theme.palette.primary.dark, |
| 207 | +})); |
| 208 | + |
| 209 | +const CategoryButtonDivider = muiStyled(Divider)(({ theme }) => ({ |
| 210 | + bgcolor: theme.palette.primary.dark, |
| 211 | + borderColor: theme.palette.primary.dark, |
| 212 | +})); |
| 213 | + |
| 214 | +const SessionContainer = styled.div` |
| 215 | + margin-top: 1rem; |
| 216 | + margin-bottom: 1rem; |
| 217 | +`; |
| 218 | + |
| 219 | +const InfoTextContainer = styled.div` |
| 220 | + padding-bottom: 0.5rem; |
| 221 | + text-align: right; |
| 222 | + font-size: 0.75rem; |
| 223 | + p { |
| 224 | + text-align: right; |
| 225 | + font-size: 0.75rem; |
| 226 | + } |
| 227 | +`; |
| 228 | + |
| 229 | +const SessionCategoryButtonContainer = styled.div``; |
| 230 | + |
| 231 | +const ButtonGroupContainer = styled.div` |
| 232 | + display: flex; |
| 233 | +`; |
| 234 | + |
| 235 | +const SessionItemEl = styled.div` |
| 236 | + display: flex; |
| 237 | + align-items: center; |
| 238 | + justify-content: flex-start; |
| 239 | + padding: 0.75rem; |
| 240 | + width: 100%; |
| 241 | + gap: 1rem; |
| 242 | +
|
| 243 | + color: var(--pico-h6-color); |
| 244 | +
|
| 245 | + border-top: 1px solid var(--pico-muted-border-color); |
| 246 | + border-bottom: 1px solid var(--pico-muted-border-color); |
| 247 | +
|
| 248 | + @media only screen and (max-width: 809px) { |
| 249 | + padding: 0rem; |
| 250 | + gap: 0.5rem; |
| 251 | + } |
| 252 | +`; |
| 253 | + |
| 254 | +const SessionItemImgContainer = styled.div` |
| 255 | + width: 6rem; |
| 256 | + height: 6rem; |
| 257 | + margin: 0.6rem 0.6rem 0.6rem 1.5rem; |
| 258 | + flex-shrink: 0; |
| 259 | + flex-grow: 0; |
| 260 | +
|
| 261 | + border-radius: 50%; |
| 262 | + border: 1px solid var(--pico-muted-border-color); |
| 263 | +
|
| 264 | + * { |
| 265 | + width: 100%; |
| 266 | + height: 100%; |
| 267 | + min-width: 100%; |
| 268 | + min-height: 100%; |
| 269 | + max-width: 100%; |
| 270 | + max-height: 100%; |
| 271 | + border-radius: 50%; |
| 272 | + } |
| 273 | +
|
| 274 | + @media only screen and (max-width: 809px) { |
| 275 | + width: 5rem; |
| 276 | + height: 5rem; |
| 277 | + margin: 0.25rem; |
| 278 | + } |
| 279 | +`; |
| 280 | + |
| 281 | +const SessionItemInfoContainer = styled.div` |
| 282 | + padding-top: 0.5rem; |
| 283 | + padding-bottom: 0.5rem; |
| 284 | + margin-left: 1rem; |
| 285 | +
|
| 286 | + flex-grow: 1; |
| 287 | +
|
| 288 | + h4 { |
| 289 | + color: #febd99; |
| 290 | + margin-bottom: 0.2rem; |
| 291 | + cursor: pointer; |
| 292 | + } |
| 293 | +
|
| 294 | + p { |
| 295 | + margin-bottom: 0.3rem; |
| 296 | + color: var(--pico-h3-color); |
| 297 | + font-size: 0.8rem; |
| 298 | + font-weight: bold; |
| 299 | + } |
| 300 | +
|
| 301 | + @media only screen and (max-width: 809px) { |
| 302 | + h3 { |
| 303 | + font-size: 1.5rem; |
| 304 | + } |
| 305 | +
|
| 306 | + p { |
| 307 | + font-size: 0.8rem; |
| 308 | + font-weight: bold; |
| 309 | + } |
| 310 | + } |
| 311 | +`; |
| 312 | + |
| 313 | +const SessionTitleContainer = styled.div` |
| 314 | + display: flex; |
| 315 | + align-items: center; |
| 316 | + justify-content: flex-start; |
| 317 | + kbd { |
| 318 | + color: ${({ theme }) => theme.palette.text.primary}; |
| 319 | + gap: 0.5rem; |
| 320 | + font-family: ${({ theme }) => theme.typography.fontFamily}; |
| 321 | + font-weight: bold; |
| 322 | + font-size: 1.25rem; |
| 323 | + } |
| 324 | +`; |
| 325 | + |
| 326 | +const SessionSpeakerContainer = styled.div` |
| 327 | + display: flex; |
| 328 | + align-items: center; |
| 329 | + justify-content: flex-start; |
| 330 | + kbd { |
| 331 | + padding-top: 0.25rem; |
| 332 | + font-size: 0.8rem; |
| 333 | + color: #4e869d; |
| 334 | + } |
| 335 | +`; |
| 336 | + |
| 337 | +const TagContainer = styled.div` |
| 338 | + display: flex; |
| 339 | + align-items: center; |
| 340 | + justify-content: flex-start; |
| 341 | + padding: 0.25rem 0; |
| 342 | + gap: 0.35rem; |
| 343 | +`; |
| 344 | + |
| 345 | +const Tag = styled.kbd` |
| 346 | + background-color: white; |
| 347 | + padding: 0.2rem 0.4rem; |
| 348 | + font-size: 0.75rem; |
| 349 | + font-family: ${({ theme }) => theme.typography.fontFamily}; |
| 350 | + color: ${({ theme }) => theme.palette.primary.main}; |
| 351 | + bordercolor: ${({ theme }) => theme.palette.primary.main}; |
| 352 | + border: 1px solid; |
| 353 | + border-radius: 15px; |
| 354 | +`; |
0 commit comments