diff --git a/.env.example b/.env.example
deleted file mode 100644
index f6a41f1..0000000
--- a/.env.example
+++ /dev/null
@@ -1,4 +0,0 @@
-NEXT_PUBLIC_KAKAO_CLIENT_ID=
-NEXT_PUBLIC_CLIENT_REDIRECT_URI=
-NEXT_PUBLIC_LOCAL_BASE_URL=
-NEXT_PUBLIC_KAKAO_MAP_KEY=
diff --git a/app/(router)/analysis/amount/page.tsx b/app/(router)/analysis/amount/page.tsx
index 1bc78eb..7819db4 100644
--- a/app/(router)/analysis/amount/page.tsx
+++ b/app/(router)/analysis/amount/page.tsx
@@ -1,5 +1,9 @@
+'use client';
+
+import AmountAnalysisWrapper from '@/_components/analysis/amount/AmountAnalysisWrapper';
+
const AmountAnalysis = () => {
- return
금액별
;
+ return ;
};
export default AmountAnalysis;
diff --git a/app/(router)/analysis/period/filter/page.tsx b/app/(router)/analysis/period/filter/page.tsx
new file mode 100644
index 0000000..729919f
--- /dev/null
+++ b/app/(router)/analysis/period/filter/page.tsx
@@ -0,0 +1,9 @@
+'use client';
+
+import PeriodicDateFilterWrapper from '@/_components/analysis/period/filter/PeriodicDateFilterWrapper';
+
+const PeriodAnalysis = () => {
+ return ;
+};
+
+export default PeriodAnalysis;
diff --git a/app/(router)/analysis/period/pick/page.tsx b/app/(router)/analysis/period/pick/page.tsx
deleted file mode 100644
index 31db556..0000000
--- a/app/(router)/analysis/period/pick/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-'use client';
-
-import PeriodicDatePicker from '@/_components/analysis/period/pick/PeriodicDatePicker';
-
-const PeriodAnalysis = () => {
- return ;
-};
-
-export default PeriodAnalysis;
diff --git a/app/(router)/analysis/rounds/page.tsx b/app/(router)/analysis/rounds/page.tsx
index 7a0a3d3..327db5c 100644
--- a/app/(router)/analysis/rounds/page.tsx
+++ b/app/(router)/analysis/rounds/page.tsx
@@ -1,5 +1,9 @@
+'use client';
+
+import RoundsAnalysisWrapper from '@/_components/analysis/rounds/RoundsAnalysisWrapper';
+
const RoundsAnalysis = () => {
- return 회차별
;
+ return ;
};
export default RoundsAnalysis;
diff --git a/app/_components/analysis/AnalysisMainWrapper.tsx b/app/_components/analysis/AnalysisMainWrapper.tsx
index 36f0edc..29c4a54 100644
--- a/app/_components/analysis/AnalysisMainWrapper.tsx
+++ b/app/_components/analysis/AnalysisMainWrapper.tsx
@@ -1,36 +1,79 @@
'use client';
import palette from '@/_styles/palette';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import ResultAmountBannerList from './ResultAmountBannerList';
import AnalyticsDashboardMenu from './AnalyticsDashboardMenu';
import Image from 'next/image';
import PolygonIcon from '@assets/svg/polygon.svg';
+import ArrowDropDownIcon from '@assets/svg/arrowDropDown.svg';
+import AuthProvider from '../providers/AuthProvider';
+import useLatestNumber from '@/_hooks/useLatestNumber';
+import { parse } from 'date-fns';
+import BottomSheet from '../common/BottomSheet';
+import RoundSelector from './RoundSelector';
+import { LatestNumberResponseType } from '@/_types/analysis';
-type AnalysisMainWrapperProps = {};
+const AnalysisMainWrapper: React.FC = () => {
+ const { latestNumbers, isLoading } = useLatestNumber();
+ const [isOpenScrapBottomSheet, setIsOpenScrapBottomSheet] = useState(false);
+ const [selectNumbers, setSelectNumbers] = useState();
-const AnalysisMainWrapper: React.FC = () => {
- // const test = [3, 5, 7, 9, 1, 13, 4]; //? res값 정제
+ useEffect(() => {
+ if (!latestNumbers) {
+ return console.log('no number');
+ }
+ setSelectNumbers(latestNumbers);
+ }, [latestNumbers]);
- return (
-
-
- 7월 2주차 1078회
-
- 당첨번호를 확인하세요.
-
+ const parsedDate = parse(`${selectNumbers?.drwt_date}`, 'yyyy-MM-dd', new Date());
+ const monthNumber = parsedDate.getMonth() + 1;
+ const weekNumber = Math.ceil(parsedDate.getDate() / 7);
+
+ if (isLoading) return loading
; //로딩 UI 필요
+ //latestNumbers 없을 때 행동
-
- 3 5 7 9 1 13 4{' '}
- 34
-
-
+ return (
+
+
+
+
+ {monthNumber}월 {weekNumber}주차{' '}
+ setIsOpenScrapBottomSheet(true)}>
+ {selectNumbers?.drwt_no}회
+
+
+ 당첨번호를 확인하세요.
+
+
+ {selectNumbers &&
+ new Array(6)
+ .fill('')
+ .map((_, i) => (
+ {selectNumbers[`drwt_no${i + 1}`]}
+ ))}
+ {selectNumbers?.bnus_no}
+
+
+
+
-
-
-
-
+
+
+ setIsOpenScrapBottomSheet(true)}
+ onClose={() => setIsOpenScrapBottomSheet(false)}
+ title="회차 선택"
+ >
+
+
+
);
};
@@ -42,14 +85,28 @@ const AnalysisMainWrapperBlock = styled.div`
`;
const AnalysisMainTitle = styled.p`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
font-size: 20px;
font-weight: bold;
line-height: 26px;
- text-align: center;
padding: 15px 0;
margin-bottom: 16px;
`;
+const Text = styled.p`
+ display: flex;
+ gap: 5px;
+`;
+
+const SelectorButton = styled.button`
+ display: flex;
+ font-size: 20px;
+ font-weight: bold;
+ color: ${palette.orange_20};
+`;
+
const PolygonIconSVG = styled(PolygonIcon)`
position: absolute;
bottom: -10px;
@@ -64,12 +121,13 @@ const WeekWinningNumberBox = styled.div`
border-radius: 400px;
margin-bottom: 21px;
position: relative;
+ display: flex;
+ gap: 10px;
`;
const WeekWinningNumber = styled.span`
color: ${palette.grey_20};
font-size: 20px;
- letter-spacing: 6px;
font-weight: bold;
`;
const WeekWinningBonusNumber = styled.span`
diff --git a/app/_components/analysis/AnalyticsDashboardMenu.tsx b/app/_components/analysis/AnalyticsDashboardMenu.tsx
index cbe5858..085b3dc 100644
--- a/app/_components/analysis/AnalyticsDashboardMenu.tsx
+++ b/app/_components/analysis/AnalyticsDashboardMenu.tsx
@@ -21,7 +21,7 @@ const AnalyticsDashboardMenu: React.FC = () => {
-
+
당첨금액별
금액이 높은 순으로 조회하기
diff --git a/app/_components/analysis/ResultAmountBannerList.tsx b/app/_components/analysis/ResultAmountBannerList.tsx
index faebaa7..af6a599 100644
--- a/app/_components/analysis/ResultAmountBannerList.tsx
+++ b/app/_components/analysis/ResultAmountBannerList.tsx
@@ -2,54 +2,31 @@ import React from 'react';
import styled from 'styled-components';
import palette from '@/_styles/palette';
import { Swiper, SwiperSlide } from 'swiper/react';
+import useLatestNumber from '@/_hooks/useLatestNumber';
+import { LatestNumberResponseType } from '@/_types/analysis';
-type ResultAmountBannerListProps = {};
-
-const ResultAmountBannerList: React.FC = () => {
- const testList = [
- {
- count: 1,
- amount: 2539849237,
- },
- {
- count: 1,
- amount: 2539849237,
- },
- {
- count: 1,
- amount: 2539849237,
- },
- {
- count: 1,
- amount: 2539849237,
- },
- {
- count: 1,
- amount: 2539849237,
- },
- {
- count: 1,
- amount: 2539849237,
- },
- ];
+type ResultAmountBannerListProps = {
+ roundNumbers: LatestNumberResponseType;
+};
+const ResultAmountBannerList: React.FC = ({ roundNumbers }) => {
return (
- {testList.map((test, i) => (
-
+ {roundNumbers && (
+
- {i + 1}등
- {test.count}명
+ 1등
+ {roundNumbers.first_win_count}명
- {test.amount.toLocaleString()}원
+ {roundNumbers.first_win_amount.toLocaleString()}원
- ))}
+ )}
);
};
@@ -71,9 +48,10 @@ const ResultAmountBannerListBlock = styled(Swiper)`
`;
const ResultAmountBannerItem = styled.li`
+ width: 108px;
display: flex;
flex-direction: column;
- padding: 20px 14px;
+ padding: 20px 15px;
background-color: ${palette.white};
border-radius: 10px;
`;
diff --git a/app/_components/analysis/RoundSelector.tsx b/app/_components/analysis/RoundSelector.tsx
new file mode 100644
index 0000000..b103fc9
--- /dev/null
+++ b/app/_components/analysis/RoundSelector.tsx
@@ -0,0 +1,56 @@
+import instance from '@/_apis/core';
+import { LatestNumberResponseType } from '@/_types/analysis';
+import React from 'react';
+import styled from 'styled-components';
+
+type RoundSelectorProps = {
+ lastestRound: number;
+ setIsOpenScrapBottomSheet: React.Dispatch>;
+ setSelectNumbers: React.Dispatch>;
+};
+
+const RoundSelector: React.FC = ({
+ lastestRound,
+ setIsOpenScrapBottomSheet,
+ setSelectNumbers,
+}) => {
+ const selectOptions = Array.from({ length: lastestRound }, (_, index) => lastestRound - index);
+
+ const onSelect = async (drwtNo: number) => {
+ try {
+ const data = await instance.get(`/api/number`, {
+ params: {
+ drwtNo,
+ },
+ });
+
+ setSelectNumbers(data);
+ } catch (err) {
+ console.log('err', err);
+ }
+ setIsOpenScrapBottomSheet(false);
+ };
+
+ return (
+
+ {selectOptions.map(round => (
+ - onSelect(round)}>
+ {round}
+
+ ))}
+
+ );
+};
+
+const RoundSelectorBlock = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 26px;
+`;
+
+const Item = styled.button`
+ text-align: center;
+ font-size: 20px;
+`;
+
+export default RoundSelector;
diff --git a/app/_components/analysis/amount/AmountAnalysisWrapper.tsx b/app/_components/analysis/amount/AmountAnalysisWrapper.tsx
new file mode 100644
index 0000000..f937409
--- /dev/null
+++ b/app/_components/analysis/amount/AmountAnalysisWrapper.tsx
@@ -0,0 +1,45 @@
+import NavTabs from '@/_components/common/NavTabs';
+import React from 'react';
+import styled from 'styled-components';
+import PrizeAmountList from './PrizeAmountList';
+import { useSearchParams } from 'next/navigation';
+import PrizeRankAnalysisWrapper from './PrizeRankAnalysisWrapper';
+import { SortOption } from '@/_types/analysis';
+
+type AmountAnalysisWrapperProps = {};
+
+const AmountAnalysisWrapper: React.FC = () => {
+ const searchParams = useSearchParams();
+
+ const tabOptions = [
+ {
+ label: '당첨금액 높은 순',
+ queryParams: 'type',
+ value: 'desc',
+ },
+ {
+ label: '당첨금액 낮은 순',
+ queryParams: 'type',
+ value: 'asc',
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+const AmountAnalysisWrapperBlock = styled.div``;
+
+const Line = styled.div`
+ width: 100%;
+ background-color: #eff3f8;
+ height: 10px;
+`;
+
+export default AmountAnalysisWrapper;
diff --git a/app/_components/analysis/amount/PrizeAmountList.tsx b/app/_components/analysis/amount/PrizeAmountList.tsx
new file mode 100644
index 0000000..806383a
--- /dev/null
+++ b/app/_components/analysis/amount/PrizeAmountList.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import styled from 'styled-components';
+import PrizeAmountListItem from './PrizeAmountListItem';
+import useRankDetail from '@/_hooks/useRankDetail';
+import { useSearchParams } from 'next/navigation';
+import { SortOption } from '@/_types/analysis';
+import { Box, CircularProgress } from '@mui/material';
+import palette from '@/_styles/palette';
+
+type PrizeAmountListProps = {};
+
+const PrizeAmountList: React.FC = () => {
+ const searchParams = useSearchParams();
+ const { rankDetailData, isLoading } = useRankDetail({
+ size: 5,
+ sortOption: (searchParams.get('type') || 'desc') as SortOption,
+ });
+
+ return (
+
+
+
+ 당첨금이 가장 높은 순서대로
+
+ 확인할 수 있어요.
+
+
+
+ {isLoading ? (
+
+
+
+ ) : (
+ rankDetailData.map((rankDetail, i) => (
+
+ ))
+ )}
+
+
+
+ );
+};
+
+const PrizeAmountListBlock = styled.div``;
+
+const PrizeAmountTopBox = styled.div`
+ padding: 31px 20px 27px;
+`;
+
+const Title = styled.p`
+ font-size: 18px;
+ font-weight: 500;
+ text-align: center;
+ line-height: 24px;
+ padding-bottom: 17px;
+`;
+
+const PrizeAmountListBox = styled.ul`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+`;
+
+export default PrizeAmountList;
diff --git a/app/_components/analysis/amount/PrizeAmountListItem.tsx b/app/_components/analysis/amount/PrizeAmountListItem.tsx
new file mode 100644
index 0000000..af50570
--- /dev/null
+++ b/app/_components/analysis/amount/PrizeAmountListItem.tsx
@@ -0,0 +1,76 @@
+import palette from '@/_styles/palette';
+import { RankDeatilResponseType } from '@/_types/analysis';
+import React from 'react';
+import styled from 'styled-components';
+
+type PrizeAmountListItemProps = {
+ isTop?: boolean;
+ index: number;
+ rankDetail: RankDeatilResponseType;
+};
+
+const PrizeAmountListItem: React.FC = ({
+ isTop = false,
+ index,
+ rankDetail,
+}) => {
+ return (
+
+
+ {index}
+ {rankDetail.drwt_no}회
+
+
+
+ {rankDetail.first_win_amount.toLocaleString()}원
+
+
+ {rankDetail.first_win_amount_tax.toLocaleString()}원
+
+
+
+ );
+};
+
+const PrizeAmountListItemBlock = styled.li<{ isTop: boolean }>`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ border-radius: 10px;
+ background: ${({ isTop }) =>
+ isTop ? 'linear-gradient(134deg, #1666ef 32.36%, #6ca7ff 98.43%)' : palette.blue_ligth};
+ padding: 19px 20px 15px;
+`;
+
+const OrderBox = styled.div`
+ display: flex;
+ gap: 16px;
+`;
+
+const OrderNum = styled.p<{ isTop: boolean }>`
+ font-weight: 700;
+ color: ${({ isTop }) => isTop && palette.white};
+`;
+
+const RoundNum = styled.p<{ isTop: boolean }>`
+ color: ${({ isTop }) => isTop && palette.white};
+`;
+
+const AmountBox = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ align-items: flex-end;
+`;
+
+const BeforeTaxAmount = styled.p<{ isTop: boolean }>`
+ font-size: 14px;
+ color: ${({ isTop }) => isTop && palette.white};
+`;
+
+const AfterTaxAmount = styled.p<{ isTop: boolean }>`
+ font-size: 12px;
+ color: ${({ isTop }) => (isTop ? '#A0C3FF' : '#979da6')};
+`;
+
+export default PrizeAmountListItem;
diff --git a/app/_components/analysis/amount/PrizeRankAnalysisWrapper.tsx b/app/_components/analysis/amount/PrizeRankAnalysisWrapper.tsx
new file mode 100644
index 0000000..becae56
--- /dev/null
+++ b/app/_components/analysis/amount/PrizeRankAnalysisWrapper.tsx
@@ -0,0 +1,89 @@
+import Selector from '@/_components/common/Selector';
+import useLatestNumber from '@/_hooks/useLatestNumber';
+import React, { useState } from 'react';
+import styled from 'styled-components';
+import BarChartWrapper from '../chart/BarChartWrapper';
+import useRankNumber from '@/_hooks/useRankNumber';
+import { useSearchParams } from 'next/navigation';
+import { SortOption } from '@/_types/analysis';
+
+type PrizeRankAnalysisWrapperProps = {};
+
+const PrizeRankAnalysisWrapper: React.FC = () => {
+ const searchParams = useSearchParams();
+ const { latestRoundsNumber } = useLatestNumber();
+
+ const [selectedStartRank, setSelectedStartRank] = useState({
+ label: '1등',
+ value: 1,
+ });
+ const [selectedEndRank, setSelectedEndRank] = useState({
+ label: '10등',
+ value: 10,
+ });
+
+ const selectOptions = Array.from({ length: latestRoundsNumber }, (_, index) => ({
+ label: `${index + 1}등`,
+ value: index + 1,
+ }));
+
+ const { rankNumbersData, isLoading } = useRankNumber({
+ startRank: selectedStartRank.value,
+ size: selectedEndRank.value - selectedStartRank.value + 1,
+ rankSortOption: searchParams.get('type') as SortOption,
+ });
+
+ return (
+
+
+ {
+ if (Number(event.target.value) > selectedEndRank.value) {
+ setSelectedEndRank({
+ label: `${event.target.value}등`,
+ value: Number(event.target.value),
+ });
+ }
+ setSelectedStartRank({
+ label: `${event.target.value}등`,
+ value: Number(event.target.value),
+ });
+ }}
+ options={selectOptions}
+ />
+ -
+ {
+ if (Number(event.target.value) < selectedStartRank.value) {
+ setSelectedStartRank({
+ label: `${event.target.value}등`,
+ value: Number(event.target.value),
+ });
+ }
+ setSelectedEndRank({
+ label: `${event.target.value}등`,
+ value: Number(event.target.value),
+ });
+ }}
+ options={selectOptions}
+ />
+
+
+
+ );
+};
+
+const PrizeRankAnalysisWrapperBlock = styled.div`
+ padding: 27px 20px 50px;
+`;
+
+const RoundsAnalysisSelectorBlock = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 35px;
+`;
+
+export default PrizeRankAnalysisWrapper;
diff --git a/app/_components/analysis/chart/BarChartWrapper.tsx b/app/_components/analysis/chart/BarChartWrapper.tsx
index 9900db8..45f8b27 100644
--- a/app/_components/analysis/chart/BarChartWrapper.tsx
+++ b/app/_components/analysis/chart/BarChartWrapper.tsx
@@ -1,51 +1,187 @@
-import React from 'react';
-import { Bar } from 'react-chartjs-2';
-import styled from 'styled-components';
-import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
+import React, { useEffect, useMemo, useState } from 'react';
+import styled, { keyframes } from 'styled-components';
+import ArrowIcon from '@assets/svg/arrow.svg';
import palette from '@/_styles/palette';
+import { Box, CircularProgress } from '@mui/material';
+import { useSearchParams } from 'next/navigation';
-const BarChartWrapperBlock = styled.div``;
-
-type BarChartWrapperProps = {};
-
-const BarChartWrapper: React.FC = () => {
- ChartJS.register(ArcElement, Tooltip);
-
- const data = {
- scales: {
- x: {
- beginAtZero: true,
- grid: {
- display: false, // x축의 그리드 라인 제거
- },
- },
- y: {
- beginAtZero: true,
- },
- },
- indexAxis: 'y',
+type BarChartWrapperProps = {
+ hasSwitch?: boolean;
+ numbers: { no: number; count: number }[];
+ isLoading: boolean;
+};
+
+const START_NUM = 1;
+const END_NUM = 10;
+const MAX_NUM = 45;
+
+const BarChartWrapper: React.FC = ({
+ hasSwitch = false,
+ numbers,
+ isLoading,
+}) => {
+ const [start, setStart] = useState(START_NUM);
+ const [end, setEnd] = useState(END_NUM);
+
+ const handleNumbers = (arrow: 'next' | 'prev') => {
+ if (arrow === 'next') {
+ setStart(end + 1);
+ setEnd(end + END_NUM);
+ return;
+ }
+
+ setStart(start - END_NUM);
+ setEnd(start - 1);
};
+ const filterByNumbers = useMemo(() => {
+ if (numbers.length && hasSwitch) {
+ const sliceNums = numbers.slice(start - 1, end);
+ if (sliceNums.length !== END_NUM) {
+ setEnd(numbers[numbers.length - 1]?.no);
+ }
+ return sliceNums;
+ }
+ return numbers;
+ }, [numbers, hasSwitch, start, end]);
+
+ useEffect(() => {
+ setStart(START_NUM);
+ setEnd(END_NUM);
+ }, [numbers]);
+
return (
-
- {/* */}
+
+ {isLoading ? (
+
+
+
+ ) : (
+ <>
+ {hasSwitch && (
+
+ handleNumbers('prev')}>
+
+
+
+ {start} ~ {end}
+
+ handleNumbers('next')}
+ >
+
+
+
+ )}
+
+ {filterByNumbers.map(num => (
+
+ {num.no}
+
+
+ ))}
+
+ >
+ )}
);
};
+const BarChartWrapperBlock = styled.div`
+ margin: 18px 0;
+`;
+
+const NumberBar = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 19px;
+`;
+
+const Text = styled.p`
+ font-weight: bold;
+ margin: 0 8px;
+`;
+
+const NextButton = styled.button`
+ display: flex;
+ align-items: center;
+ border: none;
+ background-color: transparent;
+
+ &:disabled {
+ cursor: default;
+ path {
+ fill: ${palette.grey_60};
+ }
+ }
+`;
+
+const PrevButton = styled(NextButton)`
+ transform: rotate(-180deg);
+
+ &:disabled {
+ cursor: default;
+ path {
+ fill: ${palette.grey_60};
+ }
+ }
+`;
+
+const BarList = styled.ul`
+ display: flex;
+ flex-direction: column;
+ gap: 27px;
+`;
+
+const BarItem = styled.li`
+ display: flex;
+`;
+
+const Num = styled.span`
+ color: #979da6;
+ font-size: 14px;
+ font-weight: 600;
+ min-width: 19px;
+`;
+
+const barAni = (count: number) => keyframes`
+ from {
+ width: 0;
+ }
+ to {
+ width: ${count}%;
+ }
+`;
+
+const StatisticalBar = styled.div<{ $count: number }>`
+ width: 100%;
+ height: 12px;
+ background-color: #eff3f8;
+ position: relative;
+ border-radius: 6px;
+ overflow: hidden;
+ margin-left: 15px;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: 0;
+ border-radius: 6px;
+ background-color: ${palette.blue_15};
+ animation: ${props => barAni(props.$count)} 0.8s forwards;
+ }
+`;
+
export default BarChartWrapper;
diff --git a/app/_components/analysis/chart/DoughnutChartWrapper.tsx b/app/_components/analysis/chart/DoughnutChartWrapper.tsx
index 9016866..ed25052 100644
--- a/app/_components/analysis/chart/DoughnutChartWrapper.tsx
+++ b/app/_components/analysis/chart/DoughnutChartWrapper.tsx
@@ -2,21 +2,25 @@ import palette from '@styles/palette';
import React from 'react';
import styled from 'styled-components';
import { Doughnut } from 'react-chartjs-2';
-import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js';
import { Chart, registerables } from 'chart.js';
+import { Box, CircularProgress } from '@mui/material';
+
Chart.register(...registerables);
-type DoughnutChartWrapperProps = {};
-const DoughnutChartWrapper: React.FC = () => {
- // ChartJS.register(ArcElement, Tooltip, Legend);
- // Chart.register(CategoryScale);
+type DoughnutChartWrapperProps = {
+ numbers: { no: number; count: number }[];
+ isLoading: boolean;
+};
+
+const DoughnutChartWrapper: React.FC = ({ numbers, isLoading }) => {
+ const mostNumbersList = numbers.slice(0, 6);
const data = {
- labels: [10, 20, 30, 40, 50, 60],
+ labels: mostNumbersList.map(num => num.no),
datasets: [
{
label: 'My First Dataset',
- data: [300, 50, 100, 100, 100, 100],
+ data: mostNumbersList.map(num => num.count),
backgroundColor: [
palette.blue_15,
palette.green_30,
@@ -32,32 +36,71 @@ const DoughnutChartWrapper: React.FC = () => {
};
return (
-
+
가장 많이 출현한
6개의 번호를 확인해보세요.
-
-
-
+
+
+ ) : (
+
+ {
+ let title = ``;
+ return title;
+ },
+ label: function (context) {
+ let label = `${context.raw}회 출현`;
+ return label;
+ },
},
},
},
- },
- }}
- />
-
+ }}
+ />
+
+ )}
);
};
diff --git a/app/_components/analysis/period/PeriodicAnalysisDateBar.tsx b/app/_components/analysis/period/PeriodicAnalysisDateBar.tsx
index b7f12e8..f682b53 100644
--- a/app/_components/analysis/period/PeriodicAnalysisDateBar.tsx
+++ b/app/_components/analysis/period/PeriodicAnalysisDateBar.tsx
@@ -1,38 +1,85 @@
import palette from '@/_styles/palette';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import format from 'date-fns/format';
-import { endOfMonth, set } from 'date-fns';
+import { addMonths, subMonths, parse, addYears } from 'date-fns';
import Link from 'next/link';
-import { useSearchParams } from 'next/navigation';
+import { usePathname, useSearchParams } from 'next/navigation';
import ArrowIcon from '@assets/svg/arrow.svg';
+import RefreshIcon from '@assets/svg/refresh.svg';
import DateFilterIcon from '@assets/svg/dateFilter.svg';
+import { useRouter } from 'next/navigation';
+import { subYears } from 'date-fns/esm';
-type PeriodicAnalysisDateBarProps = {
- type: 'month' | 'year';
-};
+type PeriodicAnalysisDateBarProps = {};
-const PeriodicAnalysisDateBar: React.FC = ({ type }) => {
+const PeriodicAnalysisDateBar: React.FC = () => {
+ const router = useRouter();
const searchParams = useSearchParams();
- const year = 2023; //test
- const month = 8; //test
- const dateInSpecificMonth = set(new Date(), { year, month: month - 1 });
+ const pathname = usePathname();
+ const newParams = new URLSearchParams(searchParams.toString());
+
+ const categoryMode = searchParams.get('category');
+ const startDt = searchParams.get('startDt');
+ const endDt = searchParams.get('endDt');
+ const isMonthMode = categoryMode === 'month' ? 'yyyyMM' : 'yyyy';
+ const targetFormatDate = format(new Date(), isMonthMode);
+ const [currenEndDt, setCurrenEndDt] = useState(endDt);
+
+ useEffect(() => {
+ setCurrenEndDt(endDt || targetFormatDate);
+ }, [categoryMode, targetFormatDate, endDt]);
+
+ const handleDateNavigation = ({ type }: { type: 'prev' | 'next' }) => {
+ const targetDate = parse(currenEndDt || targetFormatDate, isMonthMode, new Date());
+
+ const isPrev = categoryMode === 'month' ? subMonths(targetDate, 1) : subYears(targetDate, 1);
+ const isNext = categoryMode === 'month' ? addMonths(targetDate, 1) : addYears(targetDate, 1);
+
+ const controlDt = type === 'prev' ? isPrev : isNext;
+ setCurrenEndDt(format(controlDt, isMonthMode));
+ newParams.set('endDt', format(controlDt, isMonthMode));
+ router.push(`${pathname}?${newParams.toString()}`);
+ };
return (
+ {
+ setCurrenEndDt(targetFormatDate);
+ router.push(`${pathname}?&category=${categoryMode || 'month'}&endDt=${targetFormatDate}`);
+ }}
+ isVisible={!!startDt}
+ >
+
+
-
-
-
-
- {format(dateInSpecificMonth, 'yyyy.MM.01')} ~{' '}
- {format(endOfMonth(dateInSpecificMonth), 'yyyy.MM.dd')}
-
-
-
-
+ {!startDt && (
+ handleDateNavigation({ type: 'prev' })}>
+
+
+ )}
+ {startDt ? (
+
+ {startDt} ~ {currenEndDt}
+
+ ) : (
+ {currenEndDt}
+ )}
+ {!startDt && (
+ handleDateNavigation({ type: 'next' })}
+ disabled={currenEndDt === targetFormatDate}
+ >
+
+
+ )}
-
+
@@ -41,32 +88,43 @@ const PeriodicAnalysisDateBar: React.FC = ({ type
const PeriodicAnalysisDateBarBlock = styled.div`
display: flex;
- justify-content: center;
- padding: 14px 0;
+ padding: 12px 20px;
background-color: ${palette.grey_70};
- position: relative;
+ justify-content: space-between;
+ font-size: 14px;
`;
const NextButton = styled.button`
display: flex;
align-items: center;
- border: none;
- background-color: transparent;
+
+ &:disabled {
+ svg {
+ path {
+ fill: ${palette.grey_50};
+ }
+ }
+ }
`;
const PrevButton = styled(NextButton)`
transform: rotate(-180deg);
`;
+const CurrentDate = styled.p`
+ font-weight: bold;
+ margin: 0 8px;
+`;
+
const AnalysisDateBarBox = styled.div`
display: flex;
align-items: center;
- font-size: 14px;
`;
-const AnalysisDatePickerBox = styled(Link)`
- position: absolute;
- right: 22px;
+const AnalysisDatePickerBox = styled(Link)``;
+
+const AnalysisDateRefreshBox = styled.button<{ isVisible: boolean }>`
+ visibility: ${({ isVisible }) => !isVisible && 'hidden'};
`;
export default PeriodicAnalysisDateBar;
diff --git a/app/_components/analysis/period/PeriodicAnalysisHub.tsx b/app/_components/analysis/period/PeriodicAnalysisHub.tsx
index 92e01c3..ac8b911 100644
--- a/app/_components/analysis/period/PeriodicAnalysisHub.tsx
+++ b/app/_components/analysis/period/PeriodicAnalysisHub.tsx
@@ -2,32 +2,77 @@
import React from 'react';
import styled from 'styled-components';
-import PeriodicAnalysisTabs from './PeriodicAnalysisTabs';
-import PeriodicAnalysisDateBar from './PeriodicAnalysisDateBar';
-import { useSearchParams } from 'next/navigation';
import DoughnutChartWrapper from '../chart/DoughnutChartWrapper';
import BarChartWrapper from '../chart/BarChartWrapper';
+import PeriodicAnalysisDateBar from './PeriodicAnalysisDateBar';
+import { useSearchParams } from 'next/navigation';
+import usePeriodNumber from '@/_hooks/usePeriodNumber';
+import NavTabs from '@/_components/common/NavTabs';
+import { SortType } from '@/_types/analysis';
+import ToggleSwitch from '@/_components/common/ToggleSwitch';
type PeriodicAnalysisHubProps = {};
const PeriodicAnalysisHub: React.FC = () => {
const searchParams = useSearchParams();
+ const { periodNumbers: periodNumbersByDesc, isLoading } = usePeriodNumber({
+ sortOption: 'desc',
+ sortType: 'COUNT',
+ });
+ const { periodNumbers: periodNumbersByAsc, isLoading: isLoadingByAsc } = usePeriodNumber({
+ sortOption: searchParams.get('sortType') === 'COUNT' ? 'desc' : 'asc',
+ sortType: searchParams.get('sortType') as SortType,
+ });
return (
-
-
+
-
-
+
+
+
);
};
-const PeriodicAnalysisHubBlock = styled.div``;
+const PeriodicAnalysisHubBlock = styled.div`
+ .doughnut-chart {
+ margin-bottom: 42px;
+ }
+ .bar-chart {
+ margin-top: 30px;
+ }
+`;
const ChartWrapper = styled.div`
padding: 24px 20px;
diff --git a/app/_components/analysis/period/PeriodicAnalysisTabs.tsx b/app/_components/analysis/period/PeriodicAnalysisTabs.tsx
deleted file mode 100644
index 6bdeb85..0000000
--- a/app/_components/analysis/period/PeriodicAnalysisTabs.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-'use client';
-
-import palette from '@/_styles/palette';
-import Link from 'next/link';
-import React from 'react';
-import styled from 'styled-components';
-import { useSearchParams } from 'next/navigation';
-
-type PeriodicAnalysisTabsProps = {};
-
-const PeriodicAnalysisTabs: React.FC = () => {
- const searchParams = useSearchParams();
-
- return (
-
-
- 월별
-
-
- 연도별
-
-
- );
-};
-
-const PeriodicAnalysisTabsBlock = styled.div`
- display: flex;
-`;
-
-const PeriodicAnalysisTabLink = styled(Link)<{ isFocused: boolean }>`
- padding: 13px 0;
- color: ${({ isFocused }) => (isFocused ? palette.black : palette.grey_40)};
- font-weight: ${({ isFocused }) => (isFocused ? 'bold' : 'normal')};
- font-size: 14px;
- background-color: ${palette.white};
- text-decoration: none;
- flex: 1;
- text-align: center;
- //animation 추후 추가
- border-bottom: 2px solid ${({ isFocused }) => (isFocused ? palette.black : palette.grey_60)};
-`;
-
-export default PeriodicAnalysisTabs;
diff --git a/app/_components/analysis/period/filter/MonthPickerFilter.tsx b/app/_components/analysis/period/filter/MonthPickerFilter.tsx
new file mode 100644
index 0000000..786719e
--- /dev/null
+++ b/app/_components/analysis/period/filter/MonthPickerFilter.tsx
@@ -0,0 +1,192 @@
+import React, { useState } from 'react';
+import styled from 'styled-components';
+import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
+import palette from '@/_styles/palette';
+import { ko } from 'date-fns/locale';
+import { format, parse } from 'date-fns';
+import ArrowIcon from '@assets/svg/arrow.svg';
+import { Button } from '@/_components/common';
+import { useRouter, useSearchParams } from 'next/navigation';
+
+type MonthPickerFilterTestProps = {};
+
+const MonthPickerFilterTest: React.FC = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const startDt = searchParams.get('startDt') || format(new Date(), 'yyyyMM');
+ const endDt = searchParams.get('endDt') || format(new Date(), 'yyyyMM');
+
+ const [selectedStartDate, setSelectedStartDate] = useState(
+ parse(startDt, 'yyyyMM', new Date()),
+ );
+ const [selectedEndDate, setSelectedEndDate] = useState(parse(endDt, 'yyyyMM', new Date()));
+
+ const rangeMonthPicker = [
+ {
+ title: '시작일',
+ value: selectedStartDate,
+ },
+ {
+ title: '종료일',
+ value: selectedEndDate,
+ },
+ ];
+
+ return (
+
+ {rangeMonthPicker.map((monthPicker, i) => (
+
+ {monthPicker.title}
+ {
+ if (!date) return;
+ monthPicker.title === '종료일'
+ ? setSelectedEndDate(date)
+ : setSelectedStartDate(date);
+ }}
+ selectsStart={!(monthPicker.title === '종료일')}
+ selectsEnd={monthPicker.title === '종료일'}
+ startDate={selectedStartDate}
+ endDate={selectedEndDate}
+ dateFormat="yyyyMM"
+ showMonthYearPicker
+ inline
+ minDate={monthPicker.title === '종료일' ? selectedStartDate : null}
+ maxDate={new Date()}
+ showFourColumnMonthYearPicker
+ renderCustomHeader={({
+ date,
+ decreaseYear,
+ increaseYear,
+ prevMonthButtonDisabled,
+ nextMonthButtonDisabled,
+ }) => (
+
+
+
+
+ {format(new Date(date), 'yyyy')}
+
+
+
+
+ )}
+ />
+
+ ))}
+
+
+ );
+};
+
+const MonthPickerFilterTestBlock = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 64px;
+ padding: 32px 20px;
+
+ .react-datepicker {
+ border: none;
+ width: 100%;
+
+ .react-datepicker__header {
+ background-color: transparent;
+ border: none;
+ padding: 0;
+ }
+
+ .react-datepicker__month-container {
+ width: inherit;
+
+ .react-datepicker__monthPicker {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin: 0;
+
+ .react-datepicker__month-wrapper {
+ display: flex;
+ gap: 8px;
+ width: max-content;
+ }
+
+ //? default month
+ .react-datepicker__month-text {
+ padding: 14px 7px;
+ border-radius: 8px;
+ background-color: ${palette.grey_70};
+ color: ${palette.grey_20};
+ margin: 0;
+ }
+
+ //? selected range month
+ .react-datepicker__month-text--in-range {
+ background-color: #c9dbfa; //? test color
+ color: ${palette.white};
+ }
+
+ //? selected month
+ .react-datepicker__month-text--selected {
+ background-color: ${palette.blue_30};
+ color: ${palette.white};
+ }
+
+ //? disabled month
+ .react-datepicker__month-text--disabled {
+ background-color: transparent;
+ color: ${palette.grey_50};
+ }
+ }
+ }
+ }
+`;
+
+const MonthHeader = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16px;
+ margin-bottom: 16px;
+`;
+
+const ArrowNextButton = styled.button`
+ display: flex;
+ align-items: center;
+`;
+
+const ArrowPrevButton = styled(ArrowNextButton)`
+ transform: rotate(-180deg);
+`;
+
+const YearText = styled.span`
+ font-size: 18px;
+ font-weight: bold;
+`;
+
+const MonthBox = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const Title = styled.p`
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 8px;
+`;
+
+export default MonthPickerFilterTest;
diff --git a/app/_components/analysis/period/filter/PeriodicDateFilterWrapper.tsx b/app/_components/analysis/period/filter/PeriodicDateFilterWrapper.tsx
new file mode 100644
index 0000000..8b141b8
--- /dev/null
+++ b/app/_components/analysis/period/filter/PeriodicDateFilterWrapper.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import React from 'react';
+import styled from 'styled-components';
+import MonthPickerFilter from './MonthPickerFilter';
+import { useSearchParams } from 'next/navigation';
+import YearPickerFilter from './YearPickerFilter';
+
+type PeriodicDateFilterProps = {};
+
+const PeriodicDateFilter: React.FC = () => {
+ const searchParams = useSearchParams();
+ const categoryMode = searchParams.get('category');
+
+ return (
+
+ {categoryMode === 'month' ? : }
+
+ );
+};
+
+const PeriodicDateFilterBlock = styled.div`
+ min-height: calc(100vh - 10.8rem);
+`;
+
+export default PeriodicDateFilter;
diff --git a/app/_components/analysis/period/filter/YearPickerFilter.tsx b/app/_components/analysis/period/filter/YearPickerFilter.tsx
new file mode 100644
index 0000000..f4be241
--- /dev/null
+++ b/app/_components/analysis/period/filter/YearPickerFilter.tsx
@@ -0,0 +1,193 @@
+import React, { useState } from 'react';
+import styled from 'styled-components';
+import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
+import palette from '@/_styles/palette';
+import { ko } from 'date-fns/locale';
+import { format } from 'date-fns';
+import ArrowIcon from '@assets/svg/arrow.svg';
+import { Button } from '@/_components/common';
+import { useRouter, useSearchParams } from 'next/navigation';
+
+type YearPickerFilterProps = {};
+
+const YearPickerFilter: React.FC = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const startDt = searchParams.get('startDt');
+ const endDt = searchParams.get('endDt');
+
+ const [selectedStartDate, setSelectedStartDate] = useState(new Date(`${startDt}`));
+ const [selectedEndDate, setSelectedEndDate] = useState(new Date(`${endDt}`));
+
+ const rangeYearPicker = [
+ {
+ title: '시작일',
+ value: selectedStartDate,
+ },
+ {
+ title: '종료일',
+ value: selectedEndDate,
+ },
+ ];
+
+ //! 타이틀수정
+ return (
+
+ {rangeYearPicker.map((yearPicker, i) => (
+
+ {yearPicker.title}
+ {
+ if (!date) return;
+ yearPicker.title === '종료일' ? setSelectedEndDate(date) : setSelectedStartDate(date);
+ }}
+ selectsStart={!(yearPicker.title === '종료일')}
+ selectsEnd={yearPicker.title === '종료일'}
+ startDate={selectedStartDate}
+ endDate={selectedEndDate}
+ dateFormat="yyyy"
+ showYearPicker
+ inline
+ minDate={yearPicker.title === '종료일' ? selectedStartDate : null}
+ maxDate={new Date()}
+ showFourColumnMonthYearPicker
+ renderCustomHeader={({
+ date,
+ decreaseYear,
+ increaseYear,
+ prevYearButtonDisabled,
+ nextYearButtonDisabled,
+ customHeaderCount,
+ }) => (
+
+
+
+
+
+ {yearPicker.title === '종료일'
+ ? format(selectedEndDate, 'yyyy')
+ : format(selectedStartDate, 'yyyy')}
+
+
+
+
+
+ )}
+ />
+
+ ))}
+
+
+ );
+};
+
+const YearPickerFilterBlock = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 64px;
+ padding: 32px 20px;
+
+ .react-datepicker {
+ border: none;
+ width: 100%;
+
+ .react-datepicker__header {
+ background-color: transparent;
+ border: none;
+ padding: 0;
+ }
+
+ .react-datepicker__year--container {
+ width: inherit;
+
+ .react-datepicker__year {
+ margin: 0;
+ }
+
+ .react-datepicker__year-wrapper {
+ display: flex;
+ gap: 7px;
+ width: max-content;
+ flex-wrap: wrap;
+ max-width: 100%;
+ }
+
+ //? default year
+ .react-datepicker__year-text {
+ padding: 14px 7px;
+ border-radius: 8px;
+ background-color: ${palette.grey_70};
+ color: ${palette.grey_20};
+ margin: 0;
+ }
+
+ //? selected range year
+ .react-datepicker__year-text--in-range {
+ background-color: #c9dbfa; //? test color
+ color: ${palette.white};
+ }
+
+ //? selected year
+ .react-datepicker__year-text--selected {
+ background-color: ${palette.blue_30};
+ color: ${palette.white};
+ }
+
+ //? disabled year
+ .react-datepicker__year-text--disabled {
+ background-color: transparent;
+ color: ${palette.grey_50};
+ }
+ }
+ }
+`;
+
+const MonthHeader = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16px;
+ margin-bottom: 16px;
+`;
+
+const ArrowNextButton = styled.button`
+ display: flex;
+ align-items: center;
+`;
+
+const ArrowPrevButton = styled(ArrowNextButton)`
+ transform: rotate(-180deg);
+`;
+
+const YearText = styled.span`
+ font-size: 18px;
+ font-weight: bold;
+`;
+
+const MonthBox = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const Title = styled.p`
+ font-size: 20px;
+ font-weight: bold;
+ margin-bottom: 8px;
+`;
+
+export default YearPickerFilter;
diff --git a/app/_components/analysis/period/pick/PeriodicDatePicker.tsx b/app/_components/analysis/period/pick/PeriodicDatePicker.tsx
deleted file mode 100644
index 5fba683..0000000
--- a/app/_components/analysis/period/pick/PeriodicDatePicker.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-'use client';
-
-import React from 'react';
-import styled from 'styled-components';
-
-type PeriodicDatePickerProps = {};
-
-const PeriodicDatePicker: React.FC = () => {
- return (
-
- 날짜 선택
-
- );
-};
-
-const PeriodicDatePickerBlock = styled.div``;
-
-export default PeriodicDatePicker;
diff --git a/app/_components/analysis/rounds/RoundsAnalysisWrapper.tsx b/app/_components/analysis/rounds/RoundsAnalysisWrapper.tsx
new file mode 100644
index 0000000..94dac37
--- /dev/null
+++ b/app/_components/analysis/rounds/RoundsAnalysisWrapper.tsx
@@ -0,0 +1,105 @@
+'use client';
+
+import React, { useState } from 'react';
+import styled from 'styled-components';
+import Selector from '@/_components/common/Selector';
+import useLatestNumber from '@/_hooks/useLatestNumber';
+import useRoundsNumber from '@/_hooks/useRoundsNumber';
+import DoughnutChartWrapper from '../chart/DoughnutChartWrapper';
+import BarChartWrapper from '../chart/BarChartWrapper';
+
+type RoundsAnalysisWrapperProps = {};
+
+const RoundsAnalysisWrapper: React.FC = () => {
+ const { latestRoundsNumber } = useLatestNumber();
+
+ //? 1072 최신회차 논의 필요
+ const [selectedStartRound, setSelectedStartRound] = useState<{ label: string; value: number }>({
+ label: `${latestRoundsNumber || 1072}회`,
+ value: latestRoundsNumber || 1072,
+ });
+ const [selectedEndRound, setSelectedEndRound] = useState({
+ label: `${latestRoundsNumber || 1072}회`,
+ value: latestRoundsNumber || 1072,
+ });
+
+ const selectOptions = Array.from({ length: latestRoundsNumber }, (_, index) => ({
+ label: `${latestRoundsNumber - index}회`,
+ value: latestRoundsNumber - index,
+ }));
+
+ const { roundNumbersData, isLoading } = useRoundsNumber({
+ startNo: selectedStartRound.value,
+ endNo: selectedEndRound.value,
+ sortType: 'COUNT',
+ sortOption: 'desc',
+ });
+
+ const { roundNumbersData: roundNumbersDataByAsc, isLoading: isLoadingByAsc } = useRoundsNumber({
+ startNo: selectedStartRound.value,
+ endNo: selectedEndRound.value,
+ sortOption: 'asc',
+ });
+
+ return (
+
+
+ {
+ if (Number(event.target.value) > selectedEndRound.value) {
+ setSelectedEndRound({
+ label: `${event.target.value}회`,
+ value: Number(event.target.value),
+ });
+ }
+ setSelectedStartRound({
+ label: `${event.target.value}회`,
+ value: Number(event.target.value),
+ });
+ }}
+ options={selectOptions}
+ />
+ -
+ {
+ if (Number(event.target.value) < selectedStartRound.value) {
+ setSelectedStartRound({
+ label: `${event.target.value}회`,
+ value: Number(event.target.value),
+ });
+ }
+ setSelectedEndRound({
+ label: `${event.target.value}회`,
+ value: Number(event.target.value),
+ });
+ }}
+ options={selectOptions}
+ />
+
+
+
+
+
+ );
+};
+
+const RoundsAnalysisWrapperBlock = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 6px 20px;
+`;
+
+const RoundsAnalysisSelectorBlock = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 18px;
+`;
+
+export default RoundsAnalysisWrapper;
diff --git a/app/_components/common/BottomSheet.tsx b/app/_components/common/BottomSheet.tsx
index 5db8b65..1392530 100644
--- a/app/_components/common/BottomSheet.tsx
+++ b/app/_components/common/BottomSheet.tsx
@@ -4,6 +4,7 @@ import useMediaQuery from '@mui/material/useMediaQuery';
import palette from '@styles/palette';
import type { PropsWithChildren } from 'react';
import { styled } from 'styled-components';
+import CloseIconSVG from '@assets/svg/close.svg';
const CUSTOM_STYLE = {
margin: '0 auto',
@@ -32,6 +33,7 @@ interface BottomSheetProps {
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
+ title?: string;
}
const BottomSheet = ({
@@ -39,6 +41,7 @@ const BottomSheet = ({
isOpen,
onOpen,
onClose,
+ title,
}: PropsWithChildren) => {
const isMobile = useMediaQuery('(max-width:576px)');
@@ -51,6 +54,15 @@ const BottomSheet = ({
sx={isMobile ? MOBILE_CUSTOM_STYLE : DESKTOP_CUSTOM_STYLE}
>
+ {title && (
+
+ )}
{children}
);
@@ -69,6 +81,30 @@ const HandleBar = styled(Box)`
background-color: ${palette.grey_60};
`;
+const Header = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ margin-bottom: 32px;
+`;
+
+const Title = styled.p`
+ font-size: 18px;
+ font-weight: 600;
+`;
+
+const CloseButton = styled.button`
+ position: absolute;
+ right: 20px;
+`;
+
+const CloseIcon = styled(CloseIconSVG)`
+ path {
+ fill: ${palette.black};
+ }
+`;
+
const BottomSheetBody = styled.div`
overflow-x: hidden;
overflow-y: auto;
diff --git a/app/_components/common/Button.tsx b/app/_components/common/Button.tsx
index 0e838ab..fe981bc 100644
--- a/app/_components/common/Button.tsx
+++ b/app/_components/common/Button.tsx
@@ -3,6 +3,7 @@ import type { ComponentPropsWithoutRef, CSSProperties, PropsWithChildren } from
import { styled } from 'styled-components';
const SIZE_STYLE = {
+ small: '4.5rem',
medium: '9.6rem',
full: '100%',
} as const;
diff --git a/app/_components/common/NavTabs.tsx b/app/_components/common/NavTabs.tsx
new file mode 100644
index 0000000..d59b100
--- /dev/null
+++ b/app/_components/common/NavTabs.tsx
@@ -0,0 +1,53 @@
+import React, { useState } from 'react';
+import Tabs from '@mui/material/Tabs';
+import Tab from '@mui/material/Tab';
+import palette from '@/_styles/palette';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+
+type NavTabsProps = {
+ tabOptions: { label: string; queryParams: string; value: string }[];
+};
+
+const NavTabs: React.FC = ({ tabOptions }) => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const pathname = usePathname();
+ const [value, setValue] = useState(searchParams.get(tabOptions[0].queryParams));
+
+ const handleChange = (event: React.SyntheticEvent, newValue: string) => {
+ setValue(newValue);
+ };
+
+ return (
+
+ {tabOptions.map((tab, i) => (
+ {
+ router.push(`${pathname}?${tab.queryParams}=${tab.value}`);
+ }}
+ sx={{
+ '&.Mui-selected': {
+ fontWeight: 700,
+ },
+ }}
+ />
+ ))}
+
+ );
+};
+
+export default NavTabs;
diff --git a/app/_components/common/Selector.tsx b/app/_components/common/Selector.tsx
new file mode 100644
index 0000000..dc09d28
--- /dev/null
+++ b/app/_components/common/Selector.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import styled from 'styled-components';
+import Select, { SelectChangeEvent } from '@mui/material/Select';
+import MenuItem from '@mui/material/MenuItem';
+import palette from '@/_styles/palette';
+
+type SelectorProps = {
+ selectOption: { label: string; value: number };
+ onChange: (e: SelectChangeEvent) => void;
+ options: { label: string; value: number }[];
+};
+
+const Selector: React.FC = ({ selectOption, onChange, options }) => {
+ return (
+
+ {options.map((option, i) => (
+
+ ))}
+
+ );
+};
+
+const SelectorBlock = styled(Select)`
+ &::-webkit-scrollbar {
+ width: '120px'; // 스크롤바의 너비를 조절할 수 있습니다.
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: '#888'; // 스크롤바 색상을 설정할 수 있습니다.
+ border-radius: '6px'; // 스크롤바의 모서리를 둥글게 만들 수 있습니다.
+ }
+`;
+
+export default Selector;
diff --git a/app/_hooks/useLatestNumber.tsx b/app/_hooks/useLatestNumber.tsx
new file mode 100644
index 0000000..5002c26
--- /dev/null
+++ b/app/_hooks/useLatestNumber.tsx
@@ -0,0 +1,31 @@
+import instance from '@/_apis/core';
+import { LatestNumberResponseType } from '@/_types/analysis';
+import { useQuery } from '@tanstack/react-query';
+import { AxiosResponse } from 'axios';
+
+const useLatestNumber = () => {
+ const fetcher = async () => {
+ try {
+ const data = await instance.get(
+ `/api/number/latest`,
+ );
+ return data;
+ } catch (err) {
+ console.log('err', err);
+ }
+ };
+
+ const { data, error, isFetching, isLoading } = useQuery(['latestNumberData'], fetcher, {
+ retry: 0,
+ });
+
+ return {
+ latestNumbers: data,
+ latestRoundsNumber: data?.drwt_no!!,
+ isLoading,
+ error,
+ isFetching,
+ };
+};
+
+export default useLatestNumber;
diff --git a/app/_hooks/usePeriodNumber.tsx b/app/_hooks/usePeriodNumber.tsx
new file mode 100644
index 0000000..b2968e6
--- /dev/null
+++ b/app/_hooks/usePeriodNumber.tsx
@@ -0,0 +1,56 @@
+import instance from '@/_apis/core';
+import { NumberResponseType, SortOption, SortType, PeriodParamType } from '@/_types/analysis';
+import { useQuery } from '@tanstack/react-query';
+import { AxiosResponse } from 'axios';
+import { format } from 'date-fns';
+import { useSearchParams } from 'next/navigation';
+
+const usePeriodNumber = ({
+ sortOption,
+ sortType,
+}: Pick) => {
+ const searchParams = useSearchParams();
+ const monthFormatDate = format(new Date(), 'yyyyMM');
+ const yearFormatDate = format(new Date(), 'yyyy');
+ const category = (searchParams.get('category') || 'month') as 'year' | 'month';
+ const startDt = searchParams.get('startDt');
+ const endDt = searchParams.get('endDt');
+
+ const params = {
+ month: {
+ startDt: startDt || endDt || monthFormatDate,
+ endDt: endDt || monthFormatDate,
+ },
+ year: {
+ startDt: `${startDt || endDt || yearFormatDate}01`,
+ endDt: `${endDt || yearFormatDate}12`,
+ },
+ };
+
+ const fetcher = async () => {
+ try {
+ const data = await instance.get(`/api/statics/period`, {
+ params: {
+ ...params[category],
+ sortOption: sortOption || (searchParams.get('sortOption') as SortOption) || 'asc',
+ sortType: sortType || (searchParams.get('sortType') as SortType) || 'NO',
+ },
+ });
+ return data;
+ } catch (err) {
+ console.log('err', err);
+ }
+ };
+
+ const { data, error, isFetching, isLoading } = useQuery(
+ ['PeriodNumberData', { category, sortOption, startDt, endDt, sortType }],
+ fetcher,
+ {
+ retry: 0,
+ },
+ );
+
+ return { periodNumbers: data || [], isLoading, error, isFetching };
+};
+
+export default usePeriodNumber;
diff --git a/app/_hooks/useRankDetail.tsx b/app/_hooks/useRankDetail.tsx
new file mode 100644
index 0000000..391b038
--- /dev/null
+++ b/app/_hooks/useRankDetail.tsx
@@ -0,0 +1,32 @@
+import instance from '@/_apis/core';
+import { RankDeatilResponseType, RankDetailParamsType } from '@/_types/analysis';
+import { useQuery } from '@tanstack/react-query';
+import { AxiosResponse } from 'axios';
+
+const useRankDetail = ({ size, sortOption = 'desc' }: RankDetailParamsType) => {
+ const params = {
+ size,
+ sortOption,
+ };
+
+ const fetcher = async () => {
+ try {
+ const data = await instance.get(
+ `/api/statics/rank/detail`,
+ {
+ params,
+ },
+ );
+ return data;
+ } catch (err) {
+ console.log('err', err);
+ return null;
+ }
+ };
+
+ const { data, isLoading } = useQuery([`rankDetailData_${sortOption}`, sortOption], fetcher);
+
+ return { rankDetailData: data || [], isLoading };
+};
+
+export default useRankDetail;
diff --git a/app/_hooks/useRankNumber.tsx b/app/_hooks/useRankNumber.tsx
new file mode 100644
index 0000000..341226b
--- /dev/null
+++ b/app/_hooks/useRankNumber.tsx
@@ -0,0 +1,38 @@
+import instance from '@/_apis/core';
+import { NumberResponseType, RankNumbersParamsType } from '@/_types/analysis';
+import { useQuery } from '@tanstack/react-query';
+import { AxiosResponse } from 'axios';
+
+const useRankNumber = ({
+ startRank,
+ size,
+ rankSortOption = 'desc',
+ sortOption = 'asc',
+ sortType = 'NO',
+}: RankNumbersParamsType) => {
+ const params = {
+ startRank,
+ size,
+ rankSortOption,
+ sortOption,
+ sortType,
+ };
+
+ const fetcher = async () => {
+ try {
+ const data = await instance.get(`/api/statics/rank`, {
+ params,
+ });
+ return data;
+ } catch (err) {
+ console.log('err', err);
+ return null;
+ }
+ };
+
+ const { data, isLoading } = useQuery([`rankNumbersData_${sortOption}`, params], fetcher);
+
+ return { rankNumbersData: data || [], isLoading };
+};
+
+export default useRankNumber;
diff --git a/app/_hooks/useRoundsNumber.tsx b/app/_hooks/useRoundsNumber.tsx
new file mode 100644
index 0000000..17ab819
--- /dev/null
+++ b/app/_hooks/useRoundsNumber.tsx
@@ -0,0 +1,36 @@
+import instance from '@/_apis/core';
+import { NumberResponseType, RoundNumbersParamsType } from '@/_types/analysis';
+import { useQuery } from '@tanstack/react-query';
+import { AxiosResponse } from 'axios';
+
+const useRoundsNumber = ({
+ startNo,
+ endNo,
+ sortOption = 'asc',
+ sortType = 'NO',
+}: RoundNumbersParamsType) => {
+ const params = {
+ startNo,
+ endNo,
+ sortOption,
+ sortType,
+ };
+
+ const fetcher = async () => {
+ try {
+ const data = await instance.get(`/api/statics/number`, {
+ params,
+ });
+ return data;
+ } catch (err) {
+ console.log('err', err);
+ return null;
+ }
+ };
+
+ const { data, isLoading } = useQuery([`roundNumbersData_${sortOption}`, params], fetcher);
+
+ return { roundNumbersData: data || [], isLoading };
+};
+
+export default useRoundsNumber;
diff --git a/app/_types/analysis.ts b/app/_types/analysis.ts
new file mode 100644
index 0000000..e3e0bfc
--- /dev/null
+++ b/app/_types/analysis.ts
@@ -0,0 +1,61 @@
+export interface LatestNumberResponseType {
+ drwt_no: number;
+ drwt_date: string;
+ drwt_no1: number;
+ drwt_no2: number;
+ drwt_no3: number;
+ drwt_no4: number;
+ drwt_no5: number;
+ drwt_no6: number;
+ bnus_no: number;
+ tot_sell_amount: number;
+ first_win_amount: number;
+ first_win_count: number;
+ first_tot_amount: number;
+}
+export interface NumberResponseType {
+ no: number;
+ count: number;
+}
+
+export interface RankDeatilResponseType {
+ drwt_no: number;
+ first_win_amount: number;
+ first_win_amount_tax: number;
+}
+
+export type SortOption = 'asc' | 'desc'; //? 정렬옵션
+
+export type SortType = 'NO' | 'COUNT'; //? 정렬구분
+
+export interface PeriodParamType {
+ startDt: string;
+ endDt: string;
+ sortOption: SortOption;
+ sortType?: SortType;
+}
+
+export interface PeriodNumbersParamsType {
+ month: PeriodParamType;
+ year: PeriodParamType;
+}
+
+export interface RankNumbersParamsType {
+ startRank: number; //? 시작 등수
+ size: number; //? 조회 개수
+ rankSortOption?: SortOption; //? 랭크 정렬옵션
+ sortOption?: SortOption;
+ sortType?: SortType;
+}
+
+export interface RankDetailParamsType {
+ size: number;
+ sortOption?: SortOption;
+}
+
+export interface RoundNumbersParamsType {
+ startNo: number;
+ endNo: number;
+ sortType?: SortType;
+ sortOption?: SortOption;
+}
diff --git a/package.json b/package.json
index 302952d..83b89e4 100644
--- a/package.json
+++ b/package.json
@@ -22,17 +22,20 @@
"next": "13.4.13",
"react": "18.2.0",
"react-chartjs-2": "^5.2.0",
+ "react-datepicker": "^4.16.0",
"react-dom": "18.2.0",
"react-kakao-maps-sdk": "^1.1.11",
"react-query": "^3.39.3",
"styled-components": "^6.0.7",
"swiper": "^10.1.0",
+ "typescript": "5.1.6",
"zustand": "^4.4.1"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "^4.32.5",
"@types/node": "^20.4.9",
"@types/react": "^18.2.20",
+ "@types/react-datepicker": "^4.15.0",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
diff --git a/public/assets/images/analysisMain.png b/public/assets/images/analysisMain.png
index 134a4ee..77678a0 100644
Binary files a/public/assets/images/analysisMain.png and b/public/assets/images/analysisMain.png differ
diff --git a/public/assets/svg/Icon.svg b/public/assets/svg/Icon.svg
new file mode 100644
index 0000000..93b73a5
--- /dev/null
+++ b/public/assets/svg/Icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/svg/arrowDropDown.svg b/public/assets/svg/arrowDropDown.svg
new file mode 100644
index 0000000..765d9a6
--- /dev/null
+++ b/public/assets/svg/arrowDropDown.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/svg/calendarFilter.svg b/public/assets/svg/calendarFilter.svg
new file mode 100644
index 0000000..85a6a37
--- /dev/null
+++ b/public/assets/svg/calendarFilter.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/svg/dash.svg b/public/assets/svg/dash.svg
new file mode 100644
index 0000000..7ad448c
--- /dev/null
+++ b/public/assets/svg/dash.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/assets/svg/refresh.svg b/public/assets/svg/refresh.svg
new file mode 100644
index 0000000..8306097
--- /dev/null
+++ b/public/assets/svg/refresh.svg
@@ -0,0 +1,5 @@
+
diff --git a/yarn.lock b/yarn.lock
index 3dd2ebe..74a58bd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1444,7 +1444,7 @@
picocolors "^1.0.0"
tslib "^2.6.0"
-"@popperjs/core@^2.11.8":
+"@popperjs/core@^2.11.8", "@popperjs/core@^2.9.2":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
@@ -1641,6 +1641,16 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
+"@types/react-datepicker@^4.15.0":
+ version "4.15.0"
+ resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.15.0.tgz#24a9c03e79ab4b232b346edd006cfb6060b0fb43"
+ integrity sha512-kr10s8ex4+MmCJmzdhA9kfmoMQBmsW5uDYDlH+8f/PgStrp7rRaz23Y/cvTiMgvESVq8ujDh4SOo6jlSwEw13g==
+ dependencies:
+ "@popperjs/core" "^2.9.2"
+ "@types/react" "*"
+ date-fns "^2.0.1"
+ react-popper "^2.2.5"
+
"@types/react-dom@^18.2.7":
version "18.2.7"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63"
@@ -2125,6 +2135,11 @@ chokidar@^3.4.0:
optionalDependencies:
fsevents "~2.3.2"
+classnames@^2.2.6:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
+ integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
+
client-only@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
@@ -2307,7 +2322,7 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
-date-fns@^2.30.0:
+date-fns@^2.0.1, date-fns@^2.30.0:
version "2.30.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
@@ -3479,7 +3494,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
-loose-envify@^1.1.0, loose-envify@^1.4.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -3919,7 +3934,7 @@ prettier@^3.0.1:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.1.tgz#65271fc9320ce4913c57747a70ce635b30beaa40"
integrity sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==
-prop-types@^15.6.2, prop-types@^15.8.1:
+prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -3948,6 +3963,18 @@ react-chartjs-2@^5.2.0:
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d"
integrity sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==
+react-datepicker@^4.16.0:
+ version "4.16.0"
+ resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.16.0.tgz#b9dd389bb5611a1acc514bba1dd944be21dd877f"
+ integrity sha512-hNQ0PAg/LQoVbDUO/RWAdm/RYmPhN3cz7LuQ3hqbs24OSp69QCiKOJRrQ4jk1gv1jNR5oYu8SjjgfDh8q6Q1yw==
+ dependencies:
+ "@popperjs/core" "^2.11.8"
+ classnames "^2.2.6"
+ date-fns "^2.30.0"
+ prop-types "^15.7.2"
+ react-onclickoutside "^6.12.2"
+ react-popper "^2.3.0"
+
react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -3956,6 +3983,11 @@ react-dom@18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
+react-fast-compare@^3.0.1:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
+ integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
+
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -3973,6 +4005,19 @@ react-kakao-maps-sdk@^1.1.11:
dependencies:
kakao.maps.d.ts "^0.1.38"
+react-onclickoutside@^6.12.2:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc"
+ integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==
+
+react-popper@^2.2.5, react-popper@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
+ integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
+ dependencies:
+ react-fast-compare "^3.0.1"
+ warning "^4.0.2"
+
react-query@^3.39.3:
version "3.39.3"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35"
@@ -4488,7 +4533,7 @@ typed-array-length@^1.0.4:
for-each "^0.3.3"
is-typed-array "^1.1.9"
-typescript@^5.1.6:
+typescript@5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
@@ -4559,6 +4604,13 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+warning@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"