diff --git a/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts b/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts index 11d556468..7ceaa3892 100644 --- a/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts +++ b/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts @@ -46,7 +46,8 @@ export const mapService = { return response.json(); }, getPbnstRuseFclts: async (ruseFcltPk:any) => { - const response = await fetch(`${DEV_URL_WR}/reuseFc/pubFc/${ruseFcltPk}`, { + const year = new Date().getFullYear(); + const response = await fetch(`${DEV_URL_WR}/reuseFc/pubFc/${ruseFcltPk}?year=${year}`, { method: 'GET', headers: { 'Accept': 'application/json', diff --git a/apps/wr/app/(popUp-layout)/util.ts b/apps/wr/app/(popUp-layout)/util.ts index e3428b300..f280aaab9 100644 --- a/apps/wr/app/(popUp-layout)/util.ts +++ b/apps/wr/app/(popUp-layout)/util.ts @@ -2,7 +2,8 @@ import { getFetch, postFectch, fetchType } from "@/components/lib/api"; const fetchpbnstDetail = async (featureCd: string, setTabData: any) => { try { - const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`); + const year = new Date().getFullYear(); + const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}?year=${year}`); // 중복 데이터 추가 방지 setTabDataFunc(response, featureCd, setTabData); } catch (error) { @@ -55,4 +56,4 @@ const detainSttn = async (featureCd: string, layerNm: string) => { } } -export { fetchHasuDetail, fetchpbnstDetail, detainSttn }; \ No newline at end of file +export { fetchHasuDetail, fetchpbnstDetail, detainSttn }; diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index 2476d1a10..cb03bd0ae 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -1,12 +1,17 @@ -/** - * 하수재이용 시설 현황 - * 재이용시설 현황 - 하수재이용시설 현황 +/** + * 하수재이용시설 현황 + * 재이용시설 현황 > 하수재이용시설 현황 + * + * 사용 API + * 하수재이용시설 현황 목록 조회 reuse-service /reuseFc/pbnstList + * 하수처리시설 기본정보 조회 operation-service /prcsFclt/ruseInfo + * 상세조회 모달 전달을 위한 하수재이용시설 상세 조회 reuse-service /reuseFc/pubFc/{pbnstRuseFcltId} + * * - layout.tsx에 의해 공통 레이아웃이 적용됨 * - with-layout 적용 - * 하수처리시설. 하수재이용시설 , 하수재이용시설 실적에 대한 정보를 확인한다. - * 모달 : 테이블의 헤더를 원하는대로 조절해서 볼수 있다. - * 클릭시 해당 재이용ㅇ - * */ + * - 하수처리시설, 하수재이용시설, 하수재이용시설 실적에 대한 정보를 확인한다. + * - 모달에서 테이블 헤더를 원하는대로 조절해서 볼 수 있다. + */ "use client"; import { useWrStore } from "@/app/store"; import { btnClass } from "@/app/store/common/code"; @@ -113,8 +118,7 @@ export default function PbnstSttn() { "100000", fixedSearchValue, pbnRuseNm, - searchYear, - [] + searchYear ); }, [pbnstSttnList, pbnRuseNm, searchYear, getFixedSearchValue]); @@ -122,10 +126,10 @@ export default function PbnstSttn() { (index: number | undefined) => { if (index !== undefined && pbnstSttnRes && pbnstSttnRes[index]) { const pbnstRuseFcltId = pbnstSttnRes[index].pbnstRuseFcltId; - getPbnStnDetail(pbnstRuseFcltId); + getPbnStnDetail(pbnstRuseFcltId, searchYear); } }, - [getPbnStnDetail, pbnstSttnRes] + [getPbnStnDetail, pbnstSttnRes, searchYear] ); useEffect(() => { @@ -145,8 +149,7 @@ export default function PbnstSttn() { String(DEFAULT_LIST_SIZE), getFixedSearchValue(ref.current), pbnRuseNm, - searchYear, - [] + searchYear ); } }, [ @@ -265,8 +268,7 @@ export default function PbnstSttn() { String(DEFAULT_LIST_SIZE), fixedSearchValue, pbnRuseNm, - searchYear, - [] + searchYear ); }} excelClick={excelClick} @@ -303,8 +305,7 @@ export default function PbnstSttn() { String(DEFAULT_LIST_SIZE), fixedSearchValue, pbnRuseNm, - searchYear, - [] + searchYear ); }} widthFixed={true} diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx index a2013102e..fca1b511a 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx @@ -1,5 +1,9 @@ -/** +/** * 재이용실적등록 > 하수재이용실적등록 + * + * 🔗 사용 API + * 하수재이용시설 목록 조회 reuse-service /reuseFc/pbnstPrfRegList + * 하수재이용시설 실적 상세 조회 reuse-service /reuseFc/pbnstPrfRegDtl */ "use client"; import { useWrStore } from "@/app/store"; @@ -10,13 +14,11 @@ import { initValOutUsage, subTotalObj, totalObj, - type FcPrfType, + type PbnstPrfRegDtlType, + type PbnstPrfRegListType, type PrfType, } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; -import { - excelDownload, - yearOptions, -} from "@/components/lib/func/common"; +import { excelDownload, yearOptions } from "@/components/lib/func/common"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { pbnstTableBody } from "@/components/lib/func/TableData"; import PbnPrfExcelModal from "@/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal"; @@ -32,18 +34,104 @@ import { UiTable, } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; -import { - Button, - Label, - Table, - TableBody, - TableHeader, -} from "@common/components/ui"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { Button, Label, Table, TableBody, TableHeader } from "@common/components/ui"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useAlertStore } from "@/src/shared/lib"; import { useTranslation } from "react-i18next"; import { headMakeprfmnc } from "../chkgdnReg/TableHead"; +type PbnstPrfmncRow = { + sido: string; + gugun: string; + bscFctNm: string; + ruseUtztnFcltNm: string; + pbnstRuseFcltId: string; +}; + +const buildInputData = (detail: PbnstPrfRegDtlType | null, searchYear: string) => { + if (!detail?.prfList?.length) { + return []; + } + + const inUsageData = structuredClone(initValInUsage).map((row) => ({ + ...row, + 구분: "장내용수", + })); + const outUsageData = structuredClone(initValOutUsage).map((row) => ({ + ...row, + 구분: "장외용수", + })); + const inSubtotal = { + ...structuredClone(subTotalObj), + 구분: "장내용수", + }; + const outSubtotal = { + ...structuredClone(subTotalObj), + 구분: "장외용수", + }; + + detail.prfList.forEach((prfItem) => { + const yr = Number(prfItem.ym?.slice(0, 4)); + if (yr !== Number(searchYear)) { + return; + } + + const month = Number(prfItem?.ym?.slice(4)); + const monthKey = `${month}월`; + + inUsageData.forEach((inData: any) => { + const usageKey = inData.usage; + const value = (prfItem as any)[usageKey] ?? 0; + + inData[monthKey] = value; + inData.total += value; + + if (usageKey !== "subtotal") { + (inSubtotal as any)[monthKey] = + ((inSubtotal as any)[monthKey] ?? 0) + value; + inSubtotal.total += value; + } + }); + + outUsageData.forEach((outData: any) => { + const usageKey = outData.usage; + const value = (prfItem as any)[usageKey] ?? 0; + + outData[monthKey] = value; + outData.total += value; + + if (usageKey !== "subtotal") { + (outSubtotal as any)[monthKey] = + ((outSubtotal as any)[monthKey] ?? 0) + value; + outSubtotal.total += value; + } + }); + }); + + if (inSubtotal.total === 0 && outSubtotal.total === 0) { + return []; + } + + inUsageData.push(inSubtotal); + outUsageData.push(outSubtotal); + + const usageTotalObj: { [key: string]: any } = { + ...structuredClone(totalObj), + 구분: "총합계", + 용수명: "", + }; + for (const key in usageTotalObj) { + if (["usage", "gubun", "edit"].includes(key)) continue; + const val = + (Number((inSubtotal as any)[key]) || 0) + + (Number((outSubtotal as any)[key]) || 0); + + (usageTotalObj as any)[key] = val; + } + + return [...inUsageData, ...outUsageData, usageTotalObj]; +}; + export default function PrfmncPrfmncReg() { const { auth, @@ -51,59 +139,85 @@ export default function PrfmncPrfmncReg() { prfmncHasuInit, prfmnRegData, prfmncHasuNmSearch, - getPrfFmcRes, + getPbnstPrfRegDtl, getLaeSeCdCheck, + returnCodeText, } = useWrStore((state) => state); - const { - prfPbnFclts, - pbnstPrfmncHeadList, - monthHeadList, - prfmncRes, - prfmncResCount, - } = prfmnc; + const { pbnstPrfmncHeadList, monthHeadList, prfmncRes, prfmncResCount, prfmncDetail } = + prfmnc; const { regAuthrtYn, mdfcnAuthrtYn, laeSeCd } = auth; const [isReset, setIsReset] = useState(false); - const [bdstTable, setbdstTable] = useState(); - const [searchTxt, setSearchTxt] = useState(); + const [searchTxt, setSearchTxt] = useState(""); + const [selectedFacilityId, setSelectedFacilityId] = useState(""); + const [selectedIndex, setSelectedIndex] = useState(0); + const [inputData, setInputData] = useState([]); const [isExcelOpen, setIsExcelOPen] = useState<{ isModal: boolean; - data: FcPrfType; - }>({ isModal: false, data: {} as FcPrfType }); - const [inputData, setInputData] = useState(); - const [pbnstRuseFcltId, setpbnstRuseFcltId] = useState(); - const [cellNum, setCellNum] = useState(1); - const [searchYear, setSearchYear] = useState( - String(new Date().getFullYear()) - ); + data: PbnstPrfRegDtlType; + }>({ isModal: false, data: {} as PbnstPrfRegDtlType }); + const [searchYear, setSearchYear] = useState(String(new Date().getFullYear())); const { confirm } = useConfirm(); const alert = useAlertStore((state) => state.setMessage); + const { t } = useTranslation("base"); - const useInitRef = useRef(false); + const bdstTable = useMemo( + () => + prfmncRes.map((item: PbnstPrfRegListType) => ({ + sido: returnCodeText(item.sidoCd, "cityCd"), + gugun: + item.sggCd && String(item.sggCd).trim().length === 5 + ? returnCodeText(String(item.sggCd), "sggCd") + : "", + bscFctNm: item.swageBscFctNm, + ruseUtztnFcltNm: item.ruseUtztnFcltNm, + pbnstRuseFcltId: item.pbnstRuseFcltId, + })), + [prfmncRes, returnCodeText] + ); + + const derivedInputData = useMemo( + () => buildInputData(prfmncDetail, searchYear), + [prfmncDetail, searchYear] + ); const excelClick = useCallback(() => { - excelDownload( - [[]], - "하수재이용실적", - "sample_pbnstPrfmncReg.xlsx", - "A1:O1" - ); - }, [prfmnc.excelList]); + excelDownload([[]], "하수재이용실적", "sample_pbnstPrfmncReg.xlsx", "A1:O1"); + }, []); + + const loadDetail = useCallback( + async (facilityId: string) => { + if (!facilityId) return; + + const detail = await getPbnstPrfRegDtl(searchYear, facilityId); + if (detail) { + setSelectedFacilityId(facilityId); + setIsExcelOPen((prev) => ({ + ...prev, + data: detail, + })); + } + }, + [getPbnstPrfRegDtl, searchYear] + ); + + const handleSearch = useCallback(() => { + if (!laeSeCd) return; + + setIsReset(true); + }, [laeSeCd]); - const { t } = useTranslation("base"); const onSave = useCallback(async () => { const handleConfirm = async () => { - if (!pbnstRuseFcltId) return; + if (!selectedFacilityId) return; const message = await prfmnRegData( - 1, - laeSeCd, excelToPbnPrfData( inputData as unknown as JSON[], searchYear, - pbnstRuseFcltId - ) as unknown as PrfType, + selectedFacilityId + ) as unknown as PrfType[], searchYear, - prfPbnFclts + selectedFacilityId ); alert(message); }; @@ -111,126 +225,47 @@ export default function PrfmncPrfmncReg() { if (await confirm(t("isUpdateMsg"))) { await handleConfirm(); } - }, [inputData, searchYear, pbnstRuseFcltId, cellNum, prfPbnFclts, confirm, t, laeSeCd, prfmnRegData]); - - const cellClick = useCallback( - (index: number | undefined) => { - if (index !== undefined) { - setIsExcelOPen({ - isModal: false, - data: prfmncRes[index] || ({} as FcPrfType), - }); - const inUsageData = structuredClone(initValInUsage).map((row) => ({ - ...row, - 구분: "장내용수", - })); - const outUsageData = structuredClone(initValOutUsage).map((row) => ({ - ...row, - 구분: "장외용수", - })); - const inSubtotal = { - ...structuredClone(subTotalObj), - 구분: "장내용수", - }; - const outSubtotal = { - ...structuredClone(subTotalObj), - 구분: "장외용수", - }; - if (prfmncRes[index]?.prfList) { - prfmncRes[index].prfList?.forEach((prfItem) => { - const yr = Number(prfItem.ym?.slice(0, 4)); // "YYYY" - - if (yr === Number(searchYear)) { - const month = Number(prfItem?.ym?.slice(4)); // "MM" - const monthKey = `${month}월`; - - inUsageData.forEach((inData: any) => { - const usageKey = inData.usage; - const value = (prfItem as any)[usageKey] ?? 0; - - inData[monthKey] = value; - inData.total += value; - - if (inSubtotal && usageKey !== "subtotal") { - (inSubtotal as any)[monthKey] = - ((inSubtotal as any)[monthKey] ?? 0) + value; - inSubtotal.total += value; - } - }); - - outUsageData.forEach((outData: any) => { - const usageKey = outData.usage; - const value = (prfItem as any)[usageKey] ?? 0; - - outData[monthKey] = value; - outData.total += value; - - if (usageKey !== "subtotal") { - (outSubtotal as any)[monthKey] = - ((outSubtotal as any)[monthKey] ?? 0) + value; - outSubtotal.total += value; - } - }); - } - }); - - if (inSubtotal.total !== 0 && outSubtotal.total !== 0) { - inUsageData.push(inSubtotal); - outUsageData.push(outSubtotal); - const usageTotalObj: { [key: string]: any } = { - ...structuredClone(totalObj), - 구분: "총합계", - 용수명: "", - }; - for (const key in usageTotalObj) { - if (["usage", "gubun", "edit"].includes(key)) continue; - const val = - (Number((inSubtotal as any)[key]) || 0) + - (Number((outSubtotal as any)[key]) || 0); - - (usageTotalObj as any)[key] = val; - } - const tableData = [...inUsageData, ...outUsageData, usageTotalObj]; - setpbnstRuseFcltId(prfmncRes[index].pbnstRuseFcltId); - setInputData(tableData); - } else { - setInputData([]); - } - } else { - setInputData([]); - } - } - }, - [prfmncRes, searchYear] - ); - useEffect(() => { - getLaeSeCdCheck(); - useInitRef.current = true; - }, []); - useEffect(() => { - if (useInitRef.current == true && laeSeCd !== undefined) { - getPrfFmcRes(); - useInitRef.current = true; - } - }, [useInitRef.current, laeSeCd]); + }, [alert, confirm, inputData, prfmnRegData, searchYear, selectedFacilityId, t]); useEffect(() => { - const year = new Date().getFullYear(); - - if(laeSeCd === undefined || laeSeCd ==='') return; - prfmncHasuInit(1, laeSeCd, String(year), prfPbnFclts); - }, [prfPbnFclts,laeSeCd]); + void getLaeSeCdCheck(); + }, [getLaeSeCdCheck]); useEffect(() => { - if (prfmncRes && prfmncRes.length > 0) { - setbdstTable(prfmncRes); + if (!laeSeCd) return; + void prfmncHasuInit(1, laeSeCd); + }, [laeSeCd, prfmncHasuInit]); + + useEffect(() => { + if (!prfmncRes.length) { + setSelectedFacilityId(""); + setSelectedIndex(0); + setInputData([]); + setIsExcelOPen((prev) => ({ ...prev, data: {} as PbnstPrfRegDtlType })); + return; } - // cellNum이 0일 경우(첫 번째 행)도 유효한 인덱스이므로 undefined 여부만 체크합니다. - if (cellNum !== undefined) { - cellClick(cellNum); + const nextIndex = Math.max( + prfmncRes.findIndex((item) => item.pbnstRuseFcltId === selectedFacilityId), + 0 + ); + const nextFacility = prfmncRes[nextIndex]; + if (!nextFacility) { + return; } - }, [prfmncRes, cellNum, cellClick]); + + setSelectedIndex(nextIndex); + void loadDetail(nextFacility.pbnstRuseFcltId); + }, [loadDetail, prfmncRes]); + + useEffect(() => { + if (!selectedFacilityId) return; + void loadDetail(selectedFacilityId); + }, [loadDetail, searchYear, selectedFacilityId]); + + useEffect(() => { + setInputData(derivedInputData); + }, [derivedInputData]); return ( <> @@ -249,9 +284,7 @@ export default function PrfmncPrfmncReg() { selectClass={""} selectArray={yearOptions} value={searchYear} - onSelectValue={(val: string | number) => - setSearchYear(String(val)) - } + onSelectValue={(val: string | number) => setSearchYear(String(val))} />
@@ -261,7 +294,7 @@ export default function PrfmncPrfmncReg() { { setSearchTxt(e); }} @@ -271,12 +304,7 @@ export default function PrfmncPrfmncReg() {
-
- - - {headMakeprfmnc(monthHeadList, [])} - - - {pbnstTableBody( - inputData ?? [], - monthHeadList, - setInputData, - mdfcnAuthrtYn === "N" ? false : true - )} - -
+
+ + + {headMakeprfmnc(monthHeadList, [])} + + + {pbnstTableBody( + inputData, + monthHeadList, + setInputData, + mdfcnAuthrtYn === "N" ? false : true + )} + +
+
@@ -376,22 +404,20 @@ export default function PrfmncPrfmncReg() { { - setIsExcelOPen({ ...isExcelOpen, isModal: open }); + setIsExcelOPen((prev) => ({ ...prev, isModal: open })); }} setModalExcelData={async (excelData) => { - const message = await prfmnRegData(1, laeSeCd, excelData, searchYear, prfPbnFclts); + if (!selectedFacilityId) return; + const message = await prfmnRegData(excelData, searchYear, selectedFacilityId); alert(message); - // 데이터 저장 후 UI 업데이트는 useEffect가 감지하여 처리하므로 여기서는 호출하지 않습니다. }} > } open={isExcelOpen.isModal} onOpenChange={() => { - setIsExcelOPen({ ...isExcelOpen, isModal: false }); + setIsExcelOPen((prev) => ({ ...prev, isModal: false })); }} > ); } - - diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx index 53617689d..2238e8bdc 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx @@ -25,7 +25,6 @@ export default function PbnstPrfmncSttn() { pbnstPrfmncSearch, getFlctsSttn, prfmnc, - pbnNmSearch, getLaeSeCdCheck } = useWrStore((state) => state); const { laeSeCd } = auth; @@ -584,11 +583,6 @@ export default function PbnstPrfmncSttn() { if (prfmncRes?.[0]?.pbnstRuseFcltId) setChartId(prfmncRes[0].pbnstRuseFcltId) }, [prfmncRes]); - useEffect(() => { - if (pbnRuseNm.length > 1) - pbnNmSearch("1", "23", getFixedSearchValue(ref.current), pbnRuseNm, searchYear, []); - }, [pbnRuseNm, getFixedSearchValue, pbnNmSearch, searchYear]); - const cellClick = (index: number | undefined, headId: string) => { if (index !== undefined && headId !== undefined) { setChartId(prfmncRes[index].pbnstRuseFcltId) diff --git a/apps/wr/app/store/map/ruseMap/index.tsx b/apps/wr/app/store/map/ruseMap/index.tsx index f68fb96b3..fa5ce06b9 100644 --- a/apps/wr/app/store/map/ruseMap/index.tsx +++ b/apps/wr/app/store/map/ruseMap/index.tsx @@ -1,4 +1,6 @@ import { fetchType, getFetch, postFectch } from "@/components/lib/api"; +import type { FcPrfType } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; +import moment from "moment"; import type { CommonTableProps } from "../../common/type/header"; import type { WaterReuseFacility } from "../../ruseFcltMng/fcltSttn/pbnstSttn/type"; import { @@ -6,10 +8,39 @@ import { pbnSttHeadListExport, } from "../../ruseFcltMng/fcltSttn/pbnstSttn/headList"; import type { ReturnType } from "@common/business_components/ui"; -import { hasuListFetch } from "../../ruseFcltMng/fcltSttn/pbnstSttn"; -import type { customPbnst } from "../../ruseFcltMng/fcltReg/pbnstReg/type"; +import type { customPbnst, pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type"; import { excelPbnstSttnParsing } from "../../ruseFcltMng/fcltSttn/pbnstSttn/excel"; +const getCurrentYear = () => String(new Date().getFullYear()); + +const sumPrfUsage = (prfList: FcPrfType["prfList"] = []) => { + return prfList.reduce( + (acc, cur) => { + acc.inUsage += + cur.wshwtr + + cur.coolUswtr + + cur.cleanUswtr + + cur.drwtUswtr + + cur.dltnUswtr + + cur.ithlRswtr + + cur.ithlEtcUswtr; + acc.outUsage += + cur.cleanToilUswtr + + cur.resn + + cur.lndwtr + + cur.hydUswtr + + cur.ugwtChr + + cur.farmUswtr + + cur.rvrKeepUswtr + + cur.indstUswtr; + acc.total = acc.inUsage + acc.outUsage; + + return acc; + }, + { total: 0, inUsage: 0, outUsage: 0 } + ); +}; + export const ruseMapSize = 15; export type ruseMapState = { ruseMap: { @@ -61,6 +92,141 @@ export const ruseMapStateExport: ruseMapState = { }, }; +const hasuListFetch = async ( + page: string, + total: string, + reurnValue: ReturnType, + searchNm: string | undefined, + year: string, + flcts: string[] +) => { + if (flcts.length === 0) { + return { data: [], count: 0 }; + } + + let url = ``; + let filteredFclt = flcts; + let nameCd = "0", + partStr = "A", + sidogunValue = "0"; + + switch (reurnValue.partValue) { + case "R": + if (reurnValue.regionValue !== "0") { + if (reurnValue.unitWaterShedValue === "0") nameCd = reurnValue.regionValue; + else if (reurnValue.middleRegionValue === "0") nameCd = reurnValue.unitWaterShedValue; + else nameCd = reurnValue.middleRegionValue; + } + partStr = reurnValue.partValue; + break; + default: + if (reurnValue.sidoValue && reurnValue.sidoValue !== "0") nameCd = reurnValue.sidoValue; + if (reurnValue.sigunValue && reurnValue.sigunValue !== "0") nameCd = reurnValue.sigunValue; + sidogunValue = nameCd; + partStr = reurnValue.envPartValue !== "0" ? reurnValue.envPartValue : "A"; + break; + } + + if (sidogunValue && sidogunValue !== "0") { + filteredFclt = flcts.filter((fclt: string) => { + return sidogunValue.length == 2 + ? fclt.slice(0, 2) === sidogunValue + : fclt.slice(0, 5) === sidogunValue; + }); + } + + url = `${partStr}/${nameCd}/0/1/SW`; + + if (searchNm && searchNm.length > 0) { + url += "/" + searchNm; + } + + if (filteredFclt.length === 0) { + return { data: [], count: 0 }; + } + + const pbnList = await postFectch( + `/prcsFclt/ruseInfo/1/${filteredFclt.length}/` + url, + filteredFclt, + fetchType.COMMON + ); + + const checkRuseFcltList = pbnList.data.map((item) => item.bscFctCd); + + const getRuseList = await postFectch( + `/reuseFc/pubDetailList/${year}/${sidogunValue}/${page}/${total}`, + checkRuseFcltList, + fetchType.WR + ); + const checkWrRegListRes = getRuseList.data; + + const data: WaterReuseFacility[] = checkWrRegListRes + .map((item: FcPrfType) => { + const findData = pbnList.data.find( + (fclt: pubHasuType) => + fclt.bscFctCd === item.pbnstRuseFcltId.substring(0, 11) + ); + + if (findData) { + const usage = item.prfList.reduce( + (acc, cur) => { + acc.inUsage += + cur.wshwtr + + cur.coolUswtr + + cur.cleanUswtr + + cur.drwtUswtr + + cur.dltnUswtr + + cur.ithlRswtr + + cur.ithlEtcUswtr; + acc.outUsage += + cur.cleanToilUswtr + + cur.resn + + cur.lndwtr + + cur.hydUswtr + + cur.ugwtChr + + cur.farmUswtr + + cur.rvrKeepUswtr + + cur.indstUswtr; + acc.total = acc.inUsage + acc.outUsage; + + return acc; + }, + { total: 0, inUsage: 0, outUsage: 0 } + ); + return { + rvbsn: findData?.rvbsn || "-", + lgbsn: findData?.lgbsn || "-", + mdmbsn: findData?.mdmbsn || "-", + rgnNm: findData?.rgnNm || "-", + sido: findData?.sido || "-", + gugun: findData?.gugun || "-", + bscFctNm: findData?.bscFctNm || "-", + efflWqltStndrdNm: findData?.efflWqltStndrdNm || "-", + fctCpc: Number(findData?.fctCpc) || 0, + tel: findData?.tel ?? "-", + ruseUtztnFcltNm: item?.ruseUtztnFcltNm || "-", + fcltCpct: item?.fcltCpct || "-", + splyExtraCpct: item?.splyExtraCpct || "-", + ruseCpct: item?.ruseCpct || "-", + pbnstRuseFcltId: item.pbnstRuseFcltId || "-", + usageTotal: usage?.total, + inUsage: usage?.inUsage, + outUsage: usage?.outUsage, + operMthd: item.operList[0]?.operMthd || "-", + rprtvBzenty: item.operList[0]?.rprtvBzenty, + bgngDt: moment(item.operList[0]?.bgngDt).format("YYYY-MM-DD") ?? "", + endDt: moment(item.operList[0]?.endDt).format("YYYY-MM-DD") ?? "", + searchNm: findData?.bscFctNm || "-", + searchPk: findData?.bscFctCd || "-", + prfList: item?.prfList ?? [], + }; + } + }) + .filter(Boolean); + + return { data: data, count: getRuseList.count }; +}; + export const ruseMapActionsExport: (set: any) => ruseMapActions = ( set: any ) => { @@ -111,22 +277,12 @@ export const ruseMapActionsExport: (set: any) => ruseMapActions = ( try { const ruseDetailList = await getFetch( fetchType.WR, - `/reuseFc/pubFc/${bscFctCd}` - ); - const year = new Date().getFullYear(); - const data = [bscFctCd]; - const rusePrfList = await postFectch( - `/reuseFc/pubDetailList/${year}/0/1/10`, - data, - fetchType.WR + `/reuseFc/pubFc/${bscFctCd}?year=${getCurrentYear()}` ); const dataList = ruseDetailList.map((ruse) => { - const matchRuse = rusePrfList?.data.find( - (prf) => prf.pbnstRuseFcltId === ruse.pbnstRuseFcltId - ); return { ...ruse, - prfList: matchRuse.prfList, + prfList: ruse.prfList ?? [], }; }); console.log("/dataList ", dataList); @@ -195,51 +351,13 @@ export const ruseMapActionsExport: (set: any) => ruseMapActions = ( try { const ruseDetailList = await getFetch( fetchType.WR, - `/reuseFc/pubFc/${bscFctCd}` - ); - const year = new Date().getFullYear(); - const data = [bscFctCd]; - const rusePrfList = await postFectch( - `/reuseFc/pubDetailList/${year}/0/1/10`, - data, - fetchType.WR + `/reuseFc/pubFc/${bscFctCd}?year=${getCurrentYear()}` ); const dataList = ruseDetailList.map((ruse) => { - const matchRuse = rusePrfList?.data.find( - (prf) => prf.pbnstRuseFcltId === ruse.pbnstRuseFcltId - ); - - - const usage = matchRuse.prfList.reduce( - (acc, cur) => { - acc.inUsage += - cur.wshwtr + - cur.coolUswtr + - cur.cleanUswtr + - cur.drwtUswtr + - cur.dltnUswtr + - cur.ithlRswtr + - cur.ithlEtcUswtr + - cur.cleanToilUswtr + - cur.resn; - acc.outUsage += - cur.lndwtr + - cur.hydUswtr + - cur.ugwtChr + - cur.farmUswtr + - cur.rvrKeepUswtr + - cur.indstUswtr; - acc.total = acc.inUsage + acc.outUsage; - - return acc; - }, - { total: 0, inUsage: 0, outUsage: 0 } - ); - - + const usage = sumPrfUsage(ruse.prfList ?? []); return { ...ruse, - prfList: matchRuse.prfList, + prfList: ruse.prfList ?? [], inUsage: usage?.inUsage, outUsage: usage?.outUsage, diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx index 83057114c..cadba69d4 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx @@ -1,229 +1,155 @@ -/** +/** * 재이용실적등록 > 하수재이용실적등록 - * - * 🔗 사용 API - * - 하수재이용시설 실적 조회: POST /api/v1/reuseFc/pubDetailList/{year}/{sggOrCity}/{page}/{pageSize} (reuse-service) - * - 하수재이용시설 실적 등록: POST /api/v1/reuseFc/pubFcPrf (reuse-service) - * - 공공재이용시설 목록 조회: POST /api/v1/prcsFclt/ruseInfo/{page}/{pageSize}/{type}/{sggOrCity}/{etc1}/{etc2}/{etc3} (server-service) - * - 공공재이용시설 목록 조회(이름검색): POST /api/v1/prcsFclt/ruseInfo/{page}/{pageSize}/{type}/{sggOrCity}/{searchTxt} (server-service) - * - 공공재이용시설 조회: GET /api/v1/reuseFc/pubFc (reuse-service) + * + * 🔗 사용 API + * - 좌측 목록 조회: GET /api/v1/reuseFc/pbnstPrfRegList/{page}/{pageSize}/{searchingCd}/{nameCd} + * - 우측 상세 조회: GET /api/v1/reuseFc/pbnstPrfRegDtl/{year}/{pbnstRuseFcltId} + * - 하수재이용시설 실적 등록: POST /api/v1/reuseFc/pubFcPrf **/ -import { fetchType, getFetch, postFectch } from "@/components/lib/api"; +import { fetchType, getFetch, getFetchWithCount, postFectch } from "@/components/lib/api"; import { pbnstPrfmncHeadListExport, monthHeadListExport } from "./headList"; -import type { pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type"; -import type { FcPrfType, PrfType } from "./type"; +import type { PbnstPrfRegDtlType, PbnstPrfRegListType, PrfType } from "./type"; import type { TableUpperProps } from "../../common/type/header"; const pbnstPrfmncHeadList = pbnstPrfmncHeadListExport; const monthHeadList = monthHeadListExport; export const prfRegPageSize = 10; + export type pbnstPrfmncState = { prfmnc: { isInit: boolean; pbnstPrfmncHeadList: TableUpperProps[]; monthHeadList: TableUpperProps[]; - prfmncRes: FcPrfType[]; + prfmncRes: PbnstPrfRegListType[]; prfmncResCount: number; excelList: number[]; pageKey: number; - prfPbnFclts: string[]; + prfmncDetail: PbnstPrfRegDtlType | null; }; }; export const pbnstPrfmncStateExport: pbnstPrfmncState = { prfmnc: { isInit: false, - pbnstPrfmncHeadList: pbnstPrfmncHeadList, - monthHeadList: monthHeadList, + pbnstPrfmncHeadList, + monthHeadList, prfmncRes: [], - prfPbnFclts: [], prfmncResCount: 1, excelList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], pageKey: 1, + prfmncDetail: null, }, }; export type pbnstPrfmncType = pbnstPrfmncState & pbnstPrfmncActions; type pbnstPrfmncActions = { - prfmncHasuInit: ( - page: number, - sggOrCity: string, - year: string, - prfPbnFclts: string[] - ) => void; - prfmncHasuNmSearch: ( - page: number, - sggOrCity: string, - year: string, - searchTxt: string, - prfPbnFclts: string[] - ) => void; - prfmnRegData: ( - page: number, - sggOrCity: string, - data: PrfType, - year: string, - prfPbnFclts: string[] - ) => Promise; - getPrfFmcRes: () => void; + prfmncHasuInit: (page: number, sggOrCity: string) => void; + prfmncHasuNmSearch: (page: number, sggOrCity: string, searchTxt: string) => void; + prfmnRegData: (data: PrfType[], year: string, pbnstRuseFcltId: string) => Promise; + getPbnstPrfRegDtl: (year: string, pbnstRuseFcltId: string) => Promise; }; -const hasuListFetch = async ( +const fetchPbnstPrfRegList = async ( page: number, sggOrCity: string, - year: string, - prfPbnFclts: string[], searchTxt?: string ) => { - let filteredFclt = prfPbnFclts; - if (sggOrCity !== "0") { - filteredFclt = prfPbnFclts?.filter((fclt: string) => { - return sggOrCity.length == 2 - ? fclt.slice(0, 2) === sggOrCity - : fclt.slice(0, 5) === sggOrCity; - }); + let url = `/reuseFc/pbnstPrfRegList/${page}/${prfRegPageSize}/A/${sggOrCity}`; + if (searchTxt && searchTxt.trim().length > 0) { + url += `?swtrmtFcltNm=${encodeURIComponent(searchTxt.trim())}`; } - //let url = `/prcsFclt/ruseInfo/1/${filteredFclt.length + 1}/A/${sggOrCity}/0/1/SW`; - let url = `/prcsFclt/ruseInfo/1/${filteredFclt.length + 1}`; - if (searchTxt && searchTxt?.length > 0) { - url += `/${searchTxt}`; - } - const rainRes = await postFectch(url, filteredFclt, fetchType.COMMON); - const checkRuseFcltList = - searchTxt && searchTxt?.length > 0 - ? rainRes.data.map((item) => item.bscFctCd) - : filteredFclt; - - const pubRuseList = await postFectch( - `/reuseFc/pubDetailList/${year}/${sggOrCity}/${page}/${prfRegPageSize}`, - checkRuseFcltList, - fetchType.WR - ); - const rusePrfList = pubRuseList.data - .map((item: FcPrfType) => { - const findData = rainRes.data?.find( - (fclt: pubHasuType) => - item.pbnstRuseFcltId.substring(0, 11) === fclt?.bscFctCd - ); - if (findData !== undefined) { - return { - sido: findData.sido, - gugun: findData.gugun, - bscFctNm: findData.bscFctNm, - bscFctCd: findData.bscFctCd, - ruseUtztnFcltNm: item.ruseUtztnFcltNm, - pbnstRuseFcltId: item.pbnstRuseFcltId, - prfList: item.prfList, - }; - } - }) - .filter(Boolean); // undefined 값을 제거합니다. - - return { data: rusePrfList, count: pubRuseList.count }; + const response = await getFetchWithCount(fetchType.WR, url); + return { + data: (response?.data ?? []) as PbnstPrfRegListType[], + count: response?.count ?? 0, + }; }; + export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( set: any ) => { return { - getPrfFmcRes: async () => { + prfmncHasuInit: async (page: number, sggOrCity: string) => { try { - const pbnPrfRes = await getFetch(fetchType.WR, "/reuseFc/pubFc"); - set((state: pbnstPrfmncState) => ({ + const prfmncRes = await fetchPbnstPrfRegList(page, sggOrCity); + return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { ...state.prfmnc, - prfPbnFclts: pbnPrfRes, + prfmncRes: prfmncRes.data, + prfmncResCount: prfmncRes.count, + prfmncDetail: null, + isInit: false, }, })); } catch (error) { console.log(error); } }, - prfmncHasuInit: async ( - page: number, - sggOrCity: string, - year: string, - prfPbnFclts: string[] - ) => { - try { - const prfmncRes = await hasuListFetch( - page, - sggOrCity, - year, - prfPbnFclts - ); - return set((state: pbnstPrfmncState) => ({ - ...state, - prfmnc: { - ...state.prfmnc, - prfmncRes: prfmncRes.data, - prfmncResCount: prfmncRes.count, - isInit: false, - }, - })); - } catch (errorHasu) { - console.log(errorHasu); - } - }, prfmncHasuNmSearch: async ( page: number, sggOrCity: string, - year: string, - searchTxt: string, - prfPbnFclts: string[] + searchTxt: string ) => { try { - const prfmncRes = await hasuListFetch( - page, - sggOrCity, - year, - prfPbnFclts, - searchTxt - ); + const prfmncRes = await fetchPbnstPrfRegList(page, sggOrCity, searchTxt); return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { ...state.prfmnc, prfmncRes: prfmncRes.data, prfmncResCount: prfmncRes.count, + prfmncDetail: null, isInit: false, }, })); - } catch (errorHasu) { - console.log(errorHasu); + } catch (error) { + console.log(error); } }, - prfmnRegData: async ( - page: number, - sggOrCity: string, - data: PrfType, - year: string, - prfPbnFclts: string[] - ) => { + getPbnstPrfRegDtl: async (year: string, pbnstRuseFcltId: string) => { + try { + const detail = (await getFetch( + fetchType.WR, + `/reuseFc/pbnstPrfRegDtl/${year}/${pbnstRuseFcltId}` + )) as PbnstPrfRegDtlType; + + set((state: pbnstPrfmncState) => ({ + ...state, + prfmnc: { + ...state.prfmnc, + prfmncDetail: detail, + }, + })); + + return detail; + } catch (error) { + console.log(error); + return undefined; + } + }, + prfmnRegData: async (data: PrfType[], year: string, pbnstRuseFcltId: string) => { const failMessage = "하수재이용 시설 실적 등록이 실패하였습니다."; try { - const ressponse = await postFectch( + const response = await postFectch( "/reuseFc/pubFcPrf", data, fetchType.WR ); - if (ressponse.status === 200) { - const prfmncRes = await hasuListFetch( - page, - sggOrCity, - year, - prfPbnFclts - ); + if (response.status === 200) { + const detail = (await getFetch( + fetchType.WR, + `/reuseFc/pbnstPrfRegDtl/${year}/${pbnstRuseFcltId}` + )) as PbnstPrfRegDtlType; set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { ...state.prfmnc, - prfmncRes: prfmncRes.data, - prfmncResCount: prfmncRes.count, + prfmncDetail: detail, isInit: false, pageKey: state.prfmnc.pageKey + 1, }, diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/type.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/type.tsx index 6723f3922..ff53a381f 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/type.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/type.tsx @@ -54,8 +54,26 @@ export const FcPrfSchema = z.object({ bscFctNm: z.string().nullish(), }); +export const PbnstPrfRegListSchema = z.object({ + swageBscFctNm: z.string().default(""), + ruseUtztnFcltNm: z.string().default(""), + pbnstRuseFcltId: z.string().default(""), + sidoCd: z.string().default(""), + sggCd: z.string().default(""), + totalCount: z.number().default(0), +}); + +export const PbnstPrfRegDtlSchema = z.object({ + pbnstRuseFcltId: z.string().default(""), + ruseUtztnFcltNm: z.string().default(""), + swageBscFctNm: z.string().default(""), + prfList: z.array(PrfSchema).default([]), +}); + export type FcPrfType = z.infer; export type PrfType = z.infer; +export type PbnstPrfRegListType = z.infer; +export type PbnstPrfRegDtlType = z.infer; export const initValInUsage:PrfUsageSummary[] = [ { diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx index fe6b606f6..1305c0126 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx @@ -152,7 +152,8 @@ const hasuListFetch = async ( }; const initFetch = async (ruseFcltPk: string | undefined) => { - const pbnstRes = await getFetch("wr", `/reuseFc/pubFc/${ruseFcltPk}`); + const year = new Date().getFullYear(); + const pbnstRes = await getFetch("wr", `/reuseFc/pubFc/${ruseFcltPk}?year=${year}`); return pbnstRes; }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx index cb86777e0..3a9756693 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx @@ -302,7 +302,7 @@ export const pbnstRegZod = z.object({ fcOpers://z.array(fcOperSchema).default([]), z .array(fcOperSchema) .min(1, { message: "재이용시설의 운영정보를 최소 하나를 등록해주세요" }), - prfList: z.array(PrfSchema).nullish(), + prfList: z.array(PrfSchema).nullish(), }); diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/headList.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/headList.tsx index cca75609f..c6a50bf5c 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/headList.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/headList.tsx @@ -63,6 +63,7 @@ export const pbnstSttNodeListExport: CommonTableProps[] = [ { id: "endDt", title: "종료일", upName: "mngInfo", visible: true, row: 2, rowSpan: 1, colSpan: 1, }, ]; +// 하수처리수 재이용시설 현황 tab 상단 export const ruseFcltNodeListExport: TableUpperProps[] = [ { id: "cycl", title: "차수", visible: true, row: 1, rowSpan: 3, colSpan: 1 }, { id: "seCd", title: "구분", visible: true, row: 1, rowSpan: 3, colSpan: 1 }, @@ -84,6 +85,7 @@ export const ruseFcltNodeListExport: TableUpperProps[] = [ { id: "totalMoney", title: "합계", visible: true, row: 3, rowSpan: 1, colSpan: 1 }, ]; +// 재이용 원수 취수 현황 export const ruseSplyHeadListExport: TableUpperProps[] = [ { id: "cycl", title: "차수", visible: true }, { id: "orgwtWtritQnt", title: "원수취수량(m³/일)", visible: true }, @@ -125,6 +127,7 @@ export const ruseDmdHeadListExport: TableUpperProps[] = [ { id: "splyFmYon", title: "공급유량계 유무", visible: true }, ]; +// 하수처리수 재이용시설 현황 tab 하단 export const ruseFcltHeadListExport: CommonTableProps[] = [ { id: "cycl", title: "차수", visible: true}, { id: "seCd", title: "구분", visible: true}, diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx index 5b2a72acf..0aca48f71 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx @@ -2,10 +2,8 @@ import type { CommonTableProps, TableUpperProps, } from "@/app/store/common/type/header"; -import type { FcPrfType } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; import { fetchType, getFetch, getFetchWithCount, postFectch } from "@/components/lib/api"; import type { ReturnType } from "@common/business_components/ui"; -import moment from "moment"; import { type z } from "zod"; import type { customPbnst, pubHasuType } from "../../fcltReg/pbnstReg/type"; import { controlHeadView } from "../rainSttn"; @@ -34,7 +32,6 @@ const pbnstSttNodeList = pbnstSttNodeListExport; export type pbnstSttnState = { pbnstSttn: { pbnstSttnRes: WaterReuseFacility[]; - pbnstSearchData: WaterReuseFacility[]; pbnstSttHeadList: CommonTableProps[]; pbnstSttNodeList: CommonTableProps[]; pbnstDetail: customPbnst; @@ -46,8 +43,6 @@ export type pbnstSttnState = { ruseFcltHeadList: TableUpperProps[]; pbnstSttnResCount: number; excelList: []; - fclts: string[]; - isFcltsLoaded: boolean; // wr 하수재이용시설 목록 조회 여부 isTableLoaded: boolean; // 현황 테이블 초기 조회 여부 }; }; @@ -60,35 +55,9 @@ type pbnstSttnActions = { total: string, reurnValue: ReturnType, searchNm: string | undefined, - year: string, - fclts: string[] - ) => void; - pbnstSttnSearch: ( - isExcel: boolean, - page: string, - total: string, - reurnValue: ReturnType, - searchNm: string | undefined, - year: string, - fclts: string[] - ) => void; - getFlcts: () => void; - pbnSttnInit: ( - page: string, - total: string, - reurnValue: ReturnType, - year: string, - flcts: string[] - ) => void; - getPbnStnDetail: (pbnstRuseFcltId: string) => void; - pbnNmSearch: ( - page: string, - total: string, - reurnValue: ReturnType, - searchNm: string | undefined, - year: string, - flcts: string[] + year: string ) => void; + getPbnStnDetail: (pbnstRuseFcltId: string, year: string) => void; resetPbnstDetail: () => void; }; @@ -98,163 +67,20 @@ export const pbnstSttnStateExport: pbnstSttnState = { pbnstSttn: { pbnstSttHeadList: pbnstSttHeadList, pbnstSttNodeList: pbnstSttNodeList, - ruseFcltNodeList: ruseFcltNodeListExport, + ruseFcltNodeList: ruseFcltNodeListExport, // 하수재이용시설 상세정보 - 하수처리수 재이용시설 현황 tab 상단 ruseSplyHeadList: ruseSplyHeadListExport, rusePplHeadList: rusePplHeadListExport, ruseGuranteeHeadList: ruseGuranteeHeadListExport, ruseDmdHeadList: ruseDmdHeadListExport, - ruseFcltHeadList: ruseFcltHeadListExport, + ruseFcltHeadList: ruseFcltHeadListExport, // 하수재이용시설 상세정보 - 하수처리수 재이용시설 현황 tab 하단 pbnstSttnRes: [], pbnstSttnResCount: 1, pbnstDetail: {} as customPbnst, - pbnstSearchData: [], excelList: [], - fclts: [], - isFcltsLoaded: false, isTableLoaded: false, }, }; -export const hasuListFetch = async ( - page: string, - total: string, - reurnValue: ReturnType, - searchNm: string | undefined, - year: string, - flcts: string[] -) => { - - // 시설 코드가 없으면 FO 조회를 호출하지 않고 빈 결과 반환 - if (flcts.length === 0) { - return { data: [], count: 0 }; - } - - let url = ``; - let filteredFclt = flcts; - let nameCd="0", partStr="A", sidogunValue="0"; - switch (reurnValue.partValue) { - case 'R': // 유역별 - if (reurnValue.regionValue !== '0') { - if(reurnValue.unitWaterShedValue==='0') nameCd = reurnValue.regionValue; - else if (reurnValue.middleRegionValue==='0') nameCd = reurnValue.unitWaterShedValue; - else nameCd = reurnValue.middleRegionValue; - } - partStr = reurnValue.partValue; - break; - default: - if(reurnValue.sidoValue && reurnValue.sidoValue !== '0') nameCd = reurnValue.sidoValue; - if(reurnValue.sigunValue &&reurnValue.sigunValue !== '0') nameCd = reurnValue.sigunValue; - sidogunValue = nameCd; - partStr = reurnValue.envPartValue !== '0' ? reurnValue.envPartValue:'A'; - break; - } - - if (sidogunValue && sidogunValue !== "0") { // 시도, 시군구 조회 조건이 있다면 - filteredFclt = flcts.filter((fclt: string) => { - return sidogunValue.length == 2 - ? fclt.slice(0, 2) === sidogunValue - : fclt.slice(0, 5) === sidogunValue; - }); - } - - url = `${partStr}/${nameCd}/0/1/SW`; - - if (searchNm && searchNm?.length > 0) { - url += "/" + searchNm; - } - // console.log(url); - - // 시설 코드가 없으면 FO 조회를 호출하지 않고 빈 결과 반환 - if (filteredFclt.length === 0) { - return { data: [], count: 0 }; - } - - // fo의 하수처리시설을 조회 - const pbnList = await postFectch( - `/prcsFclt/ruseInfo/1/${filteredFclt.length}/` + url, - filteredFclt, - fetchType.COMMON // FO - ); - - const checkRuseFcltList = pbnList.data.map((item) => item.bscFctCd); - // console.log(checkRuseFcltList); - - - const getRuseList = await postFectch( - `/reuseFc/pubDetailList/${year}/${sidogunValue}/${page}/${total}`, - checkRuseFcltList, - fetchType.WR - ); - const checkWrRegListRes = getRuseList.data; - // console.log(getRuseList); - - const data: WaterReuseFacility[] = checkWrRegListRes - .map((item: FcPrfType) => { - const findData = pbnList.data.find( - (fclt: pubHasuType) => - fclt.bscFctCd === item.pbnstRuseFcltId.substring(0, 11) - ); - - if (findData) { - const usage = item.prfList.reduce( - (acc, cur) => { - acc.inUsage += - cur.wshwtr + - cur.coolUswtr + - cur.cleanUswtr + - cur.drwtUswtr + - cur.dltnUswtr + - cur.ithlRswtr + - cur.ithlEtcUswtr + - cur.cleanToilUswtr + - cur.resn; - acc.outUsage += - cur.lndwtr + - cur.hydUswtr + - cur.ugwtChr + - cur.farmUswtr + - cur.rvrKeepUswtr + - cur.indstUswtr; - acc.total = acc.inUsage + acc.outUsage; - - return acc; - }, - { total: 0, inUsage: 0, outUsage: 0 } - ); - return { - rvbsn: findData?.rvbsn || "-", - lgbsn: findData?.lgbsn || "-", - mdmbsn: findData?.mdmbsn || "-", - rgnNm: findData?.rgnNm || "-", - sido: findData?.sido || "-", - gugun: findData?.gugun || "-", - bscFctNm: findData?.bscFctNm || "-", - efflWqltStndrdNm: findData?.efflWqltStndrdNm || "-", - fctCpc: Number(findData?.fctCpc) || 0, - tel: findData?.tel ?? "-", - ruseUtztnFcltNm: item?.ruseUtztnFcltNm || "-", - fcltCpct: item?.fcltCpct || "-", - splyExtraCpct: item?.splyExtraCpct || "-", - ruseCpct: item?.ruseCpct || "-", - pbnstRuseFcltId: item.pbnstRuseFcltId || "-", - usageTotal: usage?.total, - inUsage: usage?.inUsage, - outUsage: usage?.outUsage, - operMthd: item.operList[0]?.operMthd || "-", - rprtvBzenty: item.operList[0]?.rprtvBzenty, - bgngDt: moment(item.operList[0]?.bgngDt).format("YYYY-MM-DD") ?? "", - endDt: moment(item.operList[0]?.endDt).format("YYYY-MM-DD") ?? "", - searchNm: findData?.bscFctNm || "-", - searchPk: findData?.bscFctCd || "-", - prfList: item?.prfList ?? [], - }; - } - }) - .filter(Boolean); - - return { data: data, count: getRuseList.count }; -}; - export const pbnstListFetch = async ( page: string, total: string, @@ -361,7 +187,6 @@ export const pbnstListFetch = async ( rprtvBzenty: item.rprtvBzenty || "-", bgngDt: item.bgngDt || "", endDt: item.endDt || "", - prfList: item.prfList ?? [], }; }); @@ -372,25 +197,13 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( set: any ) => { return { - //등록된 하수재이용시설을 조회한다. - getFlcts: async () => { - const fclts = await getFetch(fetchType.WR, "/reuseFc/pubFc"); - return set((state: pbnstSttnState) => ({ - pbnstSttn: { - ...state.pbnstSttn, - isFcltsLoaded: true, - fclts: fclts, - }, - })); - }, pbnstSttnList: async ( isExcel: boolean, page: string, total: string, reurnValue: ReturnType, searchNm: string | undefined, - year: string, - // _fclts: string[] + year: string ) => { try { const pbnstSttnRes = await pbnstListFetch( @@ -440,90 +253,6 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( }, })); }, - //하수처리시설을 조회하여 하수재이용시설을 확인한다. - pbnstSttnSearch: async ( - isExcel: boolean, - page: string, - total: string, - reurnValue: ReturnType, // 검색조건 - searchNm: string | undefined, - year: string, - fclts: string[] - ) => { - try { - // console.log(reurnValue); - const pbnstSttnRes = await hasuListFetch( - page, - total, - reurnValue, - searchNm, - year, - fclts - ); - - // if (Number(total) > 9000) { - if (isExcel) { - const pbnstExcel = excelPbnstSttnParsing(pbnstSttnRes.data); - set((state: pbnstSttnState) => { - return { - ...state, - pbnstSttn: { - ...state.pbnstSttn, - excelList: pbnstExcel, - }, - }; - }); - } else { - set((state: pbnstSttnState) => { - return { - ...state, - pbnstSttn: { - ...state.pbnstSttn, - pbnstSttnRes: pbnstSttnRes.data, - pbnstSttnResCount: pbnstSttnRes.count, - isTableLoaded: true, - }, - }; - }); - } - } catch (erTxt) { - console.log("error", erTxt); - } - }, - //하수처리시설을 조회하여 하수재이용시설을 조회한다. - pbnSttnInit: async ( - page: string, - total: string, - reurnValue: ReturnType, - year: string, - fclts: string[] - ) => { - try { - const pbnstSttnRes = await hasuListFetch( - page, - total, - reurnValue, - undefined, - year, - fclts - ); - - set((state: pbnstSttnState) => { - return { - ...state, - pbnstSttn: { - ...state.pbnstSttn, - pbnstSttnRes: pbnstSttnRes.data, - pbnstSttnResCount: pbnstSttnRes.count, - pbnstDetail: {} as customPbnst, - isTableLoaded: true, - }, - }; - }); - } catch (erTxt) { - console.log("error", erTxt); - } - }, // 팝업에 있는 데이터를 초기화한다. resetPbnstDetail: async () => { set((state: pbnstSttnState) => { @@ -537,17 +266,18 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( }); }, //하수처리시설을 조회하여 하수재이용시설을 조회한다. - getPbnStnDetail: async (pbnstRuseFcltId: string) => { + getPbnStnDetail: async (pbnstRuseFcltId: string, year: string) => { try { - let pbnstDetail = await getFetch( + const pbnstDetailList = await getFetch( "wr", - `/reuseFc/pubFc/${pbnstRuseFcltId}` + `/reuseFc/pubFc/${pbnstRuseFcltId}?year=${year}` ); - if (pbnstDetail.length >= 0) { - pbnstDetail = pbnstDetail.find( - (item: customPbnst) => item.pbnstRuseFcltId === pbnstRuseFcltId - ); - } + const pbnstDetail = Array.isArray(pbnstDetailList) + ? pbnstDetailList.find( + (item: customPbnst) => item.pbnstRuseFcltId === pbnstRuseFcltId + ) + : undefined; +console.log(pbnstDetail); set((state: pbnstSttnState) => { return { ...state, @@ -561,38 +291,5 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( console.log(errorHasu); } }, - //하수재이용시설 실적 현황 에서 사용하는 조회 - pbnNmSearch: async ( - page: string, - total: string, - reurnValue: ReturnType, - searchNm: string | undefined, - year: string, - flcts: string[] - ) => { - try { - const pbnstSttnRes = await hasuListFetch( - page, - total, - reurnValue, - searchNm, - year, - flcts - ); - - set((state: pbnstSttnState) => { - return { - ...state, - pbnstSttn: { - ...state.pbnstSttn, - pbnstSearchData: pbnstSttnRes.data, - pbnstSttnResCount: pbnstSttnRes.count, - }, - }; - }); - } catch (e) { - console.log(e); - } - }, }; }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx index ee202d4f6..955ecd270 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx @@ -1,5 +1,3 @@ -import type { PrfType } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; - export type WaterReuseFacility = { swtrmtFcltId?: string; bscFctNm: string; // 기본시설명 (예: "난지") @@ -24,5 +22,4 @@ export type WaterReuseFacility = { rprtvBzenty: string; bgngDt: string; endDt: string; - prfList:PrfType[]; }; diff --git a/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx b/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx index d1d4fcd59..25d80794f 100644 --- a/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx +++ b/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx @@ -4,7 +4,7 @@ import { excelToPbnPrfData } from "@/app/store/prfmncReg/pbnstPrfmncReg/excel"; import { pbnPrfRegExcelZod } from "@/app/store/prfmncReg/pbnstPrfmncReg/headList"; import type { - FcPrfType, + PbnstPrfRegDtlType, PrfType, } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; import { yearOptions } from "@/components/lib/func/common"; @@ -18,7 +18,7 @@ import { } from "@common/business_components/ui"; import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; // import toast from "react-hot-toast"; import type { z } from "zod"; @@ -29,13 +29,14 @@ export default function PbnPrfExcelModal({ setIsExcelOPen, setModalExcelData, }: { - data: FcPrfType; + data: PbnstPrfRegDtlType; setIsExcelOPen: (open: boolean) => void; - setModalExcelData: (excelData: PrfType) => void; + setModalExcelData: (excelData: PrfType[]) => void; }) { const alert = useAlertStore((state) => state.setMessage); const [file, setFile] = useState(null); const [excelData, setExcelData] = useState(); + const fileInputRef = useRef(null); const form = useForm>({ resolver: zodResolver(pbnPrfRegExcelZod), defaultValues: { @@ -44,26 +45,32 @@ export default function PbnPrfExcelModal({ }); const handleFileChange = (e: React.ChangeEvent) => { + setExcelData(undefined); const selectedFile = e.target.files?.[0]; if (selectedFile) { setFile(selectedFile); } }; const onSubmit = (values: z.infer) => { - if (excelData) { - ////TODO: 궅이 서브밋을 써야하는 이유가 있는지 ? - console.log("values ", values); - // prfmnRegData(1,'41', excelData as unknown as PrfType); 활성화시 스토어 활성화 칠요 - - setModalExcelData(excelData as unknown as PrfType); - setIsExcelOPen(false); + if (!file) { + alert("엑셀 파일을 선택해 주세요."); + return; } + + if (!excelData || excelData.length === 0) { + alert("엑셀 파일을 먼저 확인해 주세요."); + return; + } + + ////TODO: 궅이 서브밋을 써야하는 이유가 있는지 ? + console.log("values ", values); + setModalExcelData(excelData); + setIsExcelOPen(false); }; useEffect(() => { - if (data && data.bscFctNm) - form.setValue("pbnstRuseFcltId", data.pbnstRuseFcltId); - form.setValue("bscFctNm", data.bscFctNm || ""); - form.setValue("pbnstRuseFcltNm", data.ruseUtztnFcltNm); + form.setValue("pbnstRuseFcltId", data.pbnstRuseFcltId || ""); + form.setValue("bscFctNm", data.swageBscFctNm || ""); + form.setValue("pbnstRuseFcltNm", data.ruseUtztnFcltNm || ""); }, [data]); useEffect(() => { @@ -76,20 +83,42 @@ export default function PbnPrfExcelModal({ return; } + if (!form.getValues("pbnstRuseFcltId")) { + alert("재이용시설을 다시 선택한 뒤 파일등록을 진행해 주세요."); + setFile(null); + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + return; + } + if (isFile) { filePicked(isFile, 0).then((res) => { - if (res !== undefined) - setExcelData( - excelToPbnPrfData( - res as unknown as JSON[], - form.getValues("ym"), - form.getValues("pbnstRuseFcltId") - ) as PrfType[] - ); + if (res === undefined) { + alert("엑셀 파일을 읽지 못했습니다."); + return; + } + + const parsedData = excelToPbnPrfData( + res as unknown as JSON[], + form.getValues("ym"), + form.getValues("pbnstRuseFcltId") + ) as PrfType[]; + + if (!parsedData.length) { + alert("업로드 가능한 실적 데이터가 없습니다. 양식을 확인해 주세요."); + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + setFile(null); + return; + } + + setExcelData(parsedData); }); } } - }, [file]); + }, [alert, file, form]); return ( <> @@ -177,10 +206,11 @@ export default function PbnPrfExcelModal({
{ + onChange={(event) => { console.log(" field. valus", field.value); - if (file) handleFileChange(file); + handleFileChange(event); }} accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" /> diff --git a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx index 0c168ade8..fe69e0b4c 100644 --- a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx +++ b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx @@ -1,3 +1,6 @@ +/** + * 하수재이용시설 현황 상세정보 modal + */ import { headMakeprfmnc } from "@/app/(with-layout)/ruseFcltMng/prfmncReg/chkgdnReg/TableHead"; import { useWrStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common/type/header"; @@ -57,7 +60,7 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo useEffect(() => { let tableData; - if (fcltTab === "fclt") { + if (fcltTab === "fclt") { // 하수처리수 재이용시설 현황 tableData = data.fcBizs?.map((item: fcBizType) => { return { cycl: item.cycl + "차", @@ -81,7 +84,7 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo mainSplyPumpCpct: item.mainSplyPumpCpct ?? "-", }; }); - } else if (fcltTab === "sply") { + } else if (fcltTab === "sply") { // 재이용 원수 취수 현황 tableData = data.fcBizs.flatMap((item: fcBizType) => { return item?.fcOrgwts?.map((fcOrgwt) => { return { @@ -93,7 +96,7 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo }; }); }); - } else if (fcltTab === "ppi") { + } else if (fcltTab === "ppi") { // 재이용 공급관로 현황 tableData = data.fcBizs?.flatMap((item: fcBizType) => { return item?.pplInstls?.map((ppl) => { return { @@ -104,7 +107,7 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo }; }); }); - } else if (fcltTab === "guarantee") { + } else if (fcltTab === "guarantee") { // 재이용 보증수질 현황 tableData = data.fcBizs?.map((item: fcBizType) => { return { cycl: item.cycl + "차", @@ -121,7 +124,7 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo cdtnv: item.cdtnv, }; }); - } else if (fcltTab === "dmdCo") { + } else if (fcltTab === "dmdCo") { // 수요처 tableData = data.fcBizs?.flatMap((item: fcBizType) => { return item?.fcDemands?.map((dmd) => { @@ -143,15 +146,15 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo } }, [fcltTab, data]); useEffect(() => { - if (fcltTab === "fclt") { - setRuseHeadList(pbnstSttn.ruseFcltHeadList); - } else if (fcltTab === "sply") { - setRuseHeadList(pbnstSttn.ruseSplyHeadList); - } else if (fcltTab === "ppi") { + if (fcltTab === "fclt") { // 하수처리수 재이용시설 현황 tab 하단 + setRuseHeadList(pbnstSttn.ruseFcltHeadList); + } else if (fcltTab === "sply") { // 재이용 원수 취수 현황 + setRuseHeadList(pbnstSttn.ruseSplyHeadList); + } else if (fcltTab === "ppi") { // 재이용 공급관로 현황 setRuseHeadList(pbnstSttn.rusePplHeadList); - } else if (fcltTab === "guarantee") { + } else if (fcltTab === "guarantee") { // 재이용 보증수질 현황 setRuseHeadList(pbnstSttn.ruseGuranteeHeadList); - } else if (fcltTab === "dmdCo") { + } else if (fcltTab === "dmdCo") { // 수요처 setRuseHeadList(pbnstSttn.ruseDmdHeadList); } }, [ @@ -225,26 +228,8 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo } }; useEffect(() => { - if(isMap){ - if(data.prfList &&data.prfList.length>0){ - getTablePrfData(data.prfList); - }else{ - getTablePrfData([]); - } - }else{ - - if (pbnstSttn.pbnstSttnRes && data) { - const prfList = pbnstSttn.pbnstSttnRes.find( - (item) => item.pbnstRuseFcltId === data.pbnstRuseFcltId - )?.prfList; - - if (prfList) { - - getTablePrfData(prfList); - } - } - } - }, [pbnstSttn.pbnstSttnRes, data,isMap]); + getTablePrfData(data.prfList ?? []); + }, [data]); useEffect(() => { diff --git a/apps/wr/docs/기준문서/하수재이용실적 등록 api 호출분석_20260630.md b/apps/wr/docs/기준문서/하수재이용실적 등록 api 호출분석_20260630.md new file mode 100644 index 000000000..81f46eb5a --- /dev/null +++ b/apps/wr/docs/기준문서/하수재이용실적 등록 api 호출분석_20260630.md @@ -0,0 +1,245 @@ +# 하수재이용실적 등록 API 호출 분석 + +작성일: 2026-06-30 +## 0. 공통코드 조회 +- 가장 메인 진입점은 [apps/wr/src/app/providers/app-provider.tsx (line 38)](/D:/iipbs/frontend/apps/wr/src/app/providers/app-provider.tsx:38) 의 CommonCodeInitializer +- 실제 공통코드 조회 액션은 [apps/wr/app/store/common/code/index.ts (line 372)](/D:/iipbs/frontend/apps/wr/app/store/common/code/index.ts:372) +- +## 1. 대상 화면 + +- 화면: `재이용실적등록 > 하수재이용실적등록` +- 페이지 파일: [apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:47) +- 스토어 파일: [apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:1) + +## 2. 분석 결론 요약 + +- 좌측 `하수재이용시설 목록`은 단일 API가 아니라 2개 API를 조합해 생성된다. +- 우측 `하수처리수재이용 실적(m³) 등록결과`는 별도 조회 API를 직접 호출하지 않는다. +- 우측 화면의 원본 데이터는 좌측 목록 조회 시 함께 내려오는 `prfList`이다. +- 우측 `저장`은 `POST /reuseFc/pubFcPrf`를 호출하고, 저장 성공 후 좌측 목록 조회 로직을 다시 호출해 화면을 갱신한다. + +## 3. 좌측 하수재이용시설 목록 API 호출 분석 + +### 3.1 화면 진입 시 호출 순서 + +1. 권한 코드 조회 + - `getLaeSeCdCheck()` + - 호출 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:206) +2. WR에 등록된 하수재이용시설 기준 코드 목록 조회 + - `getPrfFmcRes()` + - 호출 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:210) +3. 좌측 목록 초기 조회 + - `prfmncHasuInit(1, laeSeCd, year, prfPbnFclts)` + - 호출 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:217) + +### 3.2 기준 시설코드 목록 조회 + +`getPrfFmcRes()`는 먼저 WR에서 등록된 시설코드 목록을 가져온다. + +- 함수 위치: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:130) +- 호출 API: + +```text +GET /reuseFc/pubFc +``` + +- 목적: + - WR에 등록된 하수재이용시설의 기반 시설코드 목록 `prfPbnFclts`를 확보 + - 이후 좌측 목록 조회 시 FO/Common과 WR 조회의 기준 배열로 사용 + +### 3.3 좌측 목록 실제 조회 함수 + +좌측 목록은 `prfmncHasuInit()` 또는 `prfmncHasuNmSearch()`가 내부적으로 `hasuListFetch()`를 호출해 구성한다. + +- `prfmncHasuInit()` 위치: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:144) +- `prfmncHasuNmSearch()` 위치: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:170) +- 공통 실제 조회 함수 위치: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:72) + +### 3.4 1차 호출: FO/Common 하수처리시설 기본정보 조회 + +`hasuListFetch()`는 먼저 `prfPbnFclts`를 `sggOrCity` 기준으로 필터링한다. + +- 관련 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:79) +- 필터 기준: + - `sggOrCity` 길이 2: 시도 코드 비교 + - 그 외: 앞 5자리 시군구 코드 비교 + +그 후 FO/Common API를 호출한다. + +```text +POST /prcsFclt/ruseInfo/1/{filteredFclt.length + 1} +``` + +- 호출 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:88) +- 실제 호출부: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:92) +- 요청 바디: + - `filteredFclt` + - WR 기준코드 목록 배열 + +이름 검색 시에는 URL 뒤에 검색어가 붙는다. + +```text +POST /prcsFclt/ruseInfo/1/{filteredFclt.length + 1}/{searchTxt} +``` + +- 관련 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:89) + +이 호출의 목적은 다음과 같다. + +- 하수처리시설의 기본정보 확보 + - `sido` + - `gugun` + - `bscFctNm` + - `bscFctCd` +- 검색어가 있을 경우 실제 검색 결과 시설코드 집합 재산정 + +### 3.5 2차 호출: WR 하수재이용시설 상세 및 실적 조회 + +1차 FO/Common 조회 결과를 바탕으로 WR API를 호출한다. + +```text +POST /reuseFc/pubDetailList/{year}/{sggOrCity}/{page}/{prfRegPageSize} +``` + +- 기본 페이지 크기: `prfRegPageSize = 10` +- 관련 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:19) +- 호출 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:99) +- 요청 바디: + - 검색어 없을 때: `filteredFclt` + - 검색어 있을 때: `rainRes.data.map((item) => item.bscFctCd)` + +즉 검색 시에는 FO/Common 검색 결과에서 살아남은 시설코드만 WR로 다시 전달한다. + +### 3.6 좌측 목록 최종 데이터 생성 방식 + +WR 응답 `pubRuseList.data`와 FO/Common 응답 `rainRes.data`를 다음 기준으로 매핑한다. + +- 매핑 키: + - `item.pbnstRuseFcltId.substring(0, 11)` + - `fclt.bscFctCd` + +- 관련 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:104) + +최종 좌측 목록 `prfmncRes`에는 다음 필드가 들어간다. + +- `sido` +- `gugun` +- `bscFctNm` +- `bscFctCd` +- `ruseUtztnFcltNm` +- `pbnstRuseFcltId` +- `prfList` + +여기서 중요한 점은 `prfList`가 좌측 목록 데이터에 이미 포함된다는 점이다. + +## 4. 우측 하수처리수재이용 실적(m³) 등록결과 조회 분석 + +### 4.1 결론 + +우측 `하수처리수재이용 실적(m³) 등록결과`는 별도 조회 API를 호출하지 않는다. + +우측 테이블의 데이터 원본은 좌측 목록 조회 결과 `prfmncRes[index].prfList`이다. + +### 4.2 데이터 생성 위치 + +좌측 행 클릭 시 `cellClick()`이 실행된다. + +- 함수 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:116) +- 호출 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:326) + +`cellClick()` 내부 동작: + +1. 선택한 행의 `prfmncRes[index]`를 읽음 +2. 그 안의 `prfList`를 순회 +3. 현재 선택 연도 `searchYear`와 일치하는 월별 데이터만 추출 +4. `장내용수`, `장외용수`, `소계`, `총합계` 구조로 `inputData` 재구성 +5. 우측 테이블은 `inputData`를 그대로 렌더링 + +우측 렌더링 위치: + +- [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:356) + +따라서 우측 등록결과는 조회 API 기준으로 보면 다음 호출의 간접 결과다. + +```text +POST /reuseFc/pubDetailList/{year}/{sggOrCity}/{page}/{10} +``` + +## 5. 우측 저장 API 호출 분석 + +### 5.1 저장 버튼 호출 경로 + +우측 `저장` 버튼 클릭 시 `onSave()`가 실행된다. + +- 버튼 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:282) +- 저장 함수 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:93) + +`onSave()`는 화면 편집 데이터 `inputData`를 `excelToPbnPrfData()`로 변환한 뒤 `prfmnRegData()`에 전달한다. + +- 변환 코드: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:100) + +### 5.2 실제 저장 API + +`prfmnRegData()`에서 실제 저장 호출을 수행한다. + +- 함수 위치: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:198) + +```text +POST /reuseFc/pubFcPrf +``` + +- 호출 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:207) +- 요청 바디: + - `PrfType` + - `excelToPbnPrfData(inputData, searchYear, pbnstRuseFcltId)` 변환 결과 + +### 5.3 저장 성공 후 후속 조회 + +저장 성공 시 `prfmnRegData()`는 종료하지 않고 다시 `hasuListFetch()`를 호출한다. + +- 관련 코드: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:213) + +즉 저장 직후 다음 흐름으로 재조회가 일어난다. + +1. `POST /reuseFc/pubFcPrf` 저장 +2. `hasuListFetch()` 재실행 +3. 내부에서 다시 FO/Common + WR 조회 수행 +4. `prfmncRes`, `prfmncResCount` 갱신 +5. `pageKey` 증가 +6. 우측 테이블 `TableBody` 재렌더링 + +관련 코드: + +- 상태 갱신: [index.tsx](/D:/iipbs/frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx:221) +- 우측 `TableBody` key: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:360) + +## 6. 파일등록 엑셀 저장도 동일 API 사용 + +파일등록 모달에서도 저장 시 동일한 `prfmnRegData()`를 호출한다. + +- 호출 위치: [page.tsx](/D:/iipbs/frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx:381) + +즉 수기 입력 저장과 엑셀 등록 저장은 최종적으로 같은 API를 사용한다. + +```text +POST /reuseFc/pubFcPrf +``` + +## 7. 화면 이벤트별 API 요약 + +| 화면 이벤트 | 호출 함수 | 실제 API | +| --- | --- | --- | +| 화면 진입 | `getPrfFmcRes()` | `GET /reuseFc/pubFc` | +| 화면 진입 후 좌측 목록 초기조회 | `prfmncHasuInit()` | `POST /prcsFclt/ruseInfo/...` + `POST /reuseFc/pubDetailList/...` | +| 좌측 목록 이름검색 | `prfmncHasuNmSearch()` | `POST /prcsFclt/ruseInfo/.../{searchTxt}` + `POST /reuseFc/pubDetailList/...` | +| 좌측 행 클릭 | `cellClick()` | 별도 API 없음, `prfmncRes[index].prfList` 사용 | +| 우측 저장 | `prfmnRegData()` | `POST /reuseFc/pubFcPrf` 후 `POST /prcsFclt/ruseInfo/...` + `POST /reuseFc/pubDetailList/...` 재조회 | +| 엑셀 파일등록 저장 | `prfmnRegData()` | `POST /reuseFc/pubFcPrf` 후 재조회 | + +## 8. 확인이 필요한 사항 + +- `POST /prcsFclt/ruseInfo/1/{filteredFclt.length + 1}` 에서 `+ 1`을 주는 이유는 코드상 명확한 설명이 없다. +- 저장 후 재조회 시 현재 페이지를 유지하지 않고, 호출부에서 전달한 페이지 값에 의존한다. + - 현재 수기 저장과 엑셀 저장 모두 `1` 페이지로 호출하고 있다. +- 우측 화면은 독립 조회 API가 없으므로, `pubDetailList` 응답 구조가 바뀌면 좌우 화면이 함께 영향을 받는다. + diff --git a/apps/wr/docs/작업계획/하수재이용시설 현황 api 정리_20260701.md b/apps/wr/docs/작업계획/하수재이용시설 현황 api 정리_20260701.md new file mode 100644 index 000000000..b3a32c7d3 --- /dev/null +++ b/apps/wr/docs/작업계획/하수재이용시설 현황 api 정리_20260701.md @@ -0,0 +1,371 @@ +# 하수재이용시설 현황 API 정리 작업계획 + +작성일: 2026-07-01 + +## 1. 배경 + +현재 하수재이용시설 현황/상세 조회는 백엔드와 프런트 모두에서 응답 구조가 과도하게 분산되어 있다. + +- 상세 조회는 `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 호출 후, 다시 `POST /reuseFc/pubDetailList/...`를 추가 호출해서 실적을 붙이고 있다. +- 현황 목록은 `GET /reuseFc/pbnstList/...` 호출 시 월별 실적 내역 `prfList`까지 함께 조회한 뒤, 서비스 레이어에서 장내/장외/총합계를 계산하고 있다. +- 프런트도 위 구조에 맞춰 상세 모달과 현황 화면에서 중복 후처리를 수행한다. + +이번 작업의 목표는 아래 2가지다. + +1. `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 단일 호출만으로 상세 모달에 필요한 데이터가 모두 내려오도록 정리 +2. `GET /reuseFc/pbnstList/...`는 목록에 필요한 합계만 SQL에서 계산해 내려주고, 월별 실적 내역 `prfList`는 제외 + +추가 전제: + +- 본 작업은 backend 정리 후 이에 맞춰 `frontend`도 함께 수정한다. + +## 2. 현재 구조 분석 + +### 2.1 상세 조회 API + +현재 백엔드 상세 조회 엔드포인트: + +- `GET /reuseFc/pubFc/{pbnstRuseFcltId}` + +현재 SQL/매핑: + +- `public_fc_select.xml`의 `getFc` +- `PublicFcInsertResultMap` + +현재 `getFc`는 아래 성격의 데이터만 내려준다. + +- 기본 시설 정보 +- 파일 정보 +- 사업 정보 +- 운영 정보 +- 원수 취수 정보 +- 공급관로 정보 +- 수요처 정보 + +현재 `prfList`는 포함하지 않는다. + +### 2.2 상세 실적 추가 조회 API + +현재 추가 호출 엔드포인트: + +- `POST /reuseFc/pubDetailList/{year}/{sggCd}/{pageNo}/{pageCnt}` + +현재 역할: + +- 시설 목록 기준으로 `prfList`를 포함한 실적 정보를 별도 조회 +- 프런트에서 `pubFc/{id}` 결과와 `pubDetailList` 결과를 다시 매핑 + +### 2.3 상세 모달 사용 위치 분석 + +실제 상세 모달 렌더링 위치는 아래 2곳이다. + +- `apps/wr/components/ui/map/detailPopup/ruseDetailPopup.tsx` +- `apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx` + +추가 영향 지점은 아래와 같다. + +- `apps/wr/app/store/map/ruseMap/index.tsx` + - `getRuseMaplList()` + - `getRuseMaplListReturn()` + - 현재 `pubFc/{id}` 호출 후 `pubDetailList`를 다시 호출해 `prfList`를 수동 병합 + +- `apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx` + - `getPbnStnDetail()` + - 현재는 `pubFc/{id}`만 조회하지만, 모달 내부는 상황에 따라 목록의 `prfList`를 참조 + +정리하면, 상세 모달 렌더링 지점은 2곳이 맞고, 실제 수정 범위는 스토어까지 확장된다. + +### 2.4 현황 목록 API + +현재 목록 엔드포인트: + +- `GET /reuseFc/pbnstList/{year}/{pageNo}/{pageCnt}/{searchingCd}/{nameCd}/{fcltCpct}/{oprtngSttsCd}/{bscFctClsCd}` + +현재 구조: + +- SQL `getPbnstList`에서 `tb_wr_pbnst_ruse_fclt_prfmnc`를 조인 +- `PublicPrfListMap`으로 월별 `prfList`까지 전부 매핑 +- `PublicFcService.getPbnstList()`에서 `sumUsage()`로 장내/장외/총합 계산 +- 응답 객체 `PublicPbnstList`에 `prfList`도 포함 + +문제점: + +- 목록 조회인데 월별 실적 상세까지 매번 로딩 +- 페이지 목록 응답에 비해 payload가 과다 +- 서비스 레이어에서 반복 합계 계산 수행 +- 프런트는 목록 화면에서 합계만 사용하지만 상세 내역까지 수신 + +## 3. 목표 구조 + +### 3.1 상세 조회 목표 + +`GET /reuseFc/pubFc/{pbnstRuseFcltId}` 한 번으로 아래가 모두 내려오도록 변경한다. + +- 기본 시설 정보 +- 파일/사업/운영/원수취수/공급관로/수요처 정보 +- 해당 시설의 하수재이용실적 `prfList` +- 필요 시 장내/장외/총합도 함께 포함 여부 검토 + +프런트는 상세 모달 진입 시 더 이상 `POST /reuseFc/pubDetailList/...`를 호출하지 않도록 변경한다. + +### 3.2 목록 조회 목표 + +`GET /reuseFc/pbnstList/...`는 아래만 내려오도록 정리한다. + +- 시설 기본 정보 +- 운영정보 1건 +- 장내 합계 +- 장외 합계 +- 총합계 + +제외 대상: + +- 월별 실적 상세 `prfList` + +합계는 SQL에서 계산한다. + +## 4. 백엔드 작업계획 + +### 4.1 `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 응답 확장 + +대상 파일: + +- `wr_back_out/src/main/resources/postgres/mapper/wr/public_fc_select.xml` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/object/publicFc/PublicFcInsert.java` 대신 PublicFcSelect.java 로 신규 object생성 +- `wr_back_out/src/main/java/kr/co/mindone/wrout/controller/PublicFcController.java` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/service/public_fc/PublicFcService.java` + +계획: + +1. `getFc` SQL에 `tb_wr_pbnst_ruse_fclt_prfmnc` 조인 추가 +2. `year` query param을 추가하고, 해당 연도 실적만 조회하도록 수정 +3. frontend에서 상세 조회 시 현재년도를 세팅해서 호출 + +적용안: + +- `GET /reuseFc/pubFc/{pbnstRuseFcltId}?year=2026` +- frontend는 상세 모달 진입 시 `new Date().getFullYear()` 기준 현재년도를 세팅해서 호출 +- backend는 전달받은 `year` 기준으로 `prfList`를 조회 + +이유: + +- 상세 모달은 현재 연도 기준 실적을 보는 흐름이 명확하다 +- 전체 연도 실적을 무제한 포함하면 payload가 다시 커진다 +- 실적 조회 기준 연도가 API 계약에 명시되어야 응답이 예측 가능하다 + +추가 검토: + +- 현재 `getFc`는 `like #{fcltPk}||'%'`라서 같은 하수처리시설 하위 재이용시설 여러 건을 반환하는 구조다. +- 호출명이 `/{pbnstRuseFcltId}` 이므로 실제 의도가 + - 특정 재이용시설 1건 조회인지 + - 기반 시설코드(prefix) 하위 재이용시설 다건 조회인지 + 를 명확히 해야 한다. + +현 프런트 사용상 분석: + +- `ruseDetailPopup.tsx`는 탭으로 여러 재이용시설을 보여주므로 prefix 기반 다건 조회 구조를 활용 중이다. +- `getPbnStnDetail()`는 정확한 `pbnstRuseFcltId`로 단건 선택 후 화면에서 1건 사용한다. + +따라서 이번 변경에서는 기존 다건 반환 계약은 유지하되, 반환 각 항목에 `prfList`를 포함하는 쪽이 안전하다. + +### 4.2 `pubDetailList` 영향 검토 + +현재 `pubDetailList`는 아래에서 사용된다. + +- `apps/wr/app/store/map/ruseMap/index.tsx` + - `hasuListFetch()` + - `getRuseMaplList()` + - `getRuseMaplListReturn()` +- `packages/common/business_components/src/ui/map/searchBar/searchBar.tsx` + - 경로 문자열 참조 1건 +- 과거 실적등록 관련 코드/문서 + +계획: + +- 이번 작업에서 상세 모달 경로는 `pubFc/{id}` 단일 조회로 전환 +- `pubDetailList`는 잔여 런타임 사용처가 확인되었으므로 즉시 제거하지 않는다 +- 1차로 상세 모달 경로에서 제거하고, `ruseMap` 목록/지도 경로는 영향도 확인 후 후속 정리한다 +- 작업계획 기준으로는 "상세 모달에서는 더 이상 사용하지 않음"까지를 1차 목표로 둔다 + +### 4.3 `GET /reuseFc/pbnstList/...` SQL 합계화 + +대상 파일: + +- `public_fc_select.xml` +- `PublicPbnstList.java` +- `PublicPrfListMap` 및 관련 DTO +- `PublicFcService.getPbnstList()` + +계획: + +1. `getPbnstList` SQL에서 `prfList` 월별 내역 컬럼 제거 +2. 연도 조건이 반영된 실적 집계 서브쿼리 또는 집계 조인 추가 +3. SQL에서 아래 컬럼 직접 계산 + - `in_usage` + - `out_usage` + - `usage_total` +4. 서비스 레이어 `sumUsage()` 기반 계산 제거 +5. `PublicPbnstList`에서 `prfList` 제거 + +- `pbnstList`는 현황 목록 용도 +- `pubFc/{id}`는 상세 용도 +- 응답 목적이 달라 DTO를 분리하는 편이 유지보수에 유리 + +### 4.4 서비스 레이어 정리 + +`PublicFcService.getPbnstList()`에서는 아래를 제거할 수 있다. + +- `item.setPrfList(detail.getPrfList())` +- `sumUsage(...)` 연쇄 호출 + +남는 역할: + +- SQL 결과를 DTO로 변환 +- 운영정보 1건 정리 +- 기본 문자열 보정 + +## 5. 프런트 작업계획 + +### 5.1 상세 모달 경로 정리 + +대상 파일: + +- `apps/wr/components/ui/map/detailPopup/ruseDetailPopup.tsx` +- `apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx` +- `apps/wr/app/store/map/ruseMap/index.tsx` +- `apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx` +- `apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx` + +계획: + +1. `ruseMap.getRuseMaplList()`에서 `pubDetailList` 추가 호출 제거 +2. `pubFc/{id}` 응답만으로 `ruseDetailList` 구성 +3. `PbnstSttnModal`이 `isMap` 여부에 따라 목록 `prfList`를 다시 참조하는 구조 단순화 +4. 상세 모달 공통 데이터 타입에 `prfList`가 항상 포함되도록 정리 +5. 상세 조회 호출 시 frontend에서 현재년도를 query param으로 전달 + +목표: + +- 상세 모달 렌더링 시 실적 데이터 원천을 `pubFc/{id}` 하나로 통일 + +### 5.2 현황 목록 경로 정리 + +대상 파일: + +- `apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx` +- 관련 화면: + - `apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx` + +계획: + +1. `pbnstListFetch()`가 `pbnstList` 응답의 `inUsage/outUsage/usageTotal`만 사용하도록 정리 +2. 목록 응답에서 `prfList`를 기대하는 코드 제거 +3. 상세 버튼 클릭 시 목록 데이터가 아니라 상세 API 응답을 기준으로 모달 표시 + +## 6. 데이터 구조 변경 계획 + +### 6.1 상세 응답 + +현재 후보: + +- `PublicFcInsert` 확장 +- 또는 `PublicFcDetailWithPrf` 신규 DTO 추가 + +권장: + +- 신규 상세 DTO 분리 검토 + +이유: + +- 등록/수정용 DTO와 조회용 DTO 성격이 다름 +- 상세 조회에 `prfList`, 합계 필드, 화면 전용 값이 추가될 가능성이 높음 + +### 6.2 목록 응답 + +현재 `PublicPbnstList`는 `prfList`를 포함한다. + +변경 방향: + +- 목록 DTO에서는 `prfList` 제거 +- 합계 숫자 필드만 유지 + +프런트 타입도 이에 맞춰 변경: + +- `WaterReuseFacility` +- `customPbnst` +- `FcPrfType` 사용 위치 재점검 + +## 7. 구현 순서 + +1. `pubFc/{id}` 응답에 실적 포함 설계 확정 +2. 상세 모달 사용처 2곳 및 스토어를 `pubFc/{id}` 단일 호출 구조로 전환 +3. `pbnstList` SQL을 합계형 목록 조회로 변경 +4. 목록 DTO 및 프런트 타입 정리 +5. 잔여 `pubDetailList` 사용처 영향 확인 +6. 화면 수동 검증 + +## 8. 검증 계획 + +### 8.1 상세 모달 검증 + +- 지도 팝업 상세 + - `ruseDetailPopup.tsx` +- 현황 목록 상세 모달 + - `pbnstSttnModal.tsx` + +확인 항목: + +- `pubFc/{id}` 1회 호출만 발생하는지 +- 추가 `pubDetailList` 호출이 사라졌는지 +- 실적 탭의 장내/장외/합계 표가 기존과 동일하게 보이는지 + +### 8.2 목록 검증 + +- `pbnstList` 응답에서 `prfList`가 제거되었는지 +- `inUsage`, `outUsage`, `usageTotal` 합계가 기존과 동일한지 +- 검색/페이징/시도시군구 조건에서 정상 조회되는지 + +## 9. 리스크 및 확인 필요사항 + +### 9.1 `pubFc/{id}`의 조회 단위 + +현재 `getFc` SQL은 prefix 조회다. + +- `WHERE pf.pbnst_ruse_fclt_id like #{fcltPk} || '%'` + +이 계약을 유지할지, +정확한 ID 단건 조회로 바꿀지 먼저 결정이 필요하다. + +현 프런트와의 호환성을 고려하면 1차는 prefix 다건 조회 유지가 안전하다. + +### 9.2 실적 연도 파라미터 + +상세 응답에 실적을 포함할 때 연도 조건은 필요하다. + +이번 계획에서는 아래처럼 확정한다. + +- `getFc` 조회 시 `year` query param을 받는다 +- frontend는 상세 실적 조회 시 현재년도를 세팅해서 호출한다 +- backend는 해당 연도 실적만 `prfList`에 포함한다 + +### 9.3 `pubDetailList` 잔여 사용처 + +상세 모달 외에도 아래 스토어에서 여전히 사용 중이다. + +- `apps/wr/app/store/map/ruseMap/index.tsx` + +이번 작업에서 상세 경로는 정리하되, 다른 화면의 잔여 사용 여부는 후속 정리 대상으로 남을 수 있다. + +## 10. 결론 + +이번 작업은 단순 API 이름 정리가 아니라, + +- 상세 조회는 `pubFc/{id}` 중심 +- 목록 조회는 `pbnstList` 중심 + +으로 응답 책임을 다시 나누는 작업이다. + +핵심은 아래 두 줄로 요약된다. + +1. 상세 모달은 `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 한 번만 호출해도 되게 만든다. +2. 목록 API `GET /reuseFc/pbnstList/...`는 월별 내역을 빼고 SQL 합계만 내려주게 정리한다. diff --git a/apps/wr/docs/작업계획/하수처리수재이용 실적등록_20260630.md b/apps/wr/docs/작업계획/하수처리수재이용 실적등록_20260630.md new file mode 100644 index 000000000..def2d6d0b --- /dev/null +++ b/apps/wr/docs/작업계획/하수처리수재이용 실적등록_20260630.md @@ -0,0 +1,285 @@ +# 하수처리수재이용 실적등록 작업계획 + +작성일: 2026-06-30 + +## 1. 대상 + +- 화면 + - `frontend/apps/wr/app/(with-layout)/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx` +- 프론트 스토어 + - `frontend/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx` +- 백엔드 + - `D:\iipbs\wr_back_out` +- 기준문서 + - `frontend/apps/wr/docs/기준문서/하수재이용실적 등록 api 호출분석_20260630.md` + +## 2. 요청사항 정리 + +- 좌측 목록은 `tb_wr_pbnst_ruse_fclt`만 기준으로 조회한다. +- 좌측 목록은 `reuse-service` 1회 호출로 조회한다. +- 시도/시군구 명칭은 공통코드 조회값을 사용해 화면에서 표시한다. +- 우측 `하수처리수재이용 실적(m³) 등록결과`는 좌측 시설 클릭 시 별도 API를 호출해 조회한다. +- 현재처럼 좌측 목록 조회 시 실적 `prfList`까지 함께 내려받는 구조는 제거한다. +- 좌측 신규 API명은 `reuseFc/pbnstPrfRegList`로 맞춘다. + +## 3. 현재 구조 확인 결과 + +### 3.1 프론트 + +- 화면 진입 시 현재 호출 흐름 + - `GET /reuseFc/pubFc` + - `POST /prcsFclt/ruseInfo/...` + - `POST /reuseFc/pubDetailList/{year}/{sggCd}/{pageNo}/{pageCnt}` +- 좌측 목록은 FO/Common 응답과 WR 응답을 프론트에서 합성한다. +- 우측 실적은 별도 API를 호출하지 않고 좌측 목록의 `prfList`를 그대로 사용한다. +- 저장 후에도 다시 좌측 전체 조회를 수행한다. + +### 3.2 reuse-service + +- 이미 존재하는 API + - `GET /reuseFc/pubFc` + - 현재는 `pbnst_ruse_fclt_id`의 마지막 1자리를 제거한 기초시설코드 목록만 반환한다. + - 좌측 목록 API로는 재사용 불가하다. + - `POST /reuseFc/pubDetailList/{year}/{sggCd}/{pageNo}/{pageCnt}` + - 시설 기본정보 + `prfList`를 함께 반환한다. + - 우측 클릭 조회용으로는 과조회이며 현재 구조의 결합 원인이다. + - `GET /reuseFc/pbnstList/...` + - WR 기준 목록이지만 `prfList`, 운영정보, 합계까지 같이 내려준다. + - 좌측 단순 목록 전용 API로는 과하다. +- 결론 + - 요청사항에 정확히 맞는 좌측 목록 API는 현재 없음 + - `reuseFc/pbnstPrfRegList` 신규 추가가 필요함 + +## 4. 목표 구조 + +### 4.1 좌측 목록 + +- 조회원천 + - `sm_bs_wr.tb_wr_pbnst_ruse_fclt` +- 호출 + - `reuse-service` 1회 +- 응답 범위 + - 시설명 + - 재사용시설명 + - 재사용시설코드 + - 시도코드 + - 시군구코드 + - 필요 시 페이징용 totalCount +- 화면 표시 + - 시도/시군구 명칭은 공통 코드 스토어에서 코드명으로 변환 + +### 4.2 우측 실적 + +- 조회시점 + - 좌측 행 클릭 시 +- 조회원천 + - `sm_bs_wr.tb_wr_pbnst_ruse_fclt_prfmnc` +- 응답범위 + - 선택 시설의 해당 연도 월별 실적만 반환 +- 저장 후 처리 + - 저장 성공 시 좌측 목록 전체 재조회 대신 우측 상세만 재조회 + - 좌측 목록은 변경 필요가 없으므로 불필요한 재호출 제거 + +## 5. 좌측 API 설계안 + +### 5.1 API 존재 여부 판단 + +- `GET /reuseFc/pubFc`: 부적합 + - 코드목록만 반환 +- `GET /reuseFc/pbnstList/...`: 부적합 + - `prfList`와 운영정보까지 포함 +- 따라서 신규 API 추가 + +### 5.2 API 경로 + +- `GET /reuseFc/pbnstPrfRegList/{pageNo}/{pageCnt}/{searchingCd}/{nameCd}` +- 이름검색이 필요하면 query parameter로 확장 + - 예: `?swtrmtFcltNm=...` + +### 5.3 조회조건 + +- `pageNo` +- `pageCnt` +- `searchingCd` + - 기본은 행정구역 기준 `A` +- `nameCd` + - `0`: 전체 + - `NN`: 시도코드 + - `NNNNN`: 시군구코드 +- `swtrmtFcltNm` + - 시설명 검색어 + +### 5.4 응답 컬럼 + +- `swageBscFctNm` + - 시설명 +- `ruseUtztnFcltNm` + - 재사용시설명 +- `pbnstRuseFcltId` + - 재사용시설코드 +- `sidoCd` + - `substring(swage_adst_dstr_cd, 1, 2)` +- `sggCd` + - `swage_adst_dstr_cd` +- `totalCount` + +### 5.5 DB 매핑 기준 + +- 시설명 + - `swage_bsc_fct_nm` +- 재사용시설명 + - `ruse_utztn_fclt_nm` +- 재사용시설코드 + - `pbnst_ruse_fclt_id` +- 시도코드 + - `swage_adst_dstr_cd` 앞 2자리 +- 시군구코드 + - `swage_adst_dstr_cd` + +### 5.6 SQL 방향 + +- `tb_wr_pbnst_ruse_fclt`만 조회 +- `prfList`, `operList`, 합계 컬럼 조인 제거 +- 정렬은 `pbnst_ruse_fclt_id` 기준 +- 검색은 `swage_bsc_fct_nm like '%' || #{swtrmtFcltNm} || '%'` + +## 6. 우측 API 설계안 + +### 6.1 기존 API 분석 + +- 현재 우측에 사용되는 데이터 원본은 `POST /reuseFc/pubDetailList/{year}/{sggCd}/{pageNo}/{pageCnt}` 응답의 `prfList`다. +- 즉 기존 응답에서 실제 우측 화면이 쓰는 값은 아래뿐이다. + - `pbnstRuseFcltId` + - `ym` + - `wshwtr` + - `coolUswtr` + - `cleanUswtr` + - `drwtUswtr` + - `dltnUswtr` + - `ithlRswtr` + - `ithlEtcUswtr` + - `cleanToilUswtr` + - `resn` + - `lndwtr` + - `hydUswtr` + - `rvrKeepUswtr` + - `farmUswtr` + - `ugwtChr` + - `indstUswtr` + +### 6.2 신규 상세 API 방향 + +- 우측은 클릭 시 시설 1건만 조회하므로 시설 단건 + 연도 조건 API로 분리한다. +- 권장 경로 + - `GET /reuseFc/pbnstPrfRegDtl/{year}/{pbnstRuseFcltId}` +- 최종 명칭은 구현 시 팀 네이밍 규칙에 맞춰 확정한다. + +### 6.3 요청 파라미터 + +- `year` + - 조회연도 + - 예: `2026` +- `pbnstRuseFcltId` + - 선택한 재사용시설코드 + - 예: `11000SW001R1` + +### 6.4 응답값 + +- 시설식별 정보 + - `pbnstRuseFcltId` + - `ruseUtztnFcltNm` + - `swageBscFctNm` +- 실적목록 + - `prfList: PublicFcPrfmnc[]` +- `prfList` 원소 구조 + - `ym` + - `wshwtr` + - `coolUswtr` + - `cleanUswtr` + - `drwtUswtr` + - `dltnUswtr` + - `ithlRswtr` + - `ithlEtcUswtr` + - `cleanToilUswtr` + - `resn` + - `lndwtr` + - `hydUswtr` + - `rvrKeepUswtr` + - `farmUswtr` + - `ugwtChr` + - `indstUswtr` + +### 6.5 SQL 방향 + +- `tb_wr_pbnst_ruse_fclt_prfmnc`를 `pbnst_ruse_fclt_id`, `ym like #{year} || '%'` 조건으로 조회 +- 필요 시 시설명/재사용시설명 표시를 위해 `tb_wr_pbnst_ruse_fclt` 1회 join +- 반환 단위는 1개 시설 + 최대 12개월 실적 + +## 7. 프론트 변경계획 + +### 7.1 스토어 + +- `getPrfFmcRes()` 제거 + - 더 이상 `GET /reuseFc/pubFc` 선조회 불필요 +- `hasuListFetch()` 제거 또는 전면 교체 + - FO/Common + WR 합성 로직 삭제 +- 좌측 목록 조회 함수 신설 + - `GET /reuseFc/pbnstPrfRegList/...` +- 우측 상세 조회 함수 신설 + - 좌측 클릭 시 상세 API 호출 +- 저장 후 재조회 변경 + - 기존: 좌측 전체 목록 재조회 + - 변경: 우측 상세 재조회 + +### 7.2 페이지 + +- 최초 진입 시 좌측 목록만 조회 + 첫시설의 우측 상세 호출. 첫시설 선택 표시. +- 좌측 클릭 시 우측 상세 호출 +- `cellClick()` 내부의 `prfmncRes[index].prfList` 직접 사용 제거 +- 좌측 row 데이터는 코드 중심으로 유지 + - `sidoCd`, `sggCd`를 공통코드명으로 변환해 표시 + +## 8. 백엔드 변경계획 + +### 8.1 대상 파일 + +- `src/main/java/kr/co/mindone/wrout/controller/PublicFcController.java` +- `src/main/java/kr/co/mindone/wrout/service/public_fc/PublicFcService.java` +- `src/main/java/kr/co/mindone/wrout/mapper/wr/PublicFcMapper.java` +- `src/main/resources/postgres/mapper/wr/public_fc_select.xml` +- 필요 시 신규 DTO + - 좌측 목록 DTO + - 우측 상세 DTO + +### 8.2 구현 항목 + +1. 좌측 목록 DTO 추가 + - 실적 없는 가벼운 응답 모델 +2. `pbnstPrfRegList` 컨트롤러/서비스/매퍼 추가 +3. 우측 단건 실적 조회 API 추가 +4. `pubDetailList`는 기존 화면 영향 범위 확인 중요함. 그 후에 유지 혹은 신규 추가. +5. Swagger 설명 추가 + +## 9. 검증 항목 + +1. 화면 진입 시 좌측 목록 API 1회만 호출되는지 +2. 화면 진입 시 `POST /prcsFclt/ruseInfo/...`가 더 이상 호출되지 않는지 +3. 좌측 목록 응답에 `prfList`가 포함되지 않는지 +4. 좌측 클릭 시에만 우측 상세 API가 호출되는지 +5. 저장 후 좌측 목록 재조회 없이 우측 데이터만 갱신되는지 +6. 시도/시군구 명칭이 공통 코드 기준으로 정상 표시되는지 +7. 기존 저장 API `POST /reuseFc/pubFcPrf`와 정합성 문제가 없는지 + +## 10. 작업 순서 + +1. `reuse-service`에 좌측 목록 신규 API 추가 +2. `reuse-service`에 우측 상세 신규 API 추가 +3. 프론트 스토어를 신규 API 기준으로 분리 +4. 페이지 클릭/저장 후 갱신 흐름 수정 +5. 수동 테스트로 호출 수와 데이터 정합성 확인 + +## 11. 결론 + +- 좌측 목록 전용 API `reuseFc/pbnstPrfRegList`는 현재 `reuse-service`에 없으므로 신규 추가가 필요하다. +- 우측 실적도 현재는 `pubDetailList`에 묶여 있으므로, 시설 클릭 시 조회하는 단건 상세 API로 분리하는 것이 맞다. +- 이번 작업의 핵심은 "좌측은 시설목록만, 우측은 클릭 후 실적조회"로 API 책임을 분리해 화면 구조와 호출 구조를 단순화하는 것이다. diff --git a/apps/wr/src/app/styles/components-black.css b/apps/wr/src/app/styles/components-black.css index 109df260b..e8c90d479 100644 --- a/apps/wr/src/app/styles/components-black.css +++ b/apps/wr/src/app/styles/components-black.css @@ -125,7 +125,12 @@ background: #1a1c20; } - + input[type="file"] { + background-color: #1a1a1a; + border-color: #262626; + color: #f1f1f1; + } + } input[type="file"] { diff --git a/packages/common/business_components/src/ui/chart/BarChart.tsx b/packages/common/business_components/src/ui/chart/BarChart.tsx index ec0647dff..486cc8a7c 100644 --- a/packages/common/business_components/src/ui/chart/BarChart.tsx +++ b/packages/common/business_components/src/ui/chart/BarChart.tsx @@ -53,13 +53,13 @@ const CustomTooltip = ({ }) => { if (active && payload && payload.length) { return ( -
-

{label}

+
+

{label}

{payload.map((item, index) => (
{/* 색상 박스 추가 (범례 색상과 동일) */}
- {` ${item.name} : ${fncNumberFormet(item.value)}`} + {` ${item.name} : ${fncNumberFormet(item.value)}`}
))}
@@ -76,7 +76,7 @@ const CustomLegend = (props: any) => { {payload.map((entry: unknown, index: number) => (
- {(entry as {value: string}).value} + {(entry as {value: string}).value}
))}
@@ -141,7 +141,7 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle=' radius={5} key={key} barSize={graphSize} - fill={colors[idx % colors.length]} + fill={colors[idx % colors.length] || "var(--foreground)"} /> )) )} @@ -151,4 +151,4 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
) -} \ No newline at end of file +} diff --git a/packages/common/business_components/src/ui/chart/DoubleBar.tsx b/packages/common/business_components/src/ui/chart/DoubleBar.tsx index be930f3af..598a5224f 100644 --- a/packages/common/business_components/src/ui/chart/DoubleBar.tsx +++ b/packages/common/business_components/src/ui/chart/DoubleBar.tsx @@ -44,13 +44,13 @@ const CustomTooltip = ({ }) => { if (active && payload && payload.length) { return ( -
-

{label}

+
+

{label}

{payload.map((item, index) => (
{/* 색상 박스 추가 (범례 색상과 동일) */}
- {` ${item.name} : ${item.value}`} + {` ${item.name} : ${item.value}`}
))}
@@ -67,7 +67,7 @@ const CustomLegend = (props: any) => { {payload.map((entry: unknown, index: number) => (
- {(entry as {value: string}).value} + {(entry as {value: string}).value}
))}
@@ -123,7 +123,7 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha key={key} stackId="a" barSize={graphSize} - fill={colors[idx]} + fill={colors[idx] || "var(--foreground)"} /> )) )} @@ -133,4 +133,4 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha
) -} \ No newline at end of file +} diff --git a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx index ec8fc2fd9..1692f79fd 100644 --- a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx +++ b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx @@ -69,7 +69,7 @@ const renderCustomizedLabel = ({ cx ? "start" : "end"} dominantBaseline="central" @@ -87,12 +87,12 @@ const CustomTooltip = ({ active, payload, label }: { }) => { if (active && payload && payload.length) { return ( -
-

{label}

+
+

{label}

{payload.map((item, index) => (
- {` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`} + {` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}
))}
@@ -126,7 +126,7 @@ const CustomLegend = (props: LegendProps) => { minHeight: "14px", }} >
- + {entry.payload?.label || entry.value} diff --git a/packages/common/business_components/src/ui/chart/LineBarChart.tsx b/packages/common/business_components/src/ui/chart/LineBarChart.tsx index 271629e6a..ae855dfb0 100644 --- a/packages/common/business_components/src/ui/chart/LineBarChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineBarChart.tsx @@ -49,13 +49,13 @@ type LegendProps = { const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: Array<{ name: string; value: number; color: string }>; label?: string }) => { if (active && payload && payload.length) { return ( -
-

{label}

+
+

{label}

{payload.map((item, index) => (
{/* 색상 박스 추가 (범례 색상과 동일) */}
- {` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`} + {` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}
))}
@@ -74,7 +74,7 @@ const CustomLegend = (props: LegendProps) => { {payload.map((entry: LegendEntry, index: number) => (
- {entry.value} + {entry.value}
))}
@@ -172,9 +172,10 @@ export default function LineBarchart({ x={x - padding} // 맨 좌측 바깥 y={y + height / 2 - 12} // 수직 중앙 textAnchor="middle" - transform={`rotate(-90, ${x - padding}, ${y + height / 2})`} - className="text-[12px] font-bold" - > + transform={`rotate(-90, ${x - padding}, ${y + height / 2})`} + className="text-[12px] font-bold" + fill="var(--muted-foreground)" + > {fieldText}
); @@ -184,9 +185,10 @@ export default function LineBarchart({ x={x + height / 2 } // 맨 좌측 바깥 y={y - width + padding } // 수직 중앙 textAnchor="middle" - transform={`rotate(90, ${x}, ${y})`} - className="text-[12px] font-bold" - > + transform={`rotate(90, ${x}, ${y})`} + className="text-[12px] font-bold" + fill="var(--muted-foreground)" + > {fieldText} ); @@ -249,7 +251,7 @@ export default function LineBarchart({ key={`bar-${key}-${index}`} yAxisId="right" dataKey={key} - fill={config?.color || "black"} + fill={config?.color || "var(--foreground)"} name={String(config?.label || "Y축")} maxBarSize={80} /> @@ -267,7 +269,7 @@ export default function LineBarchart({ yAxisId="left" type="linear" dataKey={key} - stroke={config?.color || "black"} + stroke={config?.color || "var(--foreground)"} strokeWidth={2} dot={{r: 0}} name={String(config?.label || "X축")} @@ -281,4 +283,4 @@ export default function LineBarchart({ ); -} \ No newline at end of file +} diff --git a/packages/common/business_components/src/ui/chart/LineBarChart2.tsx b/packages/common/business_components/src/ui/chart/LineBarChart2.tsx index ff8e6b5cb..b0ae7a0c3 100644 --- a/packages/common/business_components/src/ui/chart/LineBarChart2.tsx +++ b/packages/common/business_components/src/ui/chart/LineBarChart2.tsx @@ -46,13 +46,13 @@ type LegendProps = { const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: Array<{ name: string; value: number; color: string }>; label?: string }) => { if (active && payload && payload.length) { return ( -
-

{label}

+
+

{label}

{payload.map((item, index) => (
{/* 색상 박스 추가 (범례 색상과 동일) */}
- {` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`} + {` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}
))}
@@ -71,7 +71,7 @@ const CustomLegend = (props: LegendProps) => { {payload.map((entry: LegendEntry, index: number) => (
- {entry.value} + {entry.value}
))}
@@ -151,7 +151,7 @@ export default function LineBarchart2({ key={`bar-${key}-${index}`} yAxisId="right" dataKey={key} - fill={config?.color || "black"} + fill={config?.color || "var(--foreground)"} name={String(config?.label || "Y축")} maxBarSize={80} /> @@ -169,7 +169,7 @@ export default function LineBarchart2({ yAxisId="left" type="linear" dataKey={key} - stroke={config?.color || "black"} + stroke={config?.color || "var(--foreground)"} strokeWidth={2} dot={{r: 0}} name={String(config?.label || "X축")} @@ -183,4 +183,4 @@ export default function LineBarchart2({ ); -} \ No newline at end of file +} diff --git a/packages/common/business_components/src/ui/chart/LineChart.tsx b/packages/common/business_components/src/ui/chart/LineChart.tsx index b9ba58ff6..036dbfae4 100644 --- a/packages/common/business_components/src/ui/chart/LineChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineChart.tsx @@ -63,13 +63,13 @@ type LegendProps = { const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: Array<{ name: string; value: number; color: string }>; label?: string }) => { if (active && payload && payload.length) { return ( -
-

{label}

+
+

{label}

{payload.filter(item => item.value != null && String(item.value) != 'NaN' && item.value != undefined).map((item, index) => (
{/* 색상 박스 추가 (범례 색상과 동일) */}
- {` ${item.name} : ${item.value.toString().replace(',','')}`} + {` ${item.name} : ${item.value.toString().replace(',','')}`}
))}
@@ -122,7 +122,7 @@ const CustomLegend = (props: LegendProps) => { minHeight: "14px", }} >
- {entry.value} + {entry.value} ))} @@ -141,6 +141,7 @@ const CustomAxisText : React.FC<{ viewBox: any, fieldText: string, position:stri textAnchor="middle" transform={`rotate(-90, ${x - padding}, ${y + height / 2})`} className="text-[12px] font-bold" + fill="var(--muted-foreground)" > {fieldText} @@ -153,6 +154,7 @@ const CustomAxisText : React.FC<{ viewBox: any, fieldText: string, position:stri textAnchor="middle" transform={`rotate(90, ${x}, ${y})`} className="text-[12px] font-bold" + fill="var(--muted-foreground)" > {fieldText} @@ -277,4 +279,4 @@ export default function Linechart({ ); -} \ No newline at end of file +} diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index ed05a7f19..351f08dff 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -151,7 +151,7 @@ export function UiTable>({ ) : ( <> )} -
+
{widthFixed ?