Merge commit '7733426636f08d68a85a8739f2ef6e3206078d4e' into iipbs/wr/hasuReg

This commit is contained in:
leedj312
2025-07-03 20:22:35 +09:00
23 changed files with 623 additions and 330 deletions
+12 -4
View File
@@ -5,6 +5,7 @@ import { ruseUtzType } from "@/app/store/matDmd/ruseCaseMng/headList";
import { excelDownload, getCity } from "@/components/lib/func/commom";
import RuseCaseMngExcel from "@/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal";
import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal";
import { ToastFormUi } from "@/components/ui/toast/toastFromUi";
import {
ContentsDiv,
filePickedBlobArray,
@@ -20,11 +21,12 @@ import {
} from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { Button } from "@common/components/ui";
import { useState } from "react";
import { useEffect, useState } from "react";
import { toast, Toaster } from "react-hot-toast";
export default function RuseCaseMng() {
const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state);
const { ruseCaseMngList } = ruseCaseMng;
const { ruseCaseMngList, status } = ruseCaseMng;
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>([]);
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
@@ -142,7 +144,7 @@ export default function RuseCaseMng() {
}
];
// 버퍼에 있는 엑셀데이터 전달 후 json으로 변환
await filePickedBlobArray(blobRet, '시설정보및운영현황황', sheetParameter);
await filePickedBlobArray(blobRet, '물재이용사례현황', sheetParameter);
});
}
@@ -157,6 +159,12 @@ export default function RuseCaseMng() {
);
}
useEffect(() => {
if(status === 200){
setIsExcelOpen({ isModal: false });
}
}, [status]);
return (
<>
<ContentsDiv>
@@ -239,7 +247,7 @@ export default function RuseCaseMng() {
</div>
</div>
</ContentsDiv>
<Toaster position="top-center" />
{/* 엑셀 등록 모달 */}
<UiModal
open={isExcelOpen.isModal}
@@ -28,8 +28,10 @@ import {
type SelectDataType
} from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import LoadingOverlay from "@common/business_components/ui/loadingBar";
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useRef, useState } from "react";
import { Toaster } from "react-hot-toast";
export default function LdrmntCoSttn() {
const { code, ldrmnt, ldrmntInit, ldrmntSearch, } = useWrStore(
@@ -273,7 +275,7 @@ export default function LdrmntCoSttn() {
<div className="overflow-x-auto">
<ListDiv className="min-w-max">
<UiTable
headName={[]}
headName={undefined}
tableData={bdstTable || []}
headlist={ldrmntHeadListExport}
pageSize={20}
@@ -299,8 +301,9 @@ export default function LdrmntCoSttn() {
setIsExcelOpen({ isModal: !isExcelOpen.isModal })
}}
bodyClass={"w-[40vw] max-w-none"}
body={<CoRegExcel />}
body={<CoRegExcel isModal={(isOpen) => setIsExcelOpen({ isModal: isOpen })} />}
/>
<Toaster position="top-center"></Toaster>
</div>
@@ -8,6 +8,7 @@ import type {
} from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import {
excelDownload,
getCity,
getCityOrSgg,
tableData,
} from "@/components/lib/func/commom";
@@ -16,16 +17,22 @@ import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDeta
import {
ContentsDiv,
SearchBaseForm,
SearchDiv,
SearchFormLeft,
SearchFormRight,
SelectBox,
Title,
UiModal,
UiTable,
type ReturnType,
type SelectDataType,
} from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useRef, useState } from "react";
import { excelHtdflSttnParsing } from "@/app/store/ruseFcltMng/fcltSttn/htdflSttn/excel";
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal";
export default function HtdflSttn() {
const { code, htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore(
@@ -38,6 +45,8 @@ export default function HtdflSttn() {
const [pageNation, setPageNation] = useState(1);
const [isChHead, setIsChHead] = useState(false);
const [bdstTable, setBdstTable] = useState<customRainBdst[]>();
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>()
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const [detailModal, setDetailModal] = useState<{
isOpen: boolean;
data: customRainBdst | undefined;
@@ -50,13 +59,44 @@ export default function HtdflSttn() {
const codeData = getCityOrSgg(ref);
const excelClick = useCallback(() => {
if (codeData) htdflSttnSearch(1, "10000", codeData);
}, [htdflSttnSearch, codeData]);
// 최신 검색 조건 반영
const con = getConValue();
htdflSttnSearch(1, "10000", String(con), code.cityCd, code.sggCd);
}, [htdflSttnSearch, objSearch, code.cityCd, code.sggCd]);
useEffect(() => {
if (codeData != null) htdflSttnSearch(pageNation, "20", codeData);
if (codeData != null) htdflSttnSearch(pageNation, "20", codeData, code.cityCd, code.sggCd);
}, [pageNation]);
const getSelCity = (val: string | number) => {
const cityValue = String(val);
if (cityValue === "0") {
setSelectSgg([]);
setObjSearch({ city: cityValue, sgg: "" });
} else {
const data = getCity(cityValue, code.sggCd);
setSelectSgg(data);
const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? "");
setObjSearch({ city: cityValue, sgg: firstSggVal });
}
};
const getSgg = (val: string | number) => {
setObjSearch({ ...objSearch, sgg: String(val) })
};
const getConValue = () => {
if (!objSearch.city || objSearch.city === "0") {
return "0";
}
if (objSearch.sgg && objSearch.sgg !== "0") {
return objSearch.sgg;
}
return objSearch.city;
};
const cellClick = (index: number | undefined, headId: string) => {
if (
headId !== "chBuildingYn" &&
@@ -67,20 +107,15 @@ export default function HtdflSttn() {
}
};
useEffect(() => {
const excelDown = async () => {
if (htdflSttn.excelList.length > 0) {
if (htdflSttn.excelList.length === 0) return;
const excelData = await tableData(htdflSttn.excelList, { code });
const htdflExcel = excelHtdflSttnParsing(excelData, code.cityCd, code.sggCd);
excelDownload(
htdflExcel as [[]],
"온배수시설 현황",
"sample_htdflSttn.xlsx",
"A1:S3"
);
}
};
excelDown();
if (htdflSttn.excelList.length > 0) {
if (htdflSttn.excelList.length === 0) return;
excelDownload(
htdflSttn.excelList,
"온배수시설 현황",
"sample_htdflSttn.xlsx",
"A1:S3"
);
}
}, [htdflSttn.excelList, code]);
useEffect(() => {
@@ -90,19 +125,16 @@ export default function HtdflSttn() {
}, [htdflSttn.isInit, initHtdfl]);
useEffect(() => {
if (htdflSttnRes.length > 0 && code.cityCd.length > 0) {
const data = tableData(htdflSttnRes, { code });
const chData = data
.map((item: any) => ({
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0].htdflObj,
chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N",
}))
.filter((item: any) => item.htdflObj !== undefined);
setBdstTable(chData);
}
const data = tableData(htdflSttnRes, { code });
const chData = data
.map((item: any) => ({
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0].htdflObj,
chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N",
}))
.filter((item: any) => item.htdflObj !== undefined);
setBdstTable(chData);
}, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]);
return (
@@ -111,43 +143,65 @@ export default function HtdflSttn() {
<div className="grid grid-cols-4 gap-4 ">
<div className="col-span-4">
<Title title={"온배수 시설 현황"} />
<SearchBaseForm
selectSidoData={code.cityCd}
selectSigunData={code.sggCd}
selectRegionData={code.rvbsnCd}
selectUnitWaterShedData={code.lgbsnCd}
selectMiddleRegionData={code.mdmbsnCd}
selectEnvPartData={code.lclgvrnmntCd}
rightChildren={<Button
className={btnClass}
onClick={() => {
setIsChHead(true);
} }
>
조회항목설정
</Button>}
leftChildren={undefined}
queryClick={function (returnType: ReturnType): void {
// '{partValue: 'A', sidoValue: '0', sigunValue: '0', regionValue: '0', unitWaterShedValue: '0', …}'
let con;
if (returnType.sigunValue != "0") {
con = returnType.sigunValue;
} else {
con = returnType.sidoValue;
}
htdflSttnSearch(pageNation, "20", con);
} }
excelClick={excelClick}
initClick={function (): void { } }
btnClass={btnClass}
ref={ref} isAdmin={false} />
<SearchDiv>
<SearchFormLeft>
<div className="col-span-10 flex flex-row">
<SelectBox
className="col-span-2 w-1/6 h-full text-2xl"
label="시도"
selectClass={""}
selectArray={code.cityCd}
onSelectValue={getSelCity}
/>
<SelectBox
className="col-span-2 w-1/6 h-full text-2xl ml-4"
label="시군구"
selectClass={""}
selectArray={selectSgg || []}
value={objSearch.sgg} // value props 추가
onSelectValue={getSgg}
/>
</div>
</SearchFormLeft>
<SearchFormRight
className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 ">
<div className="flex flex-row space-x-2 items-center">
<Button
size={"sm"}
onClick={() => {
const con = getConValue();
htdflSttnSearch(1, "20", String(con), code.cityCd, code.sggCd);
}}
>
조회
</Button>
<Button
size={"sm"}
className={`${btnClass} green`}
onClick={excelClick}
>
다운로드
</Button>
<Button
size={"sm"}
className={btnClass}
onClick={() => {
setIsChHead(true);
}}
>
조회항목설정
</Button>
</div>
</SearchFormRight>
</SearchDiv>
</div>
</div>
<div className="grid grid-cols-6 gap-4 ">
<div className="col-span-6">
<ListDiv>
<UiTable
headName={[]}
headName={undefined}
tableData={bdstTable || []}
children={
<HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} />
@@ -82,7 +82,6 @@ export default function RainSttn() {
};
const getSgg = (val: string | number) => {
console.log("val", val)
setObjSearch({ ...objSearch, sgg: String(val) })
};
@@ -96,8 +95,6 @@ export default function RainSttn() {
return objSearch.city;
};
useEffect(() => {
if (!rainSttn.isInit) {
rainSttnInit(codeData ?? "0");
@@ -108,8 +105,6 @@ export default function RainSttn() {
const data = tableData(rainSttnRes, { code });
const chData = data
.map((item: any) => {
console.log("item", item);
console.log("왜안돼", item.reuseBuilding[0].rainyObj.dtfcltCpct)
return {
...item,
...item.reuseBuilding[0],
@@ -124,10 +119,16 @@ export default function RainSttn() {
? "확인불가"
: item.reuseBuilding[0].instlCstSprtAmt,
chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N",
pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.pntfColArea,
filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct,
archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea,
};
})
.filter((item: any) => item.rainyObj !== undefined);
console.log("chData", chData)
setBdstTable(chData);
}, [code, rainSttnRes, rainSttn.rainSttHeadList]);
@@ -147,9 +148,6 @@ export default function RainSttn() {
}
}, [rainSttn.excelList]);
// const excelClick = useCallback(() => {
// if (codeData) rainSttnSearch(1, "10000", codeData, code.cityCd, code.sggCd);
// }, [rainSttnSearch, codeData, code.cityCd, code.sggCd]);
const excelClick = useCallback(() => {
// 최신 검색 조건 반영
const con = getConValue();
@@ -162,8 +160,6 @@ export default function RainSttn() {
<div className="grid grid-cols-4 gap-4 ">
<div className="col-span-4">
<Title title={"빗물재이용 시설 현황"} />
<SearchDiv>
<SearchFormLeft>
<div className="col-span-10 flex flex-row">
@@ -215,50 +211,6 @@ export default function RainSttn() {
</div>
</SearchFormRight>
</SearchDiv>
{/* <SearchBaseForm
selectSidoData={code.cityCd}
selectSigunData={code.sggCd}
selectRegionData={code.rvbsnCd}
selectUnitWaterShedData={code.lgbsnCd}
selectMiddleRegionData={code.mdmbsnCd}
selectEnvPartData={code.lclgvrnmntCd}
rightChildren={
<Button
className={btnClass}
onClick={() => {
setIsChHead(true);
}}
>
조회항목설정
</Button>
}
leftChildren={undefined}
queryClick={function (returnType: ReturnType): void {
// '{partValue: 'A', sidoValue: '0', sigunValue: '0', regionValue: '0', unitWaterShedValue: '0', …}'
let con;
if (returnType.sigunValue != "0") {
con = returnType.sigunValue;
} else {
con = returnType.sidoValue;
}
rainSttnSearch(1, "20", con, code.cityCd, code.sggCd);
}}
excelClick={excelClick}
initClick={function (): void { }}
btnClass={btnClass}
ref={ref}
isAdmin={false}
/> */}
</div>
</div>
<div className="grid grid-cols-6 gap-4 ">
@@ -5,19 +5,26 @@ import type { chgBuilding } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
import {
excelDownload,
getCity,
getCityOrSgg,
tableData,
} from "@/components/lib/func/commom";
import BdstChgModal from "@/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal";
import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal";
import HeadChModal from "@/components/ui/modal/sttn/headChModal/headChModal";
import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal";
import {
ContentsDiv,
SearchBaseForm,
SearchDiv,
SearchFormLeft,
SearchFormRight,
SelectBox,
Title,
UiModal,
UiTable,
type ReturnType,
type SelectDataType,
} from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
@@ -34,6 +41,8 @@ export default function RswtSttn() {
const [isChHead, setIsChHead] = useState(false);
const ref = useRef<ReturnType>({} as ReturnType);
const [bdstTable, setBdstTable] = useState<customRswtBdst[]>();
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>()
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false })
const [bdstChgProps, setBdstChgProps] = useState<{
isOpen: boolean;
@@ -58,6 +67,35 @@ export default function RswtSttn() {
}
}
const getSelCity = (val: string | number) => {
const cityValue = String(val);
if (cityValue === "0") {
setSelectSgg([]);
setObjSearch({ city: cityValue, sgg: "" });
} else {
const data = getCity(cityValue, code.sggCd);
setSelectSgg(data);
const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? "");
setObjSearch({ city: cityValue, sgg: firstSggVal });
}
};
const getSgg = (val: string | number) => {
setObjSearch({ ...objSearch, sgg: String(val) })
};
const getConValue = () => {
if (!objSearch.city || objSearch.city === "0") {
return "0";
}
if (objSearch.sgg && objSearch.sgg !== "0") {
return objSearch.sgg;
}
return objSearch.city;
};
useEffect(() => {
if (!rswtSttn.isInit) {
rswtSttnInit("0");
@@ -65,22 +103,25 @@ export default function RswtSttn() {
}, [rswtSttn.isInit, rswtSttnInit]);
useEffect(() => {
if (rswtSttnRes?.length > 0 && code.cityCd.length > 0) {
const data = tableData(rswtSttnRes, { code });
const chData = data
.map((item: customRswtBdst) => ({
const data = tableData(rswtSttnRes, { code });
const chData = data
.map((item: customRswtBdst) => {
console.log("item", item)
return {
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0]?.rswtrObj,
chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N'
}))
.filter(
(item: customRswtBdst) =>
item.reuseBuilding[0]?.rswtrObj !== undefined
);
chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N',
instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst,
instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt,
}
})
.filter(
(item: customRswtBdst) =>
item.reuseBuilding[0]?.rswtrObj !== undefined
);
setBdstTable(chData);
}
setBdstTable(chData);
}, [code, rswtSttnRes]);
useEffect(() => {
@@ -95,13 +136,11 @@ export default function RswtSttn() {
}
}, [rswtSttn.excelList]);
const excelClick = useCallback(
(returnType: ReturnType) => {
if (codeCityOrSgg)
rswtSttnSearch(1, "10000", codeCityOrSgg, code.cityCd, code.sggCd);
},
[rswtSttnSearch, code.cityCd, code.sggCd, codeCityOrSgg]
);
const excelClick = useCallback(() => {
// 최신 검색 조건 반영
const con = getConValue();
rswtSttnSearch(1, "10000", String(con), code.cityCd, code.sggCd);
}, [rswtSttnSearch, objSearch, code.cityCd, code.sggCd]);
useEffect(() => {
if (codeCityOrSgg != null) rswtSttnSearch(pageNation, "20", codeCityOrSgg, code.cityCd, code.sggCd);
@@ -113,38 +152,64 @@ export default function RswtSttn() {
<div className="grid grid-cols-4 gap-4 ">
<div className="col-span-4">
<Title title={"중수도시설 현황"} />
<SearchBaseForm
selectSidoData={code.cityCd}
selectSigunData={code.sggCd}
selectRegionData={code.rvbsnCd}
selectUnitWaterShedData={code.lgbsnCd}
selectMiddleRegionData={code.mdmbsnCd}
selectEnvPartData={code.lclgvrnmntCd}
rightChildren={<Button
className={btnClass}
onClick={() => {
setIsChHead(true);
} }
>
조회항목설정
</Button>}
leftChildren={undefined}
queryClick={function (returnType: ReturnType): void {
// '{partValue: 'A', sidoValue: '0', sigunValue: '0', regionValue: '0', unitWaterShedValue: '0', …}'
if (codeCityOrSgg)
rswtSttnSearch(1, "20", codeCityOrSgg, code.cityCd, code.sggCd);
} }
excelClick={excelClick}
initClick={function (): void { } }
btnClass={btnClass}
ref={ref} isAdmin={false} />
<SearchDiv>
<SearchFormLeft>
<div className="col-span-10 flex flex-row">
<SelectBox
className="col-span-2 w-1/6 h-full text-2xl"
label="시도"
selectClass={""}
selectArray={code.cityCd}
onSelectValue={getSelCity}
/>
<SelectBox
className="col-span-2 w-1/6 h-full text-2xl ml-4"
label="시군구"
selectClass={""}
selectArray={selectSgg || []}
value={objSearch.sgg} // value props 추가
onSelectValue={getSgg}
/>
</div>
</SearchFormLeft>
<SearchFormRight
className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 ">
<div className="flex flex-row space-x-2 items-center">
<Button
size={"sm"}
onClick={() => {
const con = getConValue();
rswtSttnSearch(1, "20", String(con), code.cityCd, code.sggCd);
}}
>
조회
</Button>
<Button
size={"sm"}
className={`${btnClass} green`}
onClick={excelClick}
>
다운로드
</Button>
<Button
size={"sm"}
className={btnClass}
onClick={() => {
setIsChHead(true);
}}
>
조회항목설정
</Button>
</div>
</SearchFormRight>
</SearchDiv>
</div>
</div>
<div className="grid grid-cols-6 gap-4 ">
<div className="col-span-6">
<ListDiv>
<UiTable
headName={[]}
headName={undefined}
tableData={bdstTable || []}
children={
<HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} />
@@ -68,8 +68,6 @@ export default function PbnstPrfmncSttn() {
chartData5: lineChartDataProps[];
chartData6: lineChartDataProps[];
chartData7: lineChartDataProps[];
chartData8: lineChartDataProps[];
chartData9: lineChartDataProps[];
}>();
const [outLineData, setOutLineData] = useState<{
chartData1: lineChartDataProps[];
@@ -78,6 +76,8 @@ export default function PbnstPrfmncSttn() {
chartData4: lineChartDataProps[];
chartData5: lineChartDataProps[];
chartData6: lineChartDataProps[];
chartData7: lineChartDataProps[];
chartData8: lineChartDataProps[];
}>();
type dnChartForm = { label: string, val: number, fill?: string };
@@ -228,16 +228,18 @@ export default function PbnstPrfmncSttn() {
indstUswtr += element.indstUswtr;
// 월별 합계
inUsageSum = element.wshwtr
inUsageSum =
element.wshwtr
+ element.coolUswtr
+ element.cleanUswtr
+ element.drwtUswtr
+ element.dltnUswtr
+ element.ithlRswtr
+ element.ithlEtcUswtr
+ element.cleanToilUswtr
+ element.resn;
outUsageSum = element.lndwtr
outUsageSum =
element.cleanToilUswtr
+ element.resn
+ element.lndwtr
+ element.hydUswtr
+ element.ugwtChr
+ element.farmUswtr
@@ -258,9 +260,10 @@ export default function PbnstPrfmncSttn() {
dltnUswtrData.push({ label: `${month}월`, val: element.dltnUswtr, color: "purple" });
ithlRswtrData.push({ label: `${month}월`, val: element.ithlRswtr, color: "green" });
ithlEtcUswtrData.push({ label: `${month}월`, val: element.ithlEtcUswtr, color: "blue" });
cleanToilUswtrData.push({ label: `${month}월`, val: element.cleanToilUswtr, color: "magenta" });
resnData.push({ label: `${month}월`, val: element.resn, color: "skyblue" });
lndwtrData.push({ label: `${month}월`, val: element.lndwtr, color: "red" });
hydUswtrData.push({ label: `${month}월`, val: element.hydUswtr, color: "orange" });
ugwtChrData.push({ label: `${month}월`, val: element.ugwtChr, color: "yellow" });
@@ -271,22 +274,18 @@ export default function PbnstPrfmncSttn() {
}
});
let dnTotalIn = 0;
let dnTotalOut = 0;
inUsageArr.forEach((item) => {
dnTotalIn += item
})
outUsageArr.forEach((item) => {
dnTotalOut += item
})
// 도넛차트 데이터
dnData.push({ label: "세척수", val: wshwtr, fill: "#F7CFD8" });
dnData.push({ label: "냉각수", val: coolUswtr, fill: "#F4F8D3" });
dnData.push({ label: "청소수", val: cleanUswtr, fill: "#A6D6D6" });
dnData.push({ label: "식수대", val: drwtUswtr, fill: "#8E7DBE" });
dnData.push({ label: "희석용수", val: dltnUswtr, fill: "#FF8383" });
dnData.push({ label: "중수도", val: ithlRswtr, fill: "#FFF574" });
dnData.push({ label: "기타", val: ithlEtcUswtr, fill: "#A1D6CB" });
dnData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "#A19AD3" });
dnData.push({ label: "세척살수용수", val: resn, fill: "#B1F0F7" });
dnData.push({ label: "조경용수", val: lndwtr, fill: "#81BFDA" });
dnData.push({ label: "친수용수", val: hydUswtr, fill: "#F5F0CD" });
dnData.push({ label: "지하수충전", val: ugwtChr, fill: "#FADA7A" });
dnData.push({ label: "농업용수", val: farmUswtr, fill: "#A6AEBF" });
dnData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "#C5D3E8" });
dnData.push({ label: "공업용수", val: indstUswtr, fill: "#D0E8C5" });
dnData.push({ label: "장내용수", val: dnTotalIn, fill: "#8DD8FF" });
dnData.push({ label: "장외용수", val: dnTotalOut, fill: "#FFF085" });
// 장내용수 도넛
dnInData.push({ label: "세척수", val: wshwtr, fill: "#F7CFD8" });
@@ -296,10 +295,10 @@ export default function PbnstPrfmncSttn() {
dnInData.push({ label: "희석용수", val: dltnUswtr, fill: "#FF8383" });
dnInData.push({ label: "중수도", val: ithlRswtr, fill: "#FFF574" });
dnInData.push({ label: "기타", val: ithlEtcUswtr, fill: "#A1D6CB" });
dnInData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "#A19AD3" });
dnInData.push({ label: "세척살수용수", val: resn, fill: "#B1F0F7" });
// 장외용수 도넛
dnOutData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "#A19AD3" });
dnOutData.push({ label: "세척살수용수", val: resn, fill: "#B1F0F7" });
dnOutData.push({ label: "조경용수", val: lndwtr, fill: "#81BFDA" });
dnOutData.push({ label: "친수용수", val: hydUswtr, fill: "#F5F0CD" });
dnOutData.push({ label: "지하수충전", val: ugwtChr, fill: "#FADA7A" });
@@ -315,17 +314,17 @@ export default function PbnstPrfmncSttn() {
chartData4: drwtUswtrData,
chartData5: dltnUswtrData,
chartData6: ithlRswtrData,
chartData7: ithlEtcUswtrData,
chartData8: cleanToilUswtrData,
chartData9: resnData
chartData7: ithlEtcUswtrData
});
setOutLineData({
chartData1: lndwtrData,
chartData2: hydUswtrData,
chartData3: ugwtChrData,
chartData4: farmUswtrData,
chartData5: rvrKeepUswtrData,
chartData6: indstUswtrData,
chartData1: cleanToilUswtrData,
chartData2: resnData,
chartData3: lndwtrData,
chartData4: hydUswtrData,
chartData5: ugwtChrData,
chartData6: farmUswtrData,
chartData7: rvrKeepUswtrData,
chartData8: indstUswtrData,
});
setBarData(barData);
setChartData(dnData);
@@ -408,15 +407,7 @@ export default function PbnstPrfmncSttn() {
chartData7: {
label: '기타',
color: "blue",
},
chartData8: {
label: '청소화장실용수',
color: "magenta",
},
chartData9: {
label: '세척살수용수',
color: "skyblue",
},
}
} satisfies ChartConfig;
setLnInChartConfig(newChartConfig);
}
@@ -427,26 +418,34 @@ export default function PbnstPrfmncSttn() {
if (outLineData?.chartData1) {
const newChartConfig = {
chartData1: {
label: '조경용수',
label: '청소화장실용수',
color: "red",
},
chartData2: {
label: '세척살수용수',
color: "pink",
},
chartData3: {
label: '조경용수',
color: "gray",
},
chartData4: {
label: '친수용수',
color: "orange",
},
chartData3: {
chartData5: {
label: '지하수충전',
color: "yellow",
},
chartData4: {
chartData6: {
label: '농업용수',
color: "green",
},
chartData5: {
chartData7: {
label: '하천등유지용수',
color: "blue",
},
chartData6: {
chartData8: {
label: '공업용수',
color: "purple",
},
@@ -615,7 +614,7 @@ export default function PbnstPrfmncSttn() {
<h1 className="">장내·외 월별 하수재이용실적 현황</h1>
<div className="flex">
<div className="w-1/3">
<MainChart chartConfig={barChartConfig} chartData={barData} chartTitle={"월별 장내·외 용수 총 실적"} chartForm={CHART_FORM.DOUBLEBAR} />
<MainChart chartConfig={barChartConfig} chartData={barData} chartTitle={"월별 장내·외 용수 총 실적"} chartForm={CHART_FORM.DOUBLEBAR} showLegend={true} />
</div>
<div className="w-1/3">
<MainChart chartConfig={lnInChartConfig} chartData={inLineData} chartTitle={"월별 장내 용수 실적"} chartForm={CHART_FORM.LINE} />
@@ -627,25 +626,25 @@ export default function PbnstPrfmncSttn() {
<h1 className="">하수재이용실적 현황</h1>
<ListDiv className="col-span-8 pr-6 ">
{/* <ScrollArea className="h-[60vh]"> */}
{/* <div className="card-header">
{/* <div className="card-header">
<h3 className="text-2xl font-medium text-label">
{"하수처리수재이용 실적(m3) 등록결과"}
</h3>
</div> */}
<UiTable
headName={undefined}
tableData={prfData || []}
headlist={monthHeadList}
pageSize={0}
total={0}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}>
</UiTable>
<UiTable
headName={undefined}
tableData={prfData || []}
headlist={monthHeadList}
pageSize={0}
total={0}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}>
</UiTable>
{/* <ListDiv className="col-span-8 pr-6 ">
{/* <ListDiv className="col-span-8 pr-6 ">
<ScrollArea className="h-[60vh]">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
+2 -1
View File
@@ -210,8 +210,9 @@ export default function RainStat() {
...item,
ctpvCd: item.cityText
};
})
}).filter((item: RainStatProps) => item.cityText !== '없음')
: [];
setBdstTable(tbData);
}, [code, rainStatList]);
+1 -1
View File
@@ -46,7 +46,7 @@ export default function RswtStat() {
...item,
ctpvCd: item.cityText
};
})
}).filter((item: RswtStatProps) => item.cityText !== '없음')
: [];
setRswtTable(tbData);
}, [code, rswtStatList])
+43 -21
View File
@@ -20,6 +20,7 @@ export type ldrmntState = {
excelList: ExcelType[];
isInit: boolean;
ldrmntRes: customLdrmnt[];
isLoading: boolean;
};
};
@@ -35,6 +36,7 @@ type ldrmntActions = {
ldrmntSearch: (page: number, sggCd: string, total: string, coNm: string) => void;
ldrmntUpdate: (ldrmntRes: customLdrmnt) => void;
ldrmntDelete: () => void;
loadingOpen: (isOpen: boolean) => void
};
export type ldrmntType = ldrmntState & ldrmntActions;
@@ -46,6 +48,7 @@ export const ldrmntStateExport: ldrmntState = {
excelList: [],
isInit: false,
ldrmntRes: ldrmntRes,
isLoading: false,
},
};
@@ -75,31 +78,30 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = (
console.log(e)
}
},
loadingOpen: (isOpen) => {
return set((state: ldrmntState) => {
return {
...state,
ldrmnt: {
...state.ldrmnt,
isLoading: isOpen,
},
};
});
},
ldrmntRegData: async (data) => {
try {
const response = await postFectch(
"/reuseFc/request/temp",
data,
fetchType.WR
);
await deleteFectch(
`/reuseFc/request/temp`,
);
console.log('response ', response)
const response = await postFectch(
"/reuseFc/request/temp",
data,
fetchType.WR
);
if (response.status === 200) {
ToastFormUi({
body: "잠재수요처 등록이 완료되었습니다. ",
title: "잠재수요처 등록",
});
console.log('response ', response)
}
} catch (e) {
console.log(e)
ToastFormUi({
body: "잠재수요처 등록이 실패하였습니다. ",
title: "잠재수요처 등록",
});
}
},
ldrmntSearch: async (page: number, total: string, sggCd: string, coNm: string) => {
let searchUrl = "";
@@ -111,6 +113,7 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = (
searchUrl = `/reuseFc/request/temp/${page}/${total}/${sggCd}`;
}
}
try {
const ldrmntRes = await getFetch(
fetchType.WR,
@@ -178,11 +181,30 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = (
},
ldrmntDelete: async () => {
try {
set((state: ldrmntState) => {
return {
...state,
ldrmnt: {
...state.ldrmnt,
isLoading: true,
},
};
});
const response = await deleteFectch(
`/reuseFc/request/temp`,
);
} catch (error) {
console.log("error", error)
set((state: ldrmntState) => {
return {
...state,
ldrmnt: {
...state.ldrmnt,
isLoading: false,
},
};
});
console.error("Error ldrmntDelete:", error);
}
},
+13 -9
View File
@@ -1,12 +1,12 @@
import { fetchType, getFetch, postFectch } from "@/components/lib/api";
import type { TableUpperProps } from "../../common/type/header";
import { ruseCaseHeadListExport } from "./headList";
import { ToastFormUi } from "@/components/ui/toast/toastFromUi";
import { toast } from "react-hot-toast";
export type ruseCaseState = {
ruseCaseMng: {
ruseHeadList:TableUpperProps[];
ruseCaseMngList:ruseCaseMngExcelProps[];
status: number;
};
};
@@ -69,7 +69,6 @@ type ruseCaseActions = {
};
export const fetchSearchRuseCaseMng = async (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => {
const tailUrl = ((sigunguCd !== '전체' && sigunguCd !=='') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd? `/${usageCd}` : '/0') + (name? `/${name}` : '');
const resp = await getFetch(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`);
return resp;
@@ -77,7 +76,7 @@ export const fetchSearchRuseCaseMng = async (sidoCd: string, sigunguCd: string,
export const fetchInsertRuseCaseMng = async (data: any) => {
const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR);
return response.data;
return {data:response.data, status:response.status};
};
export type ruseCaseType = ruseCaseState & ruseCaseActions;
@@ -86,6 +85,7 @@ export const ruseCaseStateExport: ruseCaseState = {
ruseCaseMng: {
ruseHeadList:ruseCaseHeadListExport,
ruseCaseMngList:[],
status: 0,
},
};
@@ -95,7 +95,6 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
return {
selectRuseCaseMng: async (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => {
const res = await fetchSearchRuseCaseMng(sidoCd, sigunguCd, usageCd, name);
console.log('res : ', res);
set((state: ruseCaseState) => ({
ruseCaseMng: {
...state.ruseCaseMng,
@@ -107,11 +106,16 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
insertRuseCaseMng: async (data: any) => {
const response = await fetchInsertRuseCaseMng(data);
if(response.status === 200){
ToastFormUi({
body: "잠재수요처 등록이 완료되었습니다. ",
title: "잠재수요처 등록",
});
toast.success("물재이용사례 등록이 완료되었습니다. ");
}else{
toast.error("물재이용사례 등록에 실패하였습니다. ");
}
set((state: ruseCaseState) => ({
ruseCaseMng: {
...state.ruseCaseMng,
status: response.status,
},
}));
},
};
};
@@ -148,7 +148,7 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions
let usageSum = 0;
let year = "";
const yearsData = []
res.data.forEach((item) => {
if (item.prfList.length === 0) {
const year = ""; // 연도 정보가 없으면 빈 문자열, 필요시 item에서 추출
@@ -161,26 +161,26 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions
item.prfList.forEach((acc) => {
year = acc.ym?.substring(0, 4) ?? "";
inUsageSum =
(acc.wshwtr ?? 0) +
(acc.coolUswtr ?? 0) +
(acc.cleanUswtr ?? 0) +
(acc.drwtUswtr ?? 0) +
(acc.dltnUswtr ?? 0) +
(acc.ithlRswtr ?? 0) +
(acc.ithlEtcUswtr ?? 0) +
(acc.cleanToilUswtr ?? 0) +
(acc.resn ?? 0);
(acc.wshwtr ?? 0) + //조경용수
(acc.coolUswtr ?? 0) + //냉각수
(acc.cleanUswtr ?? 0) + //청소수
(acc.drwtUswtr ?? 0) + //식수대
(acc.dltnUswtr ?? 0) + //희석용수
(acc.ithlRswtr ?? 0) + //중수도
(acc.ithlEtcUswtr ?? 0) //기타
outUsageSum =
(acc.lndwtr ?? 0) +
(acc.hydUswtr ?? 0) +
(acc.ugwtChr ?? 0) +
(acc.farmUswtr ?? 0) +
(acc.rvrKeepUswtr ?? 0) +
(acc.indstUswtr ?? 0);
(acc.cleanToilUswtr ?? 0) + //청소화장실용수
(acc.resn ?? 0)+ //세척살수용도
(acc.lndwtr ?? 0) + //조경용수
(acc.hydUswtr ?? 0) + //친수용수
(acc.ugwtChr ?? 0) + //지하수충전
(acc.farmUswtr ?? 0) + //농업용수
(acc.rvrKeepUswtr ?? 0) + //하천등유지용수
(acc.indstUswtr ?? 0); //공업용수
const yearData = yearsData.find(
(yearItem) =>
yearItem.year === year &&
yearItem.pbnstRuseFcltPk == acc.pbnstRuseFcltPk
yearItem.pbnstRuseFcltPk == acc.pbnstRuseFcltFk
);
if (yearData) {
@@ -5,6 +5,8 @@ import type { z } from "zod";
import { controlHeadView } from "../rainSttn";
import type { customHtdflBdst } from "../../fcltReg/htdflReg";
import { fetchType, getFetch } from "@/components/lib/api";
import type { SelectDataType } from "@common/business_components";
import { excelHtdflSttnParsing } from "./excel";
const htdflSttHeadList = htdflSttHeadListExport;
const htdflSttNodeList = htdflSttNodeListExport;
@@ -21,7 +23,8 @@ export type htdflSttnState = {
};
type htdflSttnActions = {
initHtdfl: (con: string) => void;
htdflSttnSearch: (page: number, total: string, con: string) => void;
htdflSttnSearch: (page: number, total: string, con: string, sidoCode: SelectDataType[],
sggCode: SelectDataType[]) => void;
htdflChSttnHead: (
headList: z.infer<typeof sttnHeadListType>,
) => void;
@@ -67,22 +70,23 @@ export const htdflSttnActionsExport: (set: any) => htdflSttnActions = (
console.log(error);
}
},
htdflSttnSearch: async (page: number, total: string, con: string) => {
htdflSttnSearch: async (page: number, total: string, con: string,
sidoCode: SelectDataType[],
sggCode: SelectDataType[]
) => {
try {
const htdflSttnRes = await getFetch(
fetchType.WR,
`/drain/facilityList/${page}/${total}/${con}`
);
if (Number(total) > 9000) {
const htdflExcel = excelHtdflSttnParsing(htdflSttnRes, sidoCode, sggCode);
set((state: htdflSttnState) => {
return {
...state,
htdflSttn: {
...state.htdflSttn,
excelList: htdflSttnRes,
excelList: htdflExcel,
},
};
});
@@ -4,32 +4,36 @@ import type { customLdrmnt } from "@/app/store/matDmd/ldrmntCoSttn";
import { names } from "@/app/store/matDmd/ldrmntCoSttn/excel";
import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import LoadingOverlay from "@common/business_components/ui/loadingBar";
import { Button, Progress } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import type { z } from "zod";
export default function CoRegExcel() {
export default function CoRegExcel({ isModal }: { isModal: (open: boolean) => void }) {
const { ldrmnt,loadingOpen } = useWrStore((state) => state);
const { isLoading } = ldrmnt;
const form = useForm<z.infer<typeof coRegExcel>>({
resolver: zodResolver(coRegExcel),
});
const { ldrmntRegData, ldrmntDelete } = useWrStore((state) => state);
const [excelData, setExcelData] = useState<customLdrmnt[]>();
const [file, setFiles] = useState<File | undefined>();
useEffect(() => {
console.log("ldrmnt", ldrmnt)
}, [ldrmnt])
const onSubmit = (values: z.infer<typeof coRegExcel>) => {
ldrmntDelete();
loadingOpen(true);
if (file) {
filePicked(file, 0).then((res) => {
//TODO:loading
filePicked(file, 0).then(async (res) => {
try {
let array: { [key: string]: string | number }[] = []
let isError = false;
if (Array.isArray(res)) {
res.some((item, index) => {
let object: { [key: string]: string | number } = {}
@@ -43,26 +47,24 @@ export default function CoRegExcel() {
}
return false;
})
if (isError === true)
return true;
array.push(object)
if (index != 0 && index % 5000 == 0) {
ldrmntRegData(array);
array = []
array.length = 0;
}
return false;
})
}
if (isError === false)
ldrmntRegData(array);
await ldrmntRegData(array);
isModal(false);
loadingOpen(false);
toast.success(`잠재수요처 등록이 완료되었습니다.`);
} catch (e) {
console.log(e)
loadingOpen(false);
alert(e)
return true;
}
}).catch((e)=>{
console.log("file", e);
});
};
}
@@ -70,6 +72,7 @@ export default function CoRegExcel() {
return (
<>
<div>
<LoadingOverlay isLoading={isLoading}></LoadingOverlay>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
@@ -88,12 +88,12 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
return (
<>
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
<div className="!mt-3 text-[2.5rem] font-bold">
<div className="!mt-3 text-[2.5rem] font-bold text-center">
{chartTitle}
</div>): null}
<div className="flex justify-center">
<ChartContainer config={chartConfig ?? {}} className={chartClassName + " !w-[90%]"}>
<BarChart accessibilityLayer data={chartData}>
<div className="flex justify-center h-full w-full">
<ChartContainer config={chartConfig ?? {}} className={chartClassName + " !w-[88%]"}>
<BarChart accessibilityLayer data={chartData} margin={{ top: 5, right: 20, left: 30, bottom: 5 }}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="label"
@@ -110,8 +110,13 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
axisLine={true}
fontSize={12}
fontWeight={500}
domain={[0, Math.ceil(barMaxValue * 1)]}
label={yFieldText ? { value: yFieldText, className:"text-[13px] font-bold", position: 'insideLeft', angle: -90, offset: 20 } : undefined}
tickFormatter={(value) => {
// 소수점이 있는 경우 소수점을 유지하고 천단위 콤마 추가
return value.toString().includes('.')
? value.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
: value.toLocaleString();
}}
label={yFieldText ? { value: yFieldText, className:"text-[13px] font-bold", position: 'insideLeft', angle: -90, offset: -24, orientation: "left" } : undefined}
/>
<ChartTooltip cursor={false} content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />}/>
{chartData && chartData.length > 0 && (
@@ -7,6 +7,7 @@ import {
} from "./lib/chart";
import type { CSSProperties } from "react";
/*
const customStyles = {
legendContainer: {
display: "flex",
@@ -17,6 +18,22 @@ const customStyles = {
marginTop: "10px",
} as CSSProperties,
};
*/
const customStyles = {
legendContainer: {
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
alignItems: "center",
gap: "12px",
fontSize: "13px",
fontWeight: "normal",
marginTop: "16px",
marginBottom: "8px",
minHeight: "32px",
} as CSSProperties,
};
type BarChartProps = {
chartConfig: ChartConfig | undefined;
@@ -60,7 +77,7 @@ const CustomTooltip = ({
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
const CustomLegend = (props: any) => {
/*const CustomLegend = (props: any) => {
const { payload } = props;
return (
<div style={customStyles.legendContainer}>
@@ -72,6 +89,38 @@ const CustomLegend = (props: any) => {
))}
</div>
);
};*/
const CustomLegend = (props: any) => {
const { payload } = props;
if (!payload) return null;
return (
<div style={customStyles.legendContainer}>
{payload.map((entry: any, index: number) => (
<div
key={`legend-${index}`}
className="flex items-center"
style={{
minWidth: "90px",
margin: "2px 0",
}}
>
<div
style={{
width: "14px",
height: "14px",
backgroundColor: entry.color,
borderRadius: "3px",
marginRight: "8px",
minWidth: "14px",
minHeight: "14px",
}}
></div>
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{entry.value}</span>
</div>
))}
</div>
);
};
export default function DoubleBarChart({chartConfig=undefined, chartData=[], chartTitle='차트', chartClassName='w-full h-full', graphSize=40, yFieldText='', showLegend=false} : BarChartProps) {
@@ -83,7 +132,7 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha
.map(key => item[key])
).flat());
};
console.log(chartData);
const barMaxValue = getMaxValue(chartData, 'barVal');
return (
<>
@@ -48,6 +48,7 @@ type LegendProps = {
};
/** 차트 기본 스타일 */
/*
const customStyles = {
legendContainer: {
display: "flex",
@@ -58,6 +59,23 @@ const customStyles = {
marginTop: "10px",
} as CSSProperties,
};
*/
/** 차트 기본 스타일 (라인차트와 동일하게) */
const customStyles = {
legendContainer: {
display: "flex",
flexWrap: "wrap", // 여러 줄로 자동 줄바꿈
justifyContent: "center",
alignItems: "center",
gap: "12px", // 아이템 간 간격
fontSize: "13px",
fontWeight: "normal",
marginTop: "16px",
marginBottom: "8px",
minHeight: "32px", // 범례 영역 높이 통일
} as CSSProperties,
};
// 커스텀 툴팁
const CustomTooltip = ({ active, payload, label }: {
@@ -82,6 +100,7 @@ const CustomTooltip = ({ active, payload, label }: {
};
// 커스텀 범례
/*
const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
@@ -97,12 +116,52 @@ const CustomLegend = (props: LegendProps) => {
</div>
);
};
*/
const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
return (
<div style={customStyles.legendContainer}>
{payload.map((entry: LegendEntry, index: number) => (
<div
key={`legend-${index}`}
className="flex items-center"
style={{
minWidth: "90px",
margin: "2px 0",
}}
>
<div
style={{
width: "14px",
height: "14px",
backgroundColor: entry.fill,
borderRadius: "3px",
marginRight: "8px",
minWidth: "14px",
minHeight: "14px",
}}
></div>
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>
{entry.payload?.label || entry.value}
</span>
</div>
))}
</div>
);
};
export default function DoughnutChart(props: {
chartConfig: any;
chartData: ChartDataItem[] | undefined;
chartTitle: string
}) {
const legendData = props.chartData?.map(item => ({
value: item.label,
fill: props.chartConfig?.[item.label]?.fill || 'blue',
})) ?? [];
return (
<Card className="flex flex-col">
<CardContent className="flex-1 pb-0">
@@ -130,9 +189,9 @@ export default function DoughnutChart(props: {
nameKey="label"
innerRadius={60}
/>
<CustomLegend />
</PieChart>
</ChartContainer>
<CustomLegend payload={legendData} />
</div>
</CardContent>
</Card>
@@ -127,7 +127,7 @@ export default function LineBarchart({
className="text-[12px] font-bold"
tickCount={6}
orientation="left"
domain={[0, Math.ceil(lineMaxValue * 1)]}
tickFormatter={(value) => value.toLocaleString()}
label={{ value: yFieldText, angle: -90, position: 'insideLeft' }}
/>}
{/* 오른쪽 Y축: barVal */}
@@ -136,7 +136,7 @@ export default function LineBarchart({
className="text-[12px] font-bold"
tickCount={6}
orientation="right"
domain={[0, Math.ceil(barMaxValue * 1)]}
tickFormatter={(value) => value.toLocaleString()}
label={{ value: yFieldText2, angle: -90, position: 'insideRight' }}
reversed={true}
/>}
@@ -18,6 +18,7 @@ type LineChartProps = {
}
/** 차트 기본 스타일 */
/*
const customStyles = {
legendContainer: {
display: "flex",
@@ -28,6 +29,23 @@ const customStyles = {
marginTop: "10px",
} as CSSProperties,
};
*/
/** 차트 기본 스타일 */
const customStyles = {
legendContainer: {
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
alignItems: "center",
gap: "12px",
fontSize: "13px",
fontWeight: "normal",
marginTop: "16px",
marginBottom: "8px",
minHeight: "32px",
} as CSSProperties,
};
type LegendEntry = {
value: string;
@@ -60,7 +78,7 @@ const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?:
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
const CustomLegend = (props: LegendProps) => {
/*const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
@@ -74,6 +92,40 @@ const CustomLegend = (props: LegendProps) => {
))}
</div>
);
};*/
// 커스텀 범례 (도넛차트와 동일하게)
const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
return (
<div style={customStyles.legendContainer}>
{payload.map((entry: LegendEntry, index: number) => (
<div
key={`legend-${index}`}
className="flex items-center"
style={{
minWidth: "90px",
margin: "2px 0",
}}
>
<div
style={{
width: "14px",
height: "14px",
backgroundColor: entry.color,
borderRadius: "3px",
marginRight: "8px",
minWidth: "14px",
minHeight: "14px",
}}
></div>
<span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{entry.value}</span>
</div>
))}
</div>
);
};
type DataType = {label: string; val: string};
@@ -141,7 +193,7 @@ export default function Linechart({
>
<CartesianGrid vertical={false} />
<XAxis dataKey="label" fontSize="8px" fontWeight="bold" />
<YAxis yAxisId="left" className="text-[12px] font-bold" domain={[0, Math.ceil(lineMaxValue * 1.1)]} tickCount={6} orientation="left" label={{ value: yFieldText, className:"text-[12px] font-bold", angle: -90, position: 'insideLeft' }} />
<YAxis yAxisId="left" className="text-[12px] font-bold" tickFormatter={(value) => value.toLocaleString()} tickCount={6} orientation="left" label={{ value: yFieldText, className:"text-[12px] font-bold", angle: -90, position: 'insideLeft' }} />
<Tooltip content={<CustomTooltip />} />
{showLegend && <Legend content={CustomLegend as ContentType}/>}
{Object.keys(chartData).map((key) => {
@@ -17,6 +17,7 @@ type MonthPickerProps = {
field: { value: string; onChange: (date: string) => void }; // yyyyMM 문자열
selectClass?: string;
readOnly?: boolean;
startYear?: number; // 시작 연도 props 추가
};
// yyyyMM → Date 객체
@@ -37,6 +38,7 @@ export const DateMonthPicker = ({
field,
selectClass,
readOnly = false,
startYear,
}: MonthPickerProps) => {
const [open, setOpen] = useState(false);
@@ -75,6 +77,7 @@ export const DateMonthPicker = ({
<MonthPicker
currentMonth={parsedDate}
onMonthChange={handleSelect}
startYear={startYear}
/>
</PopoverContent>
</Popover>
@@ -28,35 +28,41 @@ export type MonthPickerProps = {
newMonth: Date | undefined,
shouldClosePopover?: boolean
) => void;
startYear?: number;
};
export function MonthPicker({ currentMonth, onMonthChange }: MonthPickerProps) {
export function MonthPicker({ currentMonth, onMonthChange, startYear }: MonthPickerProps) {
const today = startOfToday();
const currentYear = today.getFullYear();
const currentYear = today.getFullYear();
const yearFromProp = format(currentMonth, "yyyy");
const selectedYear = parseInt(yearFromProp);
const selectedYear = parseInt(yearFromProp);
// 현재 월의 시작 날짜
const fullYearStart = parse(yearFromProp, "yyyy", new Date());
const yearEnd =
selectedYear === currentYear ? today : endOfYear(fullYearStart);
const yearRange = Array.from({ length: 21 }, (_, i) => currentYear - 20 + i);
// startYear가 없으면 기존과 동일하게 (현재년도 - 20년부터 현재년도까지)
// startYear가 있으면 해당 년도부터 현재년도까지, 내림차순으로 정렬
const yearRange = startYear
? Array.from({ length: currentYear - startYear + 1 }, (_, i) => currentYear - i)
: Array.from({ length: 21 }, (_, i) => currentYear - 20 + i);
const months = eachMonthOfInterval({ start: fullYearStart, end: yearEnd });
const getMonthButtonClass = (month: Date) =>
cn(
"inline-flex h-9 w-16 items-center justify-center rounded-md p-0 text-sm font-normal ring-offset-white transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-selected:opacity-100 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-800",
isEqual(month, currentMonth) &&
"bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
"bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
!isEqual(month, currentMonth) &&
isEqual(month, getStartOfCurrentMonth()) &&
"bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50"
isEqual(month, getStartOfCurrentMonth()) &&
"bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50"
);
return (
@@ -77,7 +83,7 @@ const selectedYear = parseInt(yearFromProp);
onMonthChange(newDate, false);
}}
>
<SelectTrigger className="w-[120px] h-7">
<SelectTrigger className="w-[140px] h-9 text-base">
<SelectValue placeholder="년도" />
</SelectTrigger>
<SelectContent>
@@ -112,7 +118,7 @@ const selectedYear = parseInt(yearFromProp);
onClick={() => onMonthChange(month, true)}
>
<time dateTime={format(month, "yyyy-MM-dd")}>
{format(month, 'MMM', { locale: ko })}
{format(month, 'MMM', { locale: ko })}
</time>
</button>
</div>
@@ -21,4 +21,5 @@ export * from "./table_div";
export * from "./title";
export * from "./sdmInsertForm";
export * from "./popover_ui";
export * from "./date_month_picker";
export * from "./date_month_picker";
export * from "./loadingBar";
@@ -187,7 +187,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
image.src = canvas.toDataURL('image/png');
return image;
};
if (!treeData || treeData.length === 0) return null;
return (
@@ -220,6 +220,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
<input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer" />
{node.icon && <span>{node.icon}</span>}
<span className="text-[1.3rem] font-medium text-label">{node.text}</span>
{depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={()=>{}} alt={node.text}></img></span>}
</div>
)}
sort={(a, b) => {
@@ -40,7 +40,8 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
<PaginationItem>
<PaginationPrevious
href="#"
onClick={() => {
onClick={(e) => {
e.preventDefault();
if (startPage > 1) handlePageChange(startPage - 1);
}}
/>
@@ -53,7 +54,7 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
<PaginationLink
href="#"
isActive={pageNum === pageProps.cur}
onClick={() => handlePageChange(pageNum)}
onClick={(e) => {e.preventDefault(); handlePageChange(pageNum)}}
>
{pageNum}
</PaginationLink>
@@ -65,7 +66,8 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
<PaginationItem>
<PaginationNext
href="#"
onClick={() => {
onClick={(e) => {
e.preventDefault();
if (endPage < totalPages) handlePageChange(endPage + 1);
}}
/>