diff --git a/frontend/components/service/Program/DifficultyBadge.tsx b/frontend/components/service/Program/DifficultyBadge.tsx new file mode 100644 index 0000000..fecc651 --- /dev/null +++ b/frontend/components/service/Program/DifficultyBadge.tsx @@ -0,0 +1,22 @@ +import styled from 'styled-components' + +const DifficultyBadge = styled.span<{ level: string }>` + display: inline-block; + vertical-align: top; + padding: 0.3em 0.5em; + font-size: 0.7rem; + border-radius: 4px; + line-height: 1; + ${({ level }) => { + if (level === '초급') { + return `background-color: #139d2b;` + } else if (level === '중급') { + return `background-color: #ff7f00;` + } else if (level === '고급') { + return `background-color: #cf3535;` + } + return `display: none;` + }} +` + +export default DifficultyBadge diff --git a/frontend/components/service/Program/Speaker.tsx b/frontend/components/service/Program/Speaker.tsx index 0526e10..8535344 100644 --- a/frontend/components/service/Program/Speaker.tsx +++ b/frontend/components/service/Program/Speaker.tsx @@ -1,3 +1,4 @@ +import React from 'react' import { ISpeaker } from '../../../interfaces/IProgram' import styled from 'styled-components' import { media } from '../../../assets/styles/mixin' diff --git a/frontend/components/service/Program/TalkTable.tsx b/frontend/components/service/Program/TalkTable.tsx new file mode 100644 index 0000000..8945eb3 --- /dev/null +++ b/frontend/components/service/Program/TalkTable.tsx @@ -0,0 +1,124 @@ +import React from 'react' +import { ITalkItem, ITalkTableList } from '../../../interfaces/IProgram' +import styled from 'styled-components' +import { format } from 'date-fns' +import Link from 'next/link' +import DifficultyBadge from './DifficultyBadge' +import Resources from '../../../data/constants/resources' + +const Table = styled.table` + width: 100%; + border-collapse: collapse; + table-layout: fixed; +` +const LeftColumn = styled.div` + width: 90px; +` + +const TableHeader = styled.thead` + border-top: 2px solid ${(props) => props.theme.colors.white}; + border-bottom: 2px solid ${(props) => props.theme.colors.white}; +` +const TableRow = styled.tr` + & + & { + border-top: 1px solid ${(props) => props.theme.colors.white}; + } +` +const TableCell = styled.div` + padding: 1rem; + text-align: ${(props) => props.align ?? 'center'}; +` +const BadgeWrap = styled.div` + margin-top: 0.5rem; +` +const Title = styled.div` + margin: 0.4rem 0; +` +const Speaker = styled.div` + color: ${(props) => props.theme.colors.violet0}; +` +const BoldText = styled.div` + font-weight: bold; +` + +const TalkTableItem = (props: { item: ITalkItem }) => { + const { item } = props + const isKeynote = item.category === Resources.KEYNOTE_NAME + + return ( + + + {isKeynote && [키노트]} + {item.title} + {item.user_name} + + + ) +} + +const TalkTable = (props: { + day: string + headers: string[] + list: ITalkTableList[] +}) => { + const { list } = props + + const sortByTrack = (list: ITalkItem[]) => { + return list.sort((a, b) => a.track_num - b.track_num) + } + + const getTime = (timeString: string): string => { + return format(new Date(timeString), 'HH:mm') + } + + return ( +
+ + + + + {props.headers.map((header) => ( + + ))} + + + + {list.map((item, index) => ( + + + {item.talkList.length > 1 ? ( + sortByTrack(item.talkList).map((talkItem) => ( + + )) + ) : ( + + )} + + ))} + +
+ + 시간 +
+ (KST) +
+
{header}
+ + {getTime(item.video_open_at)} + + + + + + + + + +
+
+ ) +} + +export default TalkTable diff --git a/frontend/components/service/Program/TalkTableToggleButton.tsx b/frontend/components/service/Program/TalkTableToggleButton.tsx new file mode 100644 index 0000000..ec03506 --- /dev/null +++ b/frontend/components/service/Program/TalkTableToggleButton.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react' +import styled from 'styled-components' + +const ButtonGroup = styled.div` + display: flex; + width: 100%; + align-items: center; + justify-content: center; + border-bottom: 1px solid ${(props) => props.theme.colors.white}; +` +const Button = styled.button<{ selected: boolean }>` + border-radius: 4px; + font-size: 1rem; + padding: 1rem 2rem; + background: inherit; + cursor: pointer; + border: 0; + cursor: pointer; + color: ${(props) => props.theme.colors.white}; + text-decoration: ${(props) => (props.selected ? 'underline' : 'none')}; +` + +interface ToggleProps { + handleClick: (day: string) => void +} + +const TalkTableToggleButton: React.FC = ({ handleClick }) => { + const [checked, setChecked] = useState('day1') + + const handleToggle = (day: string): void => { + setChecked(day) + handleClick(day) + } + + return ( + + + + + ) +} + +export default TalkTableToggleButton diff --git a/frontend/interfaces/IProgram.ts b/frontend/interfaces/IProgram.ts index 3490389..d54b8e6 100644 --- a/frontend/interfaces/IProgram.ts +++ b/frontend/interfaces/IProgram.ts @@ -1,5 +1,10 @@ import { IApiTalkItem } from './api/IApiPrograms' +export interface ITalkTableList { + [key: string]: any + talkList: ITalkItem[] +} + export interface ITalkItem extends IApiTalkItem {} export interface ICategoryListItem { diff --git a/frontend/locales/en/sponsorLevel.ts b/frontend/locales/en/sponsorLevel.ts index 571eb6b..e113e5e 100644 --- a/frontend/locales/en/sponsorLevel.ts +++ b/frontend/locales/en/sponsorLevel.ts @@ -2,7 +2,7 @@ import { SponsorLevel } from '../../data/enums/SponsorLevel' export default { [SponsorLevel.LEVEL_1]: 'Keystone', - [SponsorLevel.LEVEL_2]: 'Partner A', + [SponsorLevel.LEVEL_2]: 'Partner', [SponsorLevel.LEVEL_3]: 'Partner B', [SponsorLevel.LEVEL_4]: 'Startup', [SponsorLevel.LEVEL_5]: 'Community' diff --git a/frontend/locales/ko/sponsorLevel.ts b/frontend/locales/ko/sponsorLevel.ts index 4eccf80..b370c37 100644 --- a/frontend/locales/ko/sponsorLevel.ts +++ b/frontend/locales/ko/sponsorLevel.ts @@ -2,7 +2,7 @@ import { SponsorLevel } from '../../data/enums/SponsorLevel' export default { [SponsorLevel.LEVEL_1]: '키스톤', - [SponsorLevel.LEVEL_2]: '파트너A', + [SponsorLevel.LEVEL_2]: '파트너', [SponsorLevel.LEVEL_3]: '파트너B', [SponsorLevel.LEVEL_4]: '스타트업', [SponsorLevel.LEVEL_5]: '커뮤니티' diff --git a/frontend/package.json b/frontend/package.json index 8d937bb..2bcb187 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@sls-next/serverless-component": "^3.7.0", "axios": "^0.27.2", "babel-plugin-styled-components": "^2.0.6", + "date-fns": "^2.29.3", "env-cmd": "^10.1.0", "gray-matter": "^4.0.3", "i18next": "^21.6.14", diff --git a/frontend/pages/program/talk-schedule.tsx b/frontend/pages/program/talk-schedule.tsx index 883576b..6130440 100644 --- a/frontend/pages/program/talk-schedule.tsx +++ b/frontend/pages/program/talk-schedule.tsx @@ -1,25 +1,100 @@ -import React from 'react' +import React, { useState } from 'react' import type { NextPage, GetServerSideProps } from 'next' import { useTranslation } from 'react-i18next' import { PageName } from '../../data/enums/PageName' import PageTitle from '../../components/core/PageTitle' import { PageProps } from '../../interfaces/PageProps' +import { getTalkList } from '../api/program' +import { GetServerSidePropsContext } from 'next' +import { ITalkItem, ITalkList, ITalkTableList } from '../../interfaces/IProgram' +import { compareAsc, isSameDay } from 'date-fns' +import TalkTableToggleButton from '../../components/service/Program/TalkTableToggleButton' +import TalkTable from '../../components/service/Program/TalkTable' -const TalkSchedule: NextPage = (props: PageProps) => { +interface TalkTableProps extends PageProps { + data: ITalkList +} + +const TalkSchedule: NextPage = (props: TalkTableProps) => { const { t } = useTranslation() + const { pageName, data } = props + const [selectedDay, setSelectedDay] = useState('day1') + + const updateSelectedDay = (day: string) => { + setSelectedDay(day) + } + + const groupByProperty = ( + array: ITalkItem[], + property: string + ): ITalkTableList[] => { + const groupByValue: { [key: string]: ITalkItem[] } = array.reduce( + (obj, item) => { + obj[item[property]] = obj[item[property]] || [] + obj[item[property]].push(item) + return obj + }, + {} + ) + + return Object.keys(groupByValue).map((key: string) => ({ + [property]: key, + talkList: groupByValue[key] + })) + } + + const tableData: ITalkItem[] = data.list.sort((a, b) => + compareAsc(new Date(a.video_open_at), new Date(b.video_open_at)) + ) + + const day1tableList: ITalkItem[] = tableData.filter((item) => + isSameDay(new Date(item.video_open_at), new Date(2022, 9, 1)) + ) + const day2tableList: ITalkItem[] = tableData.filter((item) => + isSameDay(new Date(item.video_open_at), new Date(2022, 9, 2)) + ) return (
- - {t('label:preparing')} + + + {selectedDay === 'day1' ? ( + + ) : ( + + )}
) } -export const getServerSideProps: GetServerSideProps = async () => { - return { - props: { - title: PageName.TalkSchedule +export const getServerSideProps: GetServerSideProps = async ( + context: GetServerSidePropsContext +) => { + const { locale } = context + try { + const data: ITalkList = await getTalkList() + + return { + props: { + title: PageName.TalkSchedule, + locale, + data + } + } + } catch (error) { + // TODO: Add error interface + if (error.notFound) { + return { + notFound: true + } } } } diff --git a/pyconweb2022/sponsor/viewsets.py b/pyconweb2022/sponsor/viewsets.py index 6ee01d4..3d53702 100644 --- a/pyconweb2022/sponsor/viewsets.py +++ b/pyconweb2022/sponsor/viewsets.py @@ -21,7 +21,9 @@ def get_queryset(self): return Sponsor.objects.all() def list(self, request, *args, **kwargs): - queryset = Sponsor.objects.filter(accepted=True) # 모든 절차가 완료된 후원사만 리스팅 + queryset = Sponsor.objects.filter(accepted=True).order_by( + "name" + ) # 모든 절차가 완료된 후원사만 리스팅 serializer = SponsorListSerializer(queryset, many=True) return Response(serializer.data) diff --git a/yarn.lock b/yarn.lock index d86a0df..3ecb678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3831,6 +3831,11 @@ data-uri-to-buffer@3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== +date-fns@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + dayjs@^1.10.4, dayjs@^1.10.7: version "1.11.2" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5"