diff --git a/apps/cm/src/pages/dashboard/api/sn-service.ts b/apps/cm/src/pages/dashboard/api/sn-service.ts index ae4355e1e..0beaaf59e 100644 --- a/apps/cm/src/pages/dashboard/api/sn-service.ts +++ b/apps/cm/src/pages/dashboard/api/sn-service.ts @@ -3,7 +3,7 @@ import { client } from "@/shared/api"; import { create } from "zustand"; import { ENDPOINT } from "@/shared/config"; import type { PplLengthContent, SewerageService } from "../model/sn-interface"; -import type { OldPplChartData,mapPplOldType,mapPplOldItem } from "../model/sn-interface"; +import type { OldPplChartData, mapPplOldType, mapPplOldItem } from "../model/sn-interface"; export type SnState = { snStatus: PplLengthContent[] | null; @@ -13,48 +13,48 @@ export type SnStateActions = { }; export type SnStore = SnState & SnStateActions; -const initialUsePplChartData: OldPplChartData={ - "labels": ["금강","한강","낙동강", "영산강"], - "datasets": [ - { - "label": "기타", - "data": [1966,15480,6957,1936], - "backgroundColor": "rgba(255, 178, 150, 0.9)" - }, - { - "label": "오수관로", - "data": [8074,15879,16337,5671], - "backgroundColor": "rgba(180, 240, 190, 0.95)" - }, - { - "label": "우수관로", - "data": [4838,12893,10492,4281], - "backgroundColor": "rgba(170, 210, 245, 0.95)" - } - ] +const initialUsePplChartData: OldPplChartData = { + labels: ["금강", "한강", "낙동강", "영산강"], + datasets: [ + { + label: "기타", + data: [1966, 15480, 6957, 1937], + backgroundColor: "rgba(255, 178, 150, 0.9)" + }, + { + label: "오수관로", + data: [8074, 15879, 16337, 5671], + backgroundColor: "rgba(180, 240, 190, 0.95)" + }, + { + label: "우수관로", + data: [4838, 12893, 10492, 4281], + backgroundColor: "rgba(170, 210, 245, 0.95)" + } + ] }; -const initialOldPplChartData: OldPplChartData={ - labels: ["금강","한강","낙동강","영산강"], - datasets: [ - { - label: "20-25년", - data: [ 1140,3181,3100,1662], - backgroundColor: "#3B82F6", - borderRadius: 4 - }, - { - label: "25-30년", - data: [1391, 3615, 1814, 1411], - backgroundColor: "#EF4444", - borderRadius: 4 - }, - { - label: "30년이상", - data: [ 2014, 10303,9831,2000], - backgroundColor: "#F87171", - borderRadius: 4 - } - ] +const initialOldPplChartData: OldPplChartData = { + labels: ["금강", "한강", "낙동강", "영산강"], + datasets: [ + { + label: "20-25년", + data: [1140, 3181, 3100, 1662], + backgroundColor: "#3B82F6", + borderRadius: 4 + }, + { + label: "25-30년", + data: [1391, 3615, 1814, 1411], + backgroundColor: "#EF4444", + borderRadius: 4 + }, + { + label: "30년이상", + data: [2014, 10303, 9831, 2000], + backgroundColor: "#F87171", + borderRadius: 4 + } + ] }; type OldPplStoreState = { chartData: OldPplChartData; @@ -70,13 +70,13 @@ export const useUsePplStore = create((set) => ({ })); export const initailMapOldPplData: mapPplOldType = { - colTitle: {totalLen: "총길이",pplLen2025: "20~25년",pplLen2530: "25~30년",pplLen30 : "30년 이상" }, //헤더 영역 - data: [ - {name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30 : 2014}, - {name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30 : 10303}, - {name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30 : 9831}, - {name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30 : 2000}, - ] + colTitle: { totalLen: "총길이", pplLen2025: "20~25년", pplLen2530: "25~30년", pplLen30: "30년 이상" }, //헤더 영역 + data: [ + { name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30: 2014 }, + { name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30: 10303 }, + { name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30: 9831 }, + { name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30: 2000 }, + ] }; type mapPplOldState = { mapData: mapPplOldItem[]; @@ -136,7 +136,7 @@ export class SewerageApiService implements SewerageService { ...item, sumPrlgVl: Number(item.sumPrlgVl), })); - + return list; } @@ -158,6 +158,32 @@ export class SewerageApiService implements SewerageService { })); return list; } + + // 유역별 용도별 관로 길이(km) MV조회 : usgCd - SBA004 오수관, SBA003 우수관 + public async getMvUsgPplLength(): Promise { + const response = await client.get<{ + PplLengthList: PplLengthContent[]; + }>(ENDPOINT.SEWERAGE_SERVICE.MV_USG_PPL_LENGTH); + + const list = (response.data.PplLengthList ?? []).map((item) => ({ + ...item, + sumPrlgVl: Number(item.sumPrlgVl), + })); + return list; + } + // 유역별 노후도별 관로 길이(km) MV조회 : yearGroup - 20년~25년미만,25년~30년미만,30년이상 + public async getMvOldPplLength(): Promise { + const response = await client.get<{ + PplLengthList: PplLengthContent[]; + }>(ENDPOINT.SEWERAGE_SERVICE.MV_OLD_PPL_LENGTH); + + const list = (response.data.PplLengthList ?? []).map((item) => ({ + ...item, + sumPrlgVl: Number(item.sumPrlgVl), + })); + return list; + } + } export const SewerageApi = new SewerageApiService(); diff --git a/apps/cm/src/pages/dashboard/model/sn-interface.ts b/apps/cm/src/pages/dashboard/model/sn-interface.ts index 865b96688..4b86cd897 100644 --- a/apps/cm/src/pages/dashboard/model/sn-interface.ts +++ b/apps/cm/src/pages/dashboard/model/sn-interface.ts @@ -2,11 +2,15 @@ import { basinKeyMap } from "../model/map-data"; export interface PplLengthContent { zoneNm: string; sumPrlgVl: number; + usgCd?: string; + yearGroup?: string; } export interface SewerageService { getPplLength(ymdFrom: string, ymdTo: string, useCd: string): Promise; getPplUseLength(ymdFrom: string, ymdTo: string, useCd: string): Promise; + getMvUsgPplLength(): Promise; + getMvOldPplLength(): Promise; } export interface ChartDataSet { @@ -39,11 +43,11 @@ export type mapPplOldItem = { }; export type mapPplOldType = { - colTitle: { + colTitle: { totalLen: string; // 총길이 pplLen2025: string; // 20~25년 pplLen2530: string; // 25~30년 - pplLen30 : string; // 30년 이상 + pplLen30: string; // 30년 이상 }; data: mapPplOldItem[]; }; @@ -72,7 +76,7 @@ export function buildSnOldPplLength( colTitle, rows: [ total, // 총길이 (undefined면 0) - ...datasetValues, // 각 연차 데이터 (undefined면 0) + ...datasetValues, // 각 연차 데이터 (undefined면 0) ], }; }); diff --git a/apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx b/apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx index dfe4ff260..b1a34139c 100644 --- a/apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx +++ b/apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx @@ -23,55 +23,56 @@ import { SewerageApi, useOldPplStore,useMapOldPplStore, transformChartDataToMapD ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); export default function SnOldPpl() { - const [oldPpl2025Data, setOldPpl2025Data] = useState([]); - const [oldPpl2530Data, setOldPpl2530Data] = useState([]); - const [oldPpl30Data, setOldPpl30Data] = useState([]); + const [oldPplLenData, setOldPplLenData] = useState([]); const {chartData, setChartData} = useOldPplStore(); const {setMapData} = useMapOldPplStore(); useEffect(() => { - const currentYear = new Date().getFullYear(); - - const ranges = { - "20-25년": { from: `${currentYear - 25 + 1}0101`, to: `${currentYear - 20}1231`, setter: setOldPpl2025Data }, - "25-30년": { from: `${currentYear - 30 + 1}0101`, to: `${currentYear - 25}1231`, setter: setOldPpl2530Data }, - "30년이상": { from: "19010101", to: `${currentYear - 30}1231`, setter: setOldPpl30Data }, - }; - - Object.entries(ranges).forEach(([label, range]) => { - SewerageApi.getPplLength(range.from, range.to) - .then(range.setter) - .catch((e) => console.error(`${label} 데이터 조회 실패`, e)); - }); + // 유역별, 노후년도별 관로길이 데이터를 한번에 조회 + SewerageApi.getMvOldPplLength() + .then((apiResult) => { + setOldPplLenData(apiResult); + }) + .catch((e) => console.error("노후관로 데이터 조회 실패", e)); }, []); useEffect(() => { - const allData = [...oldPpl2025Data, ...oldPpl2530Data, ...oldPpl30Data]; - if (allData.length === 0) return; + if (oldPplLenData.length === 0) return; - const allZones = Array.from(new Set(allData.map(d => d.zoneNm))); + // 유역 목록 추출 (중복 제거) + const allZones = [...new Set(oldPplLenData.map(d => d.zoneNm))]; - const createDataset = (label: string, data: PplLengthContent[], color: string) => { - const dataMap = new Map(data.map(item => [item.zoneNm, item.sumPrlgVl])); - // console.log(dataMap); - return { - label, - data: allZones.map(zone => dataMap.get(zone) ?? 0), - backgroundColor: color, - borderRadius: 4, - }; + // yearGroup 매핑: API 응답값 -> 차트 label + const yearGroupMap: { [key: string]: { label: string; color: string } } = { + "20년~25년미만": { label: "20-25년", color: "#3B82F6" }, + "25년~30년미만": { label: "25-30년", color: "#EF4444" }, + "30년이상": { label: "30년이상", color: "#F87171" }, }; - const newDatasets: ChartDataSet[] = []; - if (oldPpl2025Data.length > 0) { - newDatasets.push(createDataset('20-25년', oldPpl2025Data, '#3B82F6')); - } - if (oldPpl2530Data.length > 0) { - newDatasets.push(createDataset('25-30년', oldPpl2530Data, '#EF4444')); - } - if (oldPpl30Data.length > 0) { - newDatasets.push(createDataset('30년이상', oldPpl30Data, '#F87171')); - } + // 유역별, 노후년도별 데이터 구조화 + const dataByZone: { [zone: string]: { [yearGroup: string]: number } } = {}; + allZones.forEach(zone => { + dataByZone[zone] = {}; + }); + + oldPplLenData.forEach(item => { + let zoneData = dataByZone[item.zoneNm]; + if (!zoneData) { + zoneData = {}; + dataByZone[item.zoneNm] = zoneData; + } + if (item.yearGroup) { + zoneData[item.yearGroup] = item.sumPrlgVl; + } + }); + + // 데이터셋 생성 + const newDatasets: ChartDataSet[] = Object.entries(yearGroupMap).map(([yearGroup, { label, color }]) => ({ + label, + data: allZones.map(zone => dataByZone[zone]?.[yearGroup] ?? 0), + backgroundColor: color, + borderRadius: 4, + })); const newChartData: OldPplChartData = { labels: allZones, @@ -82,7 +83,7 @@ export default function SnOldPpl() { // 차트 데이터를 기반으로 지도 팝업 데이터 생성 및 상태 업데이트 setMapData(transformChartDataToMapData(newChartData)); - }, [oldPpl2025Data, oldPpl2530Data, oldPpl30Data]); + }, [oldPplLenData]); // 각 지역별 총 길이 계산 const totalLengths = chartData.labels.map((label, i) => { diff --git a/apps/cm/src/pages/dashboard/ui/sn-ppl-status.tsx b/apps/cm/src/pages/dashboard/ui/sn-ppl-status.tsx index 73e08a73a..8a4478010 100644 --- a/apps/cm/src/pages/dashboard/ui/sn-ppl-status.tsx +++ b/apps/cm/src/pages/dashboard/ui/sn-ppl-status.tsx @@ -25,81 +25,60 @@ interface ChartDataSet { borderColor?: string; } +// const usgCd = { "SBA004": "오수관로", "SBA003": "우수관로", "ETC": "기타" }; // 용도 ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); export default function SnPplStatus() { - const [sewageData, setSewageData] = useState([]); - const [stormwaterData, setStormwaterData] = useState([]); - const [totalData, setTotalData] = useState([]); - // const [chartData, setChartData] = useState<{ - // labels: string[]; - // datasets: ChartDataSet[]; - // }>({ labels: [], datasets: [] }); const {chartData, setChartData} = useUsePplStore(); + const [usgPplLenData, setUsgPplLenData] = useState([]); + useEffect(() => { - const ymdFrom = "19010101"; - const ymdTo = "20241231"; - - SewerageApi.getPplUseLength(ymdFrom, ymdTo, "SBA004") + // 유역별, 용도별 관로길이 데이터를 한번에 조회 + SewerageApi.getMvUsgPplLength() .then((apiResult) => { - // getPplUseLength의 결과(apiResult)를 - // setSewageData 함수의 인자로 넣어서 호출합니다. - // console.log(apiResult); - setSewageData(apiResult); + setUsgPplLenData(apiResult); }) - .catch((e) => console.error("오수관 데이터 조회 실패", e)); - - SewerageApi.getPplUseLength(ymdFrom, ymdTo, "SBA003") - .then(setStormwaterData) - .catch((e) => console.error("우수관 데이터 조회 실패", e)); - - SewerageApi.getPplLength(ymdFrom, ymdTo) - .then((apiResult) => { - // getPplLength 결과(apiResult)를 - // setTotalData 함수의 인자로 넣어서 호출합니다. - // console.log(apiResult); - setTotalData(apiResult); - }) - .catch((e) => console.error("전체 관로 데이터 조회 실패", e)); + .catch((e) => console.error("용도별 관로 데이터 조회 실패", e)); }, []); // 데이터가 변경될 때마다 차트를 다시 계산 useEffect(() => { - // 오수관 우수관 두 데이터 모두 없으면 차트를 그리지 않음 - if (sewageData.length === 0 && stormwaterData.length === 0) return; + if (usgPplLenData.length === 0) return; - const allZones = new Set([ - ...sewageData.map(d => d.zoneNm), - ...stormwaterData.map(d => d.zoneNm), - ...totalData.map(d => d.zoneNm) - ]); - const labels = Array.from(allZones); + // 유역 목록 추출 (중복 제거) + const allZones = [...new Set(usgPplLenData.map(d => d.zoneNm))]; + const labels = allZones; - const dataByZone: { [key: string]: { total: number; sewage: number; stormwater: number } } = {}; - labels.forEach(zone => {dataByZone[zone] = { total: 0, sewage: 0, stormwater: 0 }; }); - - totalData.forEach(item => { - if (!dataByZone[item.zoneNm]) { - dataByZone[item.zoneNm] = { total: 0, sewage: 0, stormwater: 0 }; - } - dataByZone[item.zoneNm]!.total = item.sumPrlgVl; + // 유역별, 용도별 데이터 구조화 + const dataByZone: { [key: string]: { sewage: number; stormwater: number; etc: number } } = {}; + labels.forEach(zone => { + dataByZone[zone] = { sewage: 0, stormwater: 0, etc: 0 }; }); - sewageData.forEach(item => { + + // usgCd별로 데이터 분류: SBA004(오수관로), SBA003(우수관로), ETC(기타) + usgPplLenData.forEach(item => { if (!dataByZone[item.zoneNm]) { - dataByZone[item.zoneNm] = { total: 0, sewage: 0, stormwater: 0 }; + dataByZone[item.zoneNm] = { sewage: 0, stormwater: 0, etc: 0 }; } - dataByZone[item.zoneNm]!.sewage = item.sumPrlgVl; - }); - stormwaterData.forEach(item => { - if (!dataByZone[item.zoneNm]) { - dataByZone[item.zoneNm] = { total: 0, sewage: 0, stormwater: 0 }; + if (item.usgCd === "SBA004") { + dataByZone[item.zoneNm]!.sewage = item.sumPrlgVl; + } else if (item.usgCd === "SBA003") { + dataByZone[item.zoneNm]!.stormwater = item.sumPrlgVl; + } else if (item.usgCd === "ETC") { + dataByZone[item.zoneNm]!.etc = item.sumPrlgVl; } - dataByZone[item.zoneNm]!.stormwater = item.sumPrlgVl; }); const datasets: ChartDataSet[] = []; + // 기타 데이터셋 (맨 아래에 쌓기 위해 먼저 추가) + datasets.push({ + label: "기타", + data: labels.map(zone => dataByZone[zone]?.etc), + backgroundColor: "rgba(255, 178, 150, 0.9)", + }); + // 오수관로 데이터셋 datasets.push({ label: "오수관로", @@ -114,21 +93,9 @@ export default function SnPplStatus() { backgroundColor: "rgba(170, 210, 245, 0.95)", }); - // 전체 데이터가 도착했다면 '기타' 데이터셋을 계산하고 추가 - if (totalData.length > 0) { - datasets.unshift({ // '기타'를 맨 아래에 쌓기 위해 unshift 사용 - label: "기타", - data: labels.map(zone => { - const zoneData = dataByZone[zone] ?? { total: 0, sewage: 0, stormwater: 0 }; - return zoneData.total - zoneData.sewage - zoneData.stormwater; - }), - backgroundColor: "rgba(255, 178, 150, 0.9)", - }); - } - setChartData({ labels, datasets }); - }, [sewageData, stormwaterData, totalData]); + }, [usgPplLenData]); // 총합(라벨당) 계산 const totals = chartData.labels.map((_, i) => diff --git a/apps/cm/src/shared/config/api.ts b/apps/cm/src/shared/config/api.ts index ed4244a21..3cdb96d0c 100644 --- a/apps/cm/src/shared/config/api.ts +++ b/apps/cm/src/shared/config/api.ts @@ -78,7 +78,9 @@ export const ENDPOINT = { PPL_USE_LENGTH: "/sewerage-service/api/v1/ms/pplUseLength", PPL_COUNTS: "/sewerage-service/api/v1/ms/pplCounts", OLD_PPL_COUNTS: "/sewerage-service/api/v1/ms/oldPplCounts", - }, + MV_USG_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvUsgPplLength", + MV_OLD_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvOldPplLength", + }, OPERATION_SERVICE: { MAIN_YEAR: "/operation-service/api/v1/dashBoard/main/year", OPER_CNTRT: "/operation-service/api/v1/dashBoard/main/oper/cntrt",