diff --git a/src/app/product/detail/building/[id]/page.tsx b/src/app/product/detail/building/[id]/page.tsx index 1a781db1..60daeea6 100644 --- a/src/app/product/detail/building/[id]/page.tsx +++ b/src/app/product/detail/building/[id]/page.tsx @@ -220,7 +220,11 @@ const BuildingDetailpage = (props: any) => { ) : sort === 'profit' ? ( ) : sort === 'detail' ? ( - + ) : undefined} ); diff --git a/src/components/product/TopProduct.tsx b/src/components/product/TopProduct.tsx index 4125abaa..f9ac9383 100644 --- a/src/components/product/TopProduct.tsx +++ b/src/components/product/TopProduct.tsx @@ -131,7 +131,9 @@ const TopProduct = memo(({ summary }: TopProductProps) => {
-
{item.price}
+
+ {item.price.toLocaleString()}원 +
0 diff --git a/src/components/product/detail/building/BuildingProductDetail.tsx b/src/components/product/detail/building/BuildingProductDetail.tsx index 8bb5becc..80d7411e 100644 --- a/src/components/product/detail/building/BuildingProductDetail.tsx +++ b/src/components/product/detail/building/BuildingProductDetail.tsx @@ -4,26 +4,50 @@ import FloatingPopulationChart from './chart/FloatingPopulationChart'; import PopulationInformationChart from './chart/PopulationInformationChart'; import PublicTransport from './PublicTransport'; import OfficialPriceChart from './chart/OfficialPriceChart'; +import AccommodationVisitorsChart from './chart/AccommodationVisitorsChart'; +import AccommoationRateChart from './chart/AccommodationRateChart'; const BuildingProductDetail = ({ url, - rentType + rentType, + stayType }: { url: string; rentType: boolean | undefined; + stayType: boolean | undefined; }) => { + console.log(rentType); + console.log(stayType); return (
-
- 상권 임대료 - (단위: 천원/m²) -
+ {stayType ? ( + <> +
+ 숙박 유형별 방문자 수 +
- -
- 상권 공실률 - (단위: %) -
- + +
+ + 숙박 방문자 비율 / 평균 숙박일 + +
+ + + ) : ( + <> + {' '} +
+ 상권 임대료 + (단위: 천원/m²) +
+ +
+ 상권 공실률 + (단위: %) +
+ + + )}
공시지가 diff --git a/src/components/product/detail/building/chart/AccommodationRateChart.tsx b/src/components/product/detail/building/chart/AccommodationRateChart.tsx new file mode 100644 index 00000000..246acc04 --- /dev/null +++ b/src/components/product/detail/building/chart/AccommodationRateChart.tsx @@ -0,0 +1,210 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import { usePathname } from 'next/navigation'; +import { IStayRateData } from '@/types/BuildingProductType'; +import { Chart } from 'react-chartjs-2'; +import { TooltipItem } from 'chart.js'; + +const AccommoationRateChart = () => { + const pathname = usePathname(); + const lastSegment = pathname.split('/').pop(); + const [startYear, setStartYear] = useState(2022); + const [endYear, setEndYear] = useState(2024); + const fetchData = async () => { + try { + const response = await axios.get( + `https://api.moaguide.com/detail/building/stay/rate/${lastSegment}?syear=${startYear}&eyear=${endYear}` + ); + return response.data; + } catch (error) { + console.error('Error fetching data:', error); + throw error; // 에러를 다시 던져서 useQuery의 onError로 전달 + } + }; + + const { + data: CopyRightFeeData, + isLoading, + error + } = useQuery({ + queryKey: ['AccommodationRate', startYear, lastSegment, endYear], + queryFn: fetchData + }); + + const AccommodationRateDay = CopyRightFeeData?.object.map((item) => item.day); + + const AccommodationRatevalue = CopyRightFeeData?.object.map((item) => item.value); + + const AccommodationRateRate = CopyRightFeeData?.object.map((item) => item.rate); + + const data = { + labels: AccommodationRateDay, + datasets: [ + { + type: 'bar' as const, + label: '평균 숙박 일수', + data: AccommodationRatevalue, + backgroundColor: (context: any) => { + const chart = context.chart; + const { ctx, chartArea } = chart; + + if (!chartArea) { + return null; + } + + const gradient = ctx.createLinearGradient( + 0, + chartArea.bottom, + 0, + chartArea.top + ); + gradient.addColorStop(0, 'rgba(140, 192, 250, 0.8)'); + gradient.addColorStop(1, '#2575d1'); + + return gradient; + }, + yAxisID: 'y1', + borderRadius: 5, + barThickness: 20, + barPercentage: 0.2, + categoryPercentage: 0.5, + + datalabels: { + display: false, + align: 'end' as const, + anchor: 'end' as const, + color: '#000', + formatter: (value: number) => `${value}` + } + }, + { + type: 'line' as const, + label: '숙박 방문자 비율', + data: AccommodationRateRate, + borderColor: '#0000FF', + backgroundColor: '#0000FF', + pointBackgroundColor: '#0000FF', + pointBorderColor: '#0000FF', + fill: false, + tension: 0.4, + yAxisID: 'y2', + pointStyle: 'circle', + pointRadius: 6, + datalabels: { + display: false + } + } + ] + }; + + const options = { + responsive: true, + maintainAspectRatio: false, + aspectRatio: 2, + scales: { + x: { + display: true, + grid: { + display: false + } + }, + y1: { + type: 'linear' as const, + position: 'left' as const, + grid: { + display: false + }, + beginAtZero: true, + // max: BarnewVariable, + ticks: { + stepSize: 10 + } + }, + y2: { + type: 'linear' as const, + position: 'right' as const, + grid: { + display: false + }, + // max: LinenewVariable, + beginAtZero: true + } + }, + plugins: { + legend: { + display: false, + position: 'top' as const + }, + tooltip: { + enabled: true, + intersect: false, + callbacks: { + label: function (context: TooltipItem<'bar' | 'line'>) { + const label = context.dataset.label || ''; + const value = context.raw as number; + const unit = context.dataset.type === 'bar' ? '일' : '%'; + // return `${label}: ${value.toLocaleString()}${unit}`; + return ``; + }, + beforeBody: function (context: TooltipItem<'bar' | 'line'>[]) { + const index = context[0].dataIndex; + const datasets = context[0].chart.data.datasets; + + let tooltipText = ''; + datasets.forEach((dataset) => { + const value = dataset.data[index] as number; + const unit = dataset.type === 'bar' ? '일' : '%'; + tooltipText += `${dataset.label}: ${value.toLocaleString()}${unit}\n`; + }); + + return tooltipText; + } + } + }, + datalabels: { + display: true + } + } + }; + + return ( +
+
+
+ + ~ + +
+
+ +
+
+ +
+
+
+ ); +}; + +export default AccommoationRateChart; diff --git a/src/components/product/detail/building/chart/AccommodationVisitorsChart.tsx b/src/components/product/detail/building/chart/AccommodationVisitorsChart.tsx new file mode 100644 index 00000000..b20d6a86 --- /dev/null +++ b/src/components/product/detail/building/chart/AccommodationVisitorsChart.tsx @@ -0,0 +1,168 @@ +import React, { useState } from 'react'; +import { Bar } from 'react-chartjs-2'; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import { usePathname } from 'next/navigation'; +import { IStayDayData } from '@/types/BuildingProductType'; +import { TooltipItem } from 'chart.js'; + +const AccommodationVisitorsChart = () => { + const pathname = usePathname(); + const lastSegment = pathname.split('/').pop(); // 경로의 마지막 부분 추출 + const [startYear, setStartYear] = useState(2022); + const [endYear, setEndYear] = useState(2024); + const fetchData = async () => { + const response = await axios.get( + `https://api.moaguide.com/detail/building/stay/day/${lastSegment}?syear=${startYear}&eyear=${endYear}` + ); + return response.data; + }; + // useQuery로 데이터 패칭 + const { data, error, isLoading } = useQuery({ + queryKey: ['accommodationVisitordata', lastSegment, startYear, endYear], + queryFn: fetchData + }); + + const accommodationVisitorDay = data?.object.map((item) => item.day); + + const accommodationVisitorData = (() => { + if (!data?.object) return []; + + const totalArray = data.object.map((item) => item.total); + const nodayArray = data.object.map((item) => item.noday); + const onedayArray = data.object.map((item) => item.oneday); + const twodayArray = data.object.map((item) => item.twoday); + const threedayArray = data.object.map((item) => item.threeday); + + return [totalArray, nodayArray, onedayArray, twodayArray, threedayArray]; + })(); + + const chartData = { + labels: accommodationVisitorDay, + datasets: [ + { + label: '전체', + data: accommodationVisitorData[0], + backgroundColor: '#89a3ff' + }, + { + label: '무박', + data: accommodationVisitorData[1], + backgroundColor: '#6243ee' + }, + { + label: '1박', + data: accommodationVisitorData[2], + backgroundColor: '#aea0e9' + }, + { + label: '2박', + data: accommodationVisitorData[3], + backgroundColor: '#3343f1' + }, + { + label: '3박이상', + data: accommodationVisitorData[4], + backgroundColor: '#e435ed' + } + ] + }; + + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: true, + position: 'bottom' as const + }, + tooltip: { + enabled: true, + intersect: false, + callbacks: { + label: function (context: TooltipItem<'bar'>) { + const label = context.dataset.label || ''; + const value = context.raw; + return ``; + }, + beforeBody: function (context: TooltipItem<'bar'>[]) { + const index = context[0].dataIndex; + const datasets = context[0].chart.data.datasets; + + let tooltipText = ''; + datasets.forEach((dataset) => { + const value = dataset.data[index] as number; + tooltipText += `${dataset.label}: ${value.toLocaleString()}명\n`; + }); + + return tooltipText; + } + } + }, + datalabels: { + display: false // 데이터 레이블 숨기기 + } + // customText: customTextPlugin + }, + scales: { + x: { + stacked: true, + grid: { + display: false + } + }, + y: { + stacked: true, + beginAtZero: true, + grid: { + display: true + } + } + } + }; + + return ( +
+
+
+
+ + ~ + +
+
+ +
+
+ {chartData ? ( + + ) : ( +
Loading chart...
+ )} +
+
+
+ ); +}; + +export default AccommodationVisitorsChart; diff --git a/src/components/product/detail/building/chart/FloatingPopulationChart.tsx b/src/components/product/detail/building/chart/FloatingPopulationChart.tsx index 06a4c794..5bbb892d 100644 --- a/src/components/product/detail/building/chart/FloatingPopulationChart.tsx +++ b/src/components/product/detail/building/chart/FloatingPopulationChart.tsx @@ -1,10 +1,8 @@ import React from 'react'; import { Bar } from 'react-chartjs-2'; - import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; import { usePathname } from 'next/navigation'; -import { it } from 'node:test'; import { ISubwayData } from '@/types/BuildingProductType'; const FloatingPopulationChart = () => { diff --git a/src/types/BuildingProductType.ts b/src/types/BuildingProductType.ts index c7065e3c..be6de6c7 100644 --- a/src/types/BuildingProductType.ts +++ b/src/types/BuildingProductType.ts @@ -37,6 +37,7 @@ export interface IBuildingProductDetail { link: string; rentType: boolean; bookmark: boolean; + stayType: boolean; } export interface IBuildingProductProfitDetail { @@ -117,3 +118,26 @@ export interface ISubwayData { subwayDay: ISubwayDayItem[]; subwayMonth: ISubwayMonthItem[]; } + +export interface ISaydayItem { + day: string; + noday: number; + oneday: number; + twoday: number; + threeday: number; + total: number; +} + +export interface IStayDayData { + object: ISaydayItem[]; +} + +export interface IStayRateItem { + day: string; + rate: number; + value: number; +} + +export interface IStayRateData { + object: IStayRateItem[]; +}