diff --git a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx index 436b77736..517dab0c0 100644 --- a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx +++ b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx @@ -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([]); const [objSearch, setObjSearch] = useState({ 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 ( <> @@ -239,7 +247,7 @@ export default function RuseCaseMng() { - + {/* 엑셀 등록 모달 */} } + body={ setIsExcelOpen({ isModal: isOpen })} />} /> + diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx index e43d5b04f..03b549270 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx @@ -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(); + const [selectSgg, setSelectSgg] = useState() + const [objSearch, setObjSearch] = useState({ 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() {
- <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} /> diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx index 7e068bcd0..804e9d808 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx @@ -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 "> diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx index f9b3c6187..05ee774f1 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx @@ -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} /> diff --git a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx index f689eb745..3e860d744 100644 --- a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx @@ -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"> diff --git a/apps/wr/app/stats/rainStat/page.tsx b/apps/wr/app/stats/rainStat/page.tsx index 08990028f..45d32d3a4 100644 --- a/apps/wr/app/stats/rainStat/page.tsx +++ b/apps/wr/app/stats/rainStat/page.tsx @@ -210,8 +210,9 @@ export default function RainStat() { ...item, ctpvCd: item.cityText }; - }) + }).filter((item: RainStatProps) => item.cityText !== '없음') : []; + setBdstTable(tbData); }, [code, rainStatList]); diff --git a/apps/wr/app/stats/rswtStat/page.tsx b/apps/wr/app/stats/rswtStat/page.tsx index c0741433c..997e95ca5 100644 --- a/apps/wr/app/stats/rswtStat/page.tsx +++ b/apps/wr/app/stats/rswtStat/page.tsx @@ -46,7 +46,7 @@ export default function RswtStat() { ...item, ctpvCd: item.cityText }; - }) + }).filter((item: RswtStatProps) => item.cityText !== '없음') : []; setRswtTable(tbData); }, [code, rswtStatList]) diff --git a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx index 6a5f18247..289ced6e1 100644 --- a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx +++ b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx @@ -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); } }, diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx index 915740f79..76468d249 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx @@ -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, + }, + })); }, }; }; diff --git a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx index b513085ae..db8b6a717 100644 --- a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx +++ b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx @@ -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) { diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx index dccc8719c..ba81066be 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx @@ -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, }, }; }); diff --git a/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx b/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx index 89f15a3cc..6f6afe186 100644 --- a/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx +++ b/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/chart/BarChart.tsx b/packages/common/business_components/src/ui/chart/BarChart.tsx index 0802a72e4..994feb3b3 100644 --- a/packages/common/business_components/src/ui/chart/BarChart.tsx +++ b/packages/common/business_components/src/ui/chart/BarChart.tsx @@ -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 && ( diff --git a/packages/common/business_components/src/ui/chart/DoubleBar.tsx b/packages/common/business_components/src/ui/chart/DoubleBar.tsx index 9627485aa..b201b3bf9 100644 --- a/packages/common/business_components/src/ui/chart/DoubleBar.tsx +++ b/packages/common/business_components/src/ui/chart/DoubleBar.tsx @@ -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 ( <> diff --git a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx index 39dee3d9e..99395267a 100644 --- a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx +++ b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/chart/LineBarChart.tsx b/packages/common/business_components/src/ui/chart/LineBarChart.tsx index 8c7202104..11984cc14 100644 --- a/packages/common/business_components/src/ui/chart/LineBarChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineBarChart.tsx @@ -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} />} diff --git a/packages/common/business_components/src/ui/chart/LineChart.tsx b/packages/common/business_components/src/ui/chart/LineChart.tsx index 4014702b6..6c1b6e7bd 100644 --- a/packages/common/business_components/src/ui/chart/LineChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineChart.tsx @@ -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) => { diff --git a/packages/common/business_components/src/ui/date_month_picker/index.tsx b/packages/common/business_components/src/ui/date_month_picker/index.tsx index d318b561f..1b97d9ce1 100644 --- a/packages/common/business_components/src/ui/date_month_picker/index.tsx +++ b/packages/common/business_components/src/ui/date_month_picker/index.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx index acf17fba4..2b64ab439 100644 --- a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx +++ b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx @@ -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> diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts index fd1e6fe9e..06c641259 100644 --- a/packages/common/business_components/src/ui/index.ts +++ b/packages/common/business_components/src/ui/index.ts @@ -21,4 +21,5 @@ export * from "./table_div"; export * from "./title"; export * from "./sdmInsertForm"; export * from "./popover_ui"; -export * from "./date_month_picker"; \ No newline at end of file +export * from "./date_month_picker"; +export * from "./loadingBar"; \ No newline at end of file diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 3c2cb2819..200e9d988 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -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) => { diff --git a/packages/common/business_components/src/ui/table/uiPageNations.tsx b/packages/common/business_components/src/ui/table/uiPageNations.tsx index 891fc4c81..a4ee3b4fc 100644 --- a/packages/common/business_components/src/ui/table/uiPageNations.tsx +++ b/packages/common/business_components/src/ui/table/uiPageNations.tsx @@ -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); }} />