diff --git a/src/components/atoms/Card.tsx b/src/components/atoms/Card.tsx new file mode 100644 index 0000000..c28ba92 --- /dev/null +++ b/src/components/atoms/Card.tsx @@ -0,0 +1,28 @@ +import { type ReactNode } from "react"; +import { Card as AntCard } from "antd"; +import { css } from "@emotion/react"; +import colors from "@/styles/colors"; + +interface Props { + children: ReactNode; + round?: boolean; +} + +const Card = ({ children, round = false }: Props) => ( + + {children} + +); + +export default Card; diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index e69de29..fc91b3d 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -0,0 +1 @@ +export { default as Card } from "./Card"; diff --git a/src/components/organisms/Footer.tsx b/src/components/organisms/Footer.tsx index 63d2ed2..0ec7572 100644 --- a/src/components/organisms/Footer.tsx +++ b/src/components/organisms/Footer.tsx @@ -1,9 +1,9 @@ -import { Col, Flex, Layout, Row, Typography } from "antd"; +import { Col, ConfigProvider, Flex, Layout, Row, Typography } from "antd"; import { css } from "@emotion/react"; - import { SNSHomepage, SNSInstagram, SNSLinkedin, SNSYoutube } from "@/assets"; +import colors from "@/styles/colors"; -const { Title, Paragraph, Text } = Typography; +const { Title, Paragraph, Text, Link } = Typography; const { Footer } = Layout; const FooterTitle = () => ( @@ -22,7 +22,20 @@ const FooterDescription = () => ( 젊치인의 도전과 성장을 돕는 에이전시 대표 박혜민 고유번호 767-82-00531 - 문의 build@newways.kr + + 문의{" "} + + + build@newways.kr + + + ); diff --git a/src/components/organisms/Histogram.tsx b/src/components/organisms/Histogram.tsx index 8aeefbb..72ca20a 100644 --- a/src/components/organisms/Histogram.tsx +++ b/src/components/organisms/Histogram.tsx @@ -72,6 +72,7 @@ export const Histogram = ({ data }: Props) => { data: histogramData, binField: "value", binWidth, + height: 250, colorField: "type", color: param => { const [binMin] = param.range as [number, number]; diff --git a/src/components/pages/LocalCouncilPage.tsx b/src/components/organisms/LocalCouncilCard.tsx similarity index 84% rename from src/components/pages/LocalCouncilPage.tsx rename to src/components/organisms/LocalCouncilCard.tsx index 3d9c57e..f0a7c45 100644 --- a/src/components/pages/LocalCouncilPage.tsx +++ b/src/components/organisms/LocalCouncilCard.tsx @@ -1,10 +1,11 @@ import { useEffect, useState } from "react"; -import { MapSelector, LocalCouncilReport } from "@/components/organisms"; import { useParams } from "react-router-dom"; import { Element, scroller } from "react-scroll"; -import { Layout } from "@/components/templates"; + import { type MetroID } from "static/MapSVGData"; import axios from "@/utils/axios"; +import MapSelector from "./MapSelector"; +import LocalCouncilReport from "./LocalCouncilReport"; type RegionInfo = { id: number; @@ -17,10 +18,12 @@ type LocalInfo = { id: number; }; -const LocalCouncil = () => { +const LocalCouncilCard = () => { const { metroName, localName } = useParams(); const [metroLocalMap, setMetroLocalMap] = useState>>(); + const [isLoaded, setIsLoaded] = useState(false); + useEffect(() => { if (!metroName || !localName) return; scroller.scrollTo("Report", { @@ -28,7 +31,8 @@ const LocalCouncil = () => { delay: 50, smooth: "easeInOutQuart", }); - }, [metroName, localName]); + }, [metroName, localName, isLoaded]); + useEffect(() => { const idMap = new Map>(); axios.get("/localCouncil/regionInfo").then(response => { @@ -50,7 +54,7 @@ const LocalCouncil = () => { }); }, []); return metroLocalMap ? ( - + <> {metroName && localName ? ( @@ -58,11 +62,12 @@ const LocalCouncil = () => { metroName={metroName as MetroID} localName={localName} idMap={metroLocalMap} + onLoaded={() => setIsLoaded(true)} /> ) : null} - + ) : null; }; -export default LocalCouncil; +export default LocalCouncilCard; diff --git a/src/components/organisms/LocalCouncilReport.tsx b/src/components/organisms/LocalCouncilReport.tsx index a6a3e3f..7d95847 100644 --- a/src/components/organisms/LocalCouncilReport.tsx +++ b/src/components/organisms/LocalCouncilReport.tsx @@ -54,9 +54,15 @@ interface Props { metroName: MetroID; localName: string; idMap: Map>; + onLoaded: () => void; } -const LocalCouncilReport = ({ metroName, localName, idMap }: Props) => { +const LocalCouncilReport = ({ + metroName, + localName, + idMap, + onLoaded, +}: Props) => { const defaultData: GenderTextData = { metroName, localName, @@ -231,6 +237,7 @@ const LocalCouncilReport = ({ metroName, localName, idMap }: Props) => { useEffect(fetchGraphColors, []); useEffect(() => { + onLoaded(); fetchTextData(); fetchGraphData(); }, [metroName, localName]); @@ -243,9 +250,14 @@ const LocalCouncilReport = ({ metroName, localName, idMap }: Props) => { margin: 40px 0 40px 0; `} > - {`${metroName} ${localName}의 ${sgYear}년도 ${ + <Title + level={2} + css={css` + word-break: keep-all; + `} + >{`${metroName} ${localName}의 ${sgYear}년도 ${ sgType === "candidate" ? "후보자" : "당선인" - } 광역의회 다양성 리포트`} + } 지역의회 다양성 리포트`} { `} > { setSgYear(parseInt(key)); @@ -271,15 +283,15 @@ const LocalCouncilReport = ({ metroName, localName, idMap }: Props) => { defaultChecked /> - 연령 다양성 + 연령 다양성 {ageHistogramData ? : null} - 성별 다양성 + 성별 다양성 {genderPieChartData && genderPieChartColorMap ? ( ) : null} - 정당 다양성 + 정당 다양성 {partyPieChartData && partyPieChartColorMap ? ( ) : null} diff --git a/src/components/organisms/MapSelector.tsx b/src/components/organisms/MapSelector.tsx index c04e906..7b8c273 100644 --- a/src/components/organisms/MapSelector.tsx +++ b/src/components/organisms/MapSelector.tsx @@ -25,7 +25,7 @@ const MapSelector = ({ idMap, type = "local" }: Props) => { align="center" gap={40} css={css` - margin: 40px 0 40px 0; + margin: 0 0 40px 0; `} > {metroName && type === "local" ? ( @@ -43,7 +43,7 @@ const MapSelector = ({ idMap, type = "local" }: Props) => { { title: ( // eslint-disable-next-line jsx-a11y/anchor-is-valid - 뉴웨이즈 다양성 리포트 + 지역의회 다양성 리포트 ), onClick: () => navigate(`/localCouncil`), }, @@ -73,7 +73,7 @@ const MapSelector = ({ idMap, type = "local" }: Props) => { `} > { const idData = idMap.get(metroName as MetroID)?.get(local); @@ -112,7 +112,7 @@ const MapSelector = ({ idMap, type = "local" }: Props) => { <> {" "} type === "local" diff --git a/src/components/pages/MetroCouncilPage.tsx b/src/components/organisms/MetroCouncilCard.tsx similarity index 85% rename from src/components/pages/MetroCouncilPage.tsx rename to src/components/organisms/MetroCouncilCard.tsx index 9e938fc..577e48d 100644 --- a/src/components/pages/MetroCouncilPage.tsx +++ b/src/components/organisms/MetroCouncilCard.tsx @@ -1,10 +1,11 @@ import { useEffect, useState } from "react"; -import { MapSelector, MetroCouncilReport } from "@/components/organisms"; import { useParams } from "react-router-dom"; import { Element, scroller } from "react-scroll"; -import { Layout } from "@/components/templates"; + import { type MetroID } from "static/MapSVGData"; import axios from "@/utils/axios"; +import MapSelector from "./MapSelector"; +import MetroCouncilReport from "./MetroCouncilReport"; type RegionInfo = { id: number; @@ -17,11 +18,13 @@ type LocalInfo = { id: number; }; -const MetroCouncilPage = () => { +const MetroCouncilCard = () => { const { metroName } = useParams(); const [metroLocalMap, setMetroLocalMap] = useState>>(); const [metroMap, setMetroMap] = useState>(); + const [isLoaded, setIsLoaded] = useState(false); + useEffect(() => { if (!metroName) return; scroller.scrollTo("Report", { @@ -29,7 +32,7 @@ const MetroCouncilPage = () => { delay: 50, smooth: "easeInOutQuart", }); - }, [metroName]); + }, [metroName, isLoaded]); useEffect(() => { const idMap = new Map>(); const metroAPIMap = new Map(); @@ -54,7 +57,7 @@ const MetroCouncilPage = () => { }); }, []); return metroLocalMap ? ( - + <> {metroName ? ( @@ -62,11 +65,12 @@ const MetroCouncilPage = () => { metroName={metroName as MetroID} metroMap={metroMap} idMap={metroLocalMap} + onLoaded={() => setIsLoaded(true)} /> ) : null} - + ) : null; }; -export default MetroCouncilPage; +export default MetroCouncilCard; diff --git a/src/components/organisms/MetroCouncilReport.tsx b/src/components/organisms/MetroCouncilReport.tsx index 054bb58..f4e8d4d 100644 --- a/src/components/organisms/MetroCouncilReport.tsx +++ b/src/components/organisms/MetroCouncilReport.tsx @@ -40,9 +40,15 @@ interface Props { metroName: MetroID; idMap: Map>; metroMap: Map | undefined; + onLoaded: () => void; } -const MetroCouncilReport = ({ metroName, idMap, metroMap }: Props) => { +const MetroCouncilReport = ({ + metroName, + idMap, + metroMap, + onLoaded, +}: Props) => { const defaultData: GenderTextData = { metroName, now: { @@ -179,6 +185,7 @@ const MetroCouncilReport = ({ metroName, idMap, metroMap }: Props) => { useEffect(fetchGraphColors, []); useEffect(() => { + onLoaded(); fetchTextData(); fetchGraphData(); }, [metroName]); @@ -191,7 +198,12 @@ const MetroCouncilReport = ({ metroName, idMap, metroMap }: Props) => { margin: 40px 0 40px 0; `} > - {`${metroName}의 ${sgYear}년도 ${ + <Title + level={2} + css={css` + word-break: keep-all; + `} + >{`${metroName}의 ${sgYear}년도 ${ sgType === "candidate" ? "후보자" : "당선인" } 광역의회 다양성 리포트`} { `} > { setSgYear(parseInt(key)); @@ -219,15 +231,15 @@ const MetroCouncilReport = ({ metroName, idMap, metroMap }: Props) => { defaultChecked /> - 연령 다양성 + 연령 다양성 {/* */} - 성별 다양성 + 성별 다양성 {genderPieChartData && genderPieChartColorMap ? ( ) : null} - 정당 다양성 + 정당 다양성 {partyPieChartData && partyPieChartColorMap ? ( ) : null} diff --git a/src/components/organisms/PieChart.tsx b/src/components/organisms/PieChart.tsx index 7c6d429..02256c5 100644 --- a/src/components/organisms/PieChart.tsx +++ b/src/components/organisms/PieChart.tsx @@ -16,6 +16,7 @@ export const PieChart = ({ data, colorMap }: Props) => { appendPadding: 10, data, angleField: "value", + height: 250, radius: 0.6, label: { type: "outer", diff --git a/src/components/organisms/ReportIntro.tsx b/src/components/organisms/ReportIntro.tsx new file mode 100644 index 0000000..58d9904 --- /dev/null +++ b/src/components/organisms/ReportIntro.tsx @@ -0,0 +1,43 @@ +import { Typography } from "antd"; +import { css } from "@emotion/react"; +import { useLocalElectionYears } from "@/utils"; + +const { Title, Paragraph, Text } = Typography; + +const ReportIntro = () => { + const localElectionYears = useLocalElectionYears(); + const firstElection = localElectionYears[0]; + const lastElection = localElectionYears[localElectionYears.length - 1]; + return ( + <> + + 우리동네 정치인 다양성 리포트 + + + {lastElection.year}년 제{lastElection.ordinal}회 전국동시지방선거가 막을{" "} + 내렸어요. +
+
+ 뉴웨이즈에서는 지난 {firstElection.year}년 지방선거부터{" "} + {lastElection.year}년 지방선거까지의 후보자와 당선자 데이터를 다양성{" "} + 관점에서 분석해봤어요. 연령과 성별, 정당을 기준으로 각 지역의 다양성이{" "} + 지난 선거 때와 비교해 어떻게 변화했는지 살펴보았어요. +
+
+ 과연 우리 동네는 어디까지 왔을까요? +
+ + 새로운 시대, 우리 동네에도 새로운 물결이 밀려오고 있을까요? +
+
+
+ + ); +}; + +export default ReportIntro; diff --git a/src/components/organisms/TabSelector.tsx b/src/components/organisms/TabSelector.tsx new file mode 100644 index 0000000..f1c84b4 --- /dev/null +++ b/src/components/organisms/TabSelector.tsx @@ -0,0 +1,49 @@ +import { ConfigProvider, Tabs } from "antd"; +import { useNavigate, useParams } from "react-router-dom"; +import { css } from "@emotion/react"; +import colors from "@/styles/colors"; + +const TabSelctor = () => { + const { metroName, localName } = useParams(); + const navigate = useNavigate(); + + return ( + + { + navigate( + `/${key}${metroName ? `/${metroName}` : ""}${ + metroName && localName ? `/${localName}` : "" + }`, + ); + }} + type="card" + activeKey={window.location.pathname.split("/")[1]} + /> + + ); +}; + +export default TabSelctor; diff --git a/src/components/organisms/index.ts b/src/components/organisms/index.ts index 2bb18c2..03acb29 100644 --- a/src/components/organisms/index.ts +++ b/src/components/organisms/index.ts @@ -5,5 +5,9 @@ export * from "./Histogram"; export * from "./PieChart"; export { default as TestChart } from "./TestChart"; export { default as LocalCouncilReport } from "./LocalCouncilReport"; +export { default as LocalCouncilCard } from "./LocalCouncilCard"; export { default as MapSelector } from "./MapSelector"; export { default as MetroCouncilReport } from "./MetroCouncilReport"; +export { default as MetroCouncilCard } from "./MetroCouncilCard"; +export { default as TabSelector } from "./TabSelector"; +export { default as ReportIntro } from "./ReportIntro"; diff --git a/src/components/pages/MainPage.tsx b/src/components/pages/MainPage.tsx new file mode 100644 index 0000000..8ed65f6 --- /dev/null +++ b/src/components/pages/MainPage.tsx @@ -0,0 +1,36 @@ +import { useParams } from "react-router-dom"; +import { Divider } from "antd"; +import { Layout } from "@/components/templates"; +import { Card } from "@/components/atoms"; +import { + LocalCouncilCard, + MetroCouncilCard, + ReportIntro, + TabSelector, +} from "@/components/organisms"; + +const MainPage = () => { + const { reportType } = useParams(); + + return ( + + + + + + {(() => { + switch (reportType) { + case "localCouncil": + return ; + case "metroCouncil": + return ; + default: + return ; + } + })()} + + + ); +}; + +export default MainPage; diff --git a/src/components/pages/index.ts b/src/components/pages/index.ts index 2bf0976..3fafe27 100644 --- a/src/components/pages/index.ts +++ b/src/components/pages/index.ts @@ -1,3 +1,2 @@ // page 컴포넌트를 모아놓은 파일 -export { default as LocalCouncilPage } from "./LocalCouncilPage"; -export { default as MetroCouncilPage } from "./MetroCouncilPage"; +export { default as MainPage } from "./MainPage"; diff --git a/src/components/templates/Layout.tsx b/src/components/templates/Layout.tsx index 22b0d02..c36112e 100644 --- a/src/components/templates/Layout.tsx +++ b/src/components/templates/Layout.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Col, ConfigProvider, Layout, Row } from "antd"; import { css } from "@emotion/react"; import { Header, Footer } from "@/components/organisms"; +import colors from "@/styles/colors"; const { Content } = Layout; @@ -20,11 +21,10 @@ const NewwaysLayout = ({ children }: Props) => ( }, components: { Layout: { - // Header, Content, Footer의 배경색을 설정합니다. - headerBg: "#F0F0F0", - bodyBg: "#00E9A1", // ugly... - // bodyBg: "#F1F1F1", // much better - footerBg: "#F0F0F0", + // Header, Footer의 배경색을 설정합니다. + headerBg: colors.white, + bodyBg: colors.black, + footerBg: colors.white, }, }, }} @@ -32,8 +32,17 @@ const NewwaysLayout = ({ children }: Props) => (
- {/* breakpoint: 768px */} - + } />, - } />, - } - />, - } />, - } />, + } />, + } />, + } />, + } />, } />, ]), ); diff --git a/src/styles/colors.ts b/src/styles/colors.ts new file mode 100644 index 0000000..7a89acf --- /dev/null +++ b/src/styles/colors.ts @@ -0,0 +1,14 @@ +const colors = { + black: "#222222", + gray54: "#363636", + gray140: "#8C8C8C", + gray200: "#C8C8C8", + gray220: "#DCDCDC", + gray232: "#E8E8E8", + white: "#F2F2F2", + newWaysFeed: "#00E9A1", + localCouncil: "#B5FD50", + metroCouncil: "#E65E2B", +}; + +export default colors; diff --git a/src/utils/index.ts b/src/utils/index.ts index 28f347f..8f6e034 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,3 +3,4 @@ export * from "./constants"; export * from "./color"; export { default as axios } from "./axios"; export { default as useGetNameFromId } from "./useGetNameFromId"; +export { default as useLocalElectionYears } from "./useLocalElectionYears"; diff --git a/src/utils/useLocalElectionYears.ts b/src/utils/useLocalElectionYears.ts new file mode 100644 index 0000000..2b71fc5 --- /dev/null +++ b/src/utils/useLocalElectionYears.ts @@ -0,0 +1,9 @@ +/** 지방선거(재보궐 선거 제외) 회차와 열린 해의 목록을 반환합니다. */ +const useLocalElectionYears = () => [ + { ordinal: 5, year: 2010 }, + { ordinal: 6, year: 2014 }, + { ordinal: 7, year: 2018 }, + { ordinal: 8, year: 2022 }, +]; + +export default useLocalElectionYears;