Merge branch 'iipbs/wr/dev'
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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 };
|
||||
export { fetchHasuDetail, fetchpbnstDetail, detainSttn };
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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<FcPrfType[]>();
|
||||
const [searchTxt, setSearchTxt] = useState<string>();
|
||||
const [searchTxt, setSearchTxt] = useState<string>("");
|
||||
const [selectedFacilityId, setSelectedFacilityId] = useState<string>("");
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
const [inputData, setInputData] = useState<any[]>([]);
|
||||
const [isExcelOpen, setIsExcelOPen] = useState<{
|
||||
isModal: boolean;
|
||||
data: FcPrfType;
|
||||
}>({ isModal: false, data: {} as FcPrfType });
|
||||
const [inputData, setInputData] = useState<any[]>();
|
||||
const [pbnstRuseFcltId, setpbnstRuseFcltId] = useState<string>();
|
||||
const [cellNum, setCellNum] = useState<number>(1);
|
||||
const [searchYear, setSearchYear] = useState<string>(
|
||||
String(new Date().getFullYear())
|
||||
);
|
||||
data: PbnstPrfRegDtlType;
|
||||
}>({ isModal: false, data: {} as PbnstPrfRegDtlType });
|
||||
const [searchYear, setSearchYear] = useState<string>(String(new Date().getFullYear()));
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const { t } = useTranslation("base");
|
||||
|
||||
const useInitRef = useRef(false);
|
||||
const bdstTable = useMemo<PbnstPrfmncRow[]>(
|
||||
() =>
|
||||
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))}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-3 flex flex-row space-x-2">
|
||||
@@ -261,7 +294,7 @@ export default function PrfmncPrfmncReg() {
|
||||
<SearchInput
|
||||
className={"w-full text-2xl"}
|
||||
label=""
|
||||
textValue={searchTxt || ""}
|
||||
textValue={searchTxt}
|
||||
setText={(e) => {
|
||||
setSearchTxt(e);
|
||||
}}
|
||||
@@ -271,12 +304,7 @@ export default function PrfmncPrfmncReg() {
|
||||
<SearchFormRight className="w-3/12 mx-5 flex flex-col items-center justify-center space-x-2 ">
|
||||
<div className="space-y">
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button
|
||||
className={btnClass}
|
||||
onClick={() => {
|
||||
setIsReset(true);
|
||||
}}
|
||||
>
|
||||
<Button className={btnClass} onClick={handleSearch}>
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
@@ -291,12 +319,11 @@ export default function PrfmncPrfmncReg() {
|
||||
<Button
|
||||
className={btnClass}
|
||||
onClick={() => {
|
||||
if (cellNum === undefined) {
|
||||
if (!selectedFacilityId) {
|
||||
alert("하수처리재이용시설을 선택해주세요.");
|
||||
return;
|
||||
} else {
|
||||
setIsExcelOPen({ ...isExcelOpen, isModal: true });
|
||||
}
|
||||
setIsExcelOPen((prev) => ({ ...prev, isModal: true }));
|
||||
}}
|
||||
disabled={regAuthrtYn === "N"}
|
||||
>
|
||||
@@ -318,30 +345,29 @@ export default function PrfmncPrfmncReg() {
|
||||
<ScrollArea className="h-[60vh]">
|
||||
<UiTable
|
||||
key={"leftTable"}
|
||||
tableData={bdstTable || []}
|
||||
tableData={bdstTable}
|
||||
headlist={pbnstPrfmncHeadList}
|
||||
pageSize={10}
|
||||
total={prfmncResCount}
|
||||
headName={"하수재이용시설 목록"}
|
||||
cellClick={(index: number | undefined) => {
|
||||
cellClick(index);
|
||||
setCellNum(index ?? 0);
|
||||
if (index === undefined) return;
|
||||
const selected = prfmncRes[index];
|
||||
if (!selected) return;
|
||||
|
||||
setSelectedIndex(index);
|
||||
void loadDetail(selected.pbnstRuseFcltId);
|
||||
}}
|
||||
onPageChange={(page: number) => {
|
||||
if (searchTxt && searchTxt?.length > 0) {
|
||||
prfmncHasuNmSearch(
|
||||
page,
|
||||
laeSeCd,
|
||||
searchYear,
|
||||
searchTxt,
|
||||
prfPbnFclts
|
||||
);
|
||||
if (searchTxt.trim().length > 0) {
|
||||
void prfmncHasuNmSearch(page, laeSeCd, searchTxt);
|
||||
} else {
|
||||
prfmncHasuInit(page, laeSeCd, searchYear, prfPbnFclts);
|
||||
void prfmncHasuInit(page, laeSeCd);
|
||||
}
|
||||
setIsReset(false);
|
||||
}}
|
||||
resetPageNo={isReset}
|
||||
index={selectedIndex}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</ListDiv>
|
||||
@@ -353,19 +379,21 @@ export default function PrfmncPrfmncReg() {
|
||||
{"하수처리수재이용 실적(m³) 등록결과"}
|
||||
</h3>
|
||||
</div>
|
||||
<Table variant="secondary">
|
||||
<TableHeader className="font-bold">
|
||||
{headMakeprfmnc(monthHeadList, [])}
|
||||
</TableHeader>
|
||||
<TableBody key={prfmnc.pageKey + "pageKey"}>
|
||||
{pbnstTableBody(
|
||||
inputData ?? [],
|
||||
monthHeadList,
|
||||
setInputData,
|
||||
mdfcnAuthrtYn === "N" ? false : true
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="card !m-[1.2rem]">
|
||||
<Table variant="secondary">
|
||||
<TableHeader className="font-bold">
|
||||
{headMakeprfmnc(monthHeadList, [])}
|
||||
</TableHeader>
|
||||
<TableBody key={prfmnc.pageKey + "pageKey"}>
|
||||
{pbnstTableBody(
|
||||
inputData,
|
||||
monthHeadList,
|
||||
setInputData,
|
||||
mdfcnAuthrtYn === "N" ? false : true
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</ListDiv>
|
||||
</div>
|
||||
@@ -376,22 +404,20 @@ export default function PrfmncPrfmncReg() {
|
||||
<PbnPrfExcelModal
|
||||
data={isExcelOpen.data}
|
||||
setIsExcelOPen={(open) => {
|
||||
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가 감지하여 처리하므로 여기서는 호출하지 않습니다.
|
||||
}}
|
||||
></PbnPrfExcelModal>
|
||||
}
|
||||
open={isExcelOpen.isModal}
|
||||
onOpenChange={() => {
|
||||
setIsExcelOPen({ ...isExcelOpen, isModal: false });
|
||||
setIsExcelOPen((prev) => ({ ...prev, isModal: false }));
|
||||
}}
|
||||
></UiModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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<string>;
|
||||
getPrfFmcRes: () => void;
|
||||
prfmncHasuInit: (page: number, sggOrCity: string) => void;
|
||||
prfmncHasuNmSearch: (page: number, sggOrCity: string, searchTxt: string) => void;
|
||||
prfmnRegData: (data: PrfType[], year: string, pbnstRuseFcltId: string) => Promise<string>;
|
||||
getPbnstPrfRegDtl: (year: string, pbnstRuseFcltId: string) => Promise<PbnstPrfRegDtlType | undefined>;
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
|
||||
@@ -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<typeof FcPrfSchema>;
|
||||
export type PrfType = z.infer<typeof PrfSchema>;
|
||||
export type PbnstPrfRegListType = z.infer<typeof PbnstPrfRegListSchema>;
|
||||
export type PbnstPrfRegDtlType = z.infer<typeof PbnstPrfRegDtlSchema>;
|
||||
|
||||
export const initValInUsage:PrfUsageSummary[] = [
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
@@ -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<File | null>(null);
|
||||
const [excelData, setExcelData] = useState<PrfType[]>();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const form = useForm<z.infer<typeof pbnPrfRegExcelZod>>({
|
||||
resolver: zodResolver(pbnPrfRegExcelZod),
|
||||
defaultValues: {
|
||||
@@ -44,26 +45,32 @@ export default function PbnPrfExcelModal({
|
||||
});
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setExcelData(undefined);
|
||||
const selectedFile = e.target.files?.[0];
|
||||
if (selectedFile) {
|
||||
setFile(selectedFile);
|
||||
}
|
||||
};
|
||||
const onSubmit = (values: z.infer<typeof pbnPrfRegExcelZod>) => {
|
||||
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({
|
||||
|
||||
<div className="space-y-4 p-4">
|
||||
<Input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
onChange={(file) => {
|
||||
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"
|
||||
/>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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` 응답 구조가 바뀌면 좌우 화면이 함께 영향을 받는다.
|
||||
|
||||
@@ -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 합계만 내려주게 정리한다.
|
||||
@@ -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 책임을 분리해 화면 구조와 호출 구조를 단순화하는 것이다.
|
||||
@@ -125,7 +125,12 @@
|
||||
background: #1a1c20;
|
||||
}
|
||||
|
||||
|
||||
input[type="file"] {
|
||||
background-color: #1a1a1a;
|
||||
border-color: #262626;
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
|
||||
@@ -53,13 +53,13 @@ const CustomTooltip = ({
|
||||
}) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
<div className="w-[180px] rounded border border-border bg-accent p-2.5 text-accent-foreground shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="m-3 text-[12px] font-bold text-accent-foreground">{label}</p>
|
||||
{payload.map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{/* 색상 박스 추가 (범례 색상과 동일) */}
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${fncNumberFormet(item.value)}`}</span>
|
||||
<span className="text-[11px] font-bold text-accent-foreground">{` ${item.name} : ${fncNumberFormet(item.value)}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@ const CustomLegend = (props: any) => {
|
||||
{payload.map((entry: unknown, index: number) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-2">
|
||||
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: (entry as {color: string}).color }}></div>
|
||||
<span className="text-[12px]">{(entry as {value: string}).value}</span>
|
||||
<span className="text-[12px] text-card-foreground">{(entry as {value: string}).value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -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='
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ const CustomTooltip = ({
|
||||
}) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
<div className="w-[180px] rounded border border-border bg-accent p-2.5 text-accent-foreground shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="m-3 text-[12px] font-bold text-accent-foreground">{label}</p>
|
||||
{payload.map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{/* 색상 박스 추가 (범례 색상과 동일) */}
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value}`}</span>
|
||||
<span className="text-[11px] font-bold text-accent-foreground">{` ${item.name} : ${item.value}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -67,7 +67,7 @@ const CustomLegend = (props: any) => {
|
||||
{payload.map((entry: unknown, index: number) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-2">
|
||||
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: (entry as {color: string}).color }}></div>
|
||||
<span className="text-[12px]">{(entry as {value: string}).value}</span>
|
||||
<span className="text-[12px] text-card-foreground">{(entry as {value: string}).value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -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
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ const renderCustomizedLabel = ({
|
||||
<text
|
||||
x={x}
|
||||
y={y}
|
||||
fill="#333"
|
||||
fill="var(--muted-foreground)"
|
||||
fontSize={11}
|
||||
textAnchor={x > cx ? "start" : "end"}
|
||||
dominantBaseline="central"
|
||||
@@ -87,12 +87,12 @@ const CustomTooltip = ({ active, payload, label }: {
|
||||
}) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
<div className="w-[180px] rounded border border-border bg-accent p-2.5 text-accent-foreground shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="m-3 text-[12px] font-bold text-accent-foreground">{label}</p>
|
||||
{payload.map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.payload.fill, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</span>
|
||||
<span className="text-[11px] font-bold text-accent-foreground">{` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -126,7 +126,7 @@ const CustomLegend = (props: LegendProps) => {
|
||||
minHeight: "14px",
|
||||
}}
|
||||
></div>
|
||||
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>
|
||||
<span className="text-card-foreground" style={{ fontSize: "13px", whiteSpace: "nowrap" }}>
|
||||
{entry.payload?.label || entry.value}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
<div className="w-[180px] rounded border border-border bg-accent p-2.5 text-accent-foreground shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="m-3 text-[12px] font-bold text-accent-foreground">{label}</p>
|
||||
{payload.map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{/* 색상 박스 추가 (범례 색상과 동일) */}
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</span>
|
||||
<span className="text-[11px] font-bold text-accent-foreground">{` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -74,7 +74,7 @@ const CustomLegend = (props: LegendProps) => {
|
||||
{payload.map((entry: LegendEntry, index: number) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-2">
|
||||
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: entry.color }}></div>
|
||||
<span className="text-[12px]">{entry.value}</span>
|
||||
<span className="text-[12px] text-card-foreground">{entry.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -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}
|
||||
</text>
|
||||
);
|
||||
@@ -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}
|
||||
</text>
|
||||
);
|
||||
@@ -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({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
<div className="w-[180px] rounded border border-border bg-accent p-2.5 text-accent-foreground shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="m-3 text-[12px] font-bold text-accent-foreground">{label}</p>
|
||||
{payload.map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{/* 색상 박스 추가 (범례 색상과 동일) */}
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</span>
|
||||
<span className="text-[11px] font-bold text-accent-foreground">{` ${item.name} : ${item.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -71,7 +71,7 @@ const CustomLegend = (props: LegendProps) => {
|
||||
{payload.map((entry: LegendEntry, index: number) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-2">
|
||||
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: entry.color }}></div>
|
||||
<span className="text-[12px]">{entry.value}</span>
|
||||
<span className="text-[12px] text-card-foreground">{entry.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -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({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
<div className="w-[180px] rounded border border-border bg-accent p-2.5 text-accent-foreground shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="m-3 text-[12px] font-bold text-accent-foreground">{label}</p>
|
||||
{payload.filter(item => item.value != null && String(item.value) != 'NaN' && item.value != undefined).map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{/* 색상 박스 추가 (범례 색상과 동일) */}
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value.toString().replace(',','')}`}</span>
|
||||
<span className="text-[11px] font-bold text-accent-foreground">{` ${item.name} : ${item.value.toString().replace(',','')}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -122,7 +122,7 @@ const CustomLegend = (props: LegendProps) => {
|
||||
minHeight: "14px",
|
||||
}}
|
||||
></div>
|
||||
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{entry.value}</span>
|
||||
<span className="text-card-foreground" style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{entry.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -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}
|
||||
</text>
|
||||
@@ -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}
|
||||
</text>
|
||||
@@ -277,4 +279,4 @@ export default function Linechart({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ export function UiTable<T extends object = Record<string, unknown>>({
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div className={`card !p-0r !m-[1.2rem] ${className}`}>
|
||||
<div className={`card !m-[1.2rem] ${className}`}>
|
||||
<Table variant="secondary" className={widthFixed ? "table-fixed" : ""}>
|
||||
{widthFixed ?
|
||||
<colgroup>
|
||||
|
||||
Reference in New Issue
Block a user