Merge commit '401b68fe24ad83c0ef2adc94c7f81f19e4a8b2bf' into iipbs/wr/dev
iipbs_wr / build (push) Waiting to run

This commit is contained in:
leedj312
2025-06-27 15:52:18 +09:00
5 changed files with 478 additions and 53 deletions
+94 -45
View File
@@ -1,30 +1,83 @@
"use client";
import { useWrStore } from "@/app/store";
import { ruseUtzType } from "@/app/store/matDmd/ruseCaseMng/headList";
import { getCity } from "@/components/lib/func/commom";
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 { ContentsDiv, SearchDiv, SearchFormLeft, SearchFormRight, SearchInput, SelectBox, Title, UiTable, type SelectDataType } from "@common/business_components/ui";
import {
ContentsDiv,
SearchDiv,
SearchFormLeft,
SearchFormRight,
SearchInput,
SelectBox,
Title,
UiModal,
UiTable,
type SelectDataType,
} from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { Button } from "@common/components/ui";
import { useState } from "react";
export default function RuseCaseMng() {
const { code,ruseCase} = useWrStore((state) => state);
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>()
const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state);
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>([{val: "0", text: "전체"}]);
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const [selectUtz, setSelectUtz] = useState<string[] | []>()
const [searchTxt, setSearchTxt] = useState<string>()
const [sidoCd, setSidoCd] = useState<string>("");
const [sigunguCd, setSigunguCd] = useState<string>("");
const [selectUtz, setSelectUtz] = useState<string[] | []>(["조경용수"]);
const [name, setName] = useState<string>("");
// 물재이용사례 조회
const searchRuseCaseMng = async () => {
console.log(selectUtz);
const res = await selectRuseCaseMng(sidoCd, sigunguCd, selectUtz, name);
console.log('res : ', res);
}
// 시도 선택
const getSelCity = (val: string | number) => {
const cityValue = String(val);
const cityText = code.cityCd.find((item: any) => item.cityCd === cityValue)?.text;
setSidoCd(cityText || "");
const data = getCity(cityValue, code.sggCd);
setSelectSgg(data);
setObjSearch({ city: cityValue, sgg: '' });
setObjSearch({ city: cityValue, sgg: "" });
};
// 시군구 선택
const getSgg = (val: string | number) => {
setObjSearch({ ...objSearch, sgg: String(val) })
const sggValue = String(val);
const sggText = selectSgg?.find((item: any) => item.sggCd === sggValue)?.sggNm;
setSigunguCd(sggText || "");
setObjSearch({ ...objSearch, sgg: String(val) });
};
// 엑셀 등록 모달
const [isExcelOpen, setIsExcelOpen] = useState<{
isModal: boolean;
}>({ isModal: false });
// 다운로드
const download = () => {
console.log("다운로드");
}
// 양식받기
const sampleDownLoad = () => {
excelDownload(
[[]],
"물재이용사례 관리",
"sample_ruseCaseMng.xlsx",
"A1:AX1",
2
);
}
return (
<>
<ContentsDiv>
@@ -54,47 +107,32 @@ export default function RuseCaseMng() {
selectClass={""}
selectArray={ruseUtzType}
onSelectValue={(val) => {
if(val)
setSelectUtz([String(val)])}}
if (val) setSelectUtz([String(val)]);
}}
/>
<SearchInput
className={"text-2xl"}
label="수요처명"
textValue={""}
textValue={name}
setText={(e) => {
setSearchTxt(e)
setName(e);
}}
/>
</div>
</SearchFormLeft>
<SearchFormRight
className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 ">
<SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2">
<div className="space-y">
<div className="flex flex-row space-x-2">
<Button
size={"sm"}
onClick={(e) => { }}
>
<Button size={"sm"} onClick={() => { searchRuseCaseMng() }}>
조회
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
<Button size={"sm"} onClick={() => { setIsExcelOpen({ isModal: true }); }}>
엑셀등록
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
<Button size={"sm"} onClick={() => {}}>
다운로드
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
<Button size={"sm"} onClick={() => { sampleDownLoad() }}>
양식받기
</Button>
</div>
@@ -103,24 +141,35 @@ export default function RuseCaseMng() {
</SearchDiv>
</div>
</div>
<div className="grid grid-cols-6 gap-4 ">
<div className="grid grid-cols-6 gap-4">
<div className="col-span-6">
<ListDiv className="overflow-x-auto">
<UiTable
headName={"물재이용사례 현황"}
tableData={[]}
children={undefined}
headlist={ruseCase.ruseHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
}}
onPageChange={() => { }}
></UiTable>
<div className="w-[350%]">
<UiTable
headName={"물재이용사례 현황"}
tableData={[]}
children={undefined}
headlist={ruseCaseMng.ruseHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {}}
onPageChange={() => {}}
/>
</div>
</ListDiv>
</div>
</div>
</ContentsDiv>
{/* 엑셀 등록 모달 */}
<UiModal
open={isExcelOpen.isModal}
onOpenChange={() => {
setIsExcelOpen({ isModal: !isExcelOpen.isModal })
}}
bodyClass={"w-[40vw] max-w-none"}
body={<RuseCaseMngExcel />}
/>
</>
)
);
}
@@ -0,0 +1,109 @@
import { z } from "zod";
export const reUseCaseExcel = z.object({
file: z.any()
});
type nameMap = {
[key: string]: string;
}
export const reUseCaseNames: nameMap = {
"사업명": "ruseBizNm",
"사업시작일": "bizBgngDate",
"사업종료일": "bizEndtDate",
"공급처_시설종류": "splyFcltKnd",
"공급처명": "splyFcltNm",
"공급처_시설용량(㎥/일)": "splyFcltCpct",
"수요처명": "dmdFcltNm",
"수요처_주소": "dmdFcltAddr",
"재이용시설_시설용량(㎥/일)": "ruseFcltCpct",
"재이용시설_관로(km)": "ruseFcltPplLen",
"재이용시설_용도": "ruseFcltUsg",
"재이용시설_공급량(㎥/일)": "ruseFcltSplyCpct",
"재이용시설_처리공법": "ruseFcltPrcsMsrc",
"보증수질_용도별": "grnteWqUsg",
"보증수질_총대장균군수": "grnteWqGrssClfg",
"보증수질_결합잔류염소": "grnteWqCombResdch",
"보증수질_탁도": "grnteWqTrbt",
"보증수질_생물학적산소요구량(BOD)(mg/L)": "grnteWqBod",
"보증수질_냄새": "grnteWqSmll",
"보증수질_색도(도)": "grnteWqCrmty",
"보증수질_총질소(T-N)(mg/L)": "grnteWqTn",
"보증수질_총인(T-P)(mg/L)": "grnteWqTp",
"보증수질_수소이온농도(pH)": "grnteWqPh",
"보증수질_염화물": "grnteWqClIonQnt",
"보증수질_전기전도도": "grnteWqCndctStat",
"보증수질_비고": "grnteWqRmrk",
"소요사업비_총(백만원)": "reqBizGrssCst",
"소요사업비_토목_재이용시설(백만원)": "reqBizReuseFcltCst",
"소요사업비_토목_관로(백만원)": "reqBizPplCst",
"소요사업비_건축(백만원)": "reqBizArchCst",
"소요사업비_기계(백만원)": "reqBizMchnCst",
"소요사업비_전기.계측(백만원)": "reqBizMtrngCst",
"소요사업비_GIS DB(백만원)": "reqBizGisCst",
"소요사업비_CCTV(백만원)": "reqBizCctvCst",
"소요사업비_시공 및 준공(백만원)": "reqBizCmcnCst",
"소요사업비_폐기물(백만원)": "reqBizWstCst",
"소요사업비_설계비(백만원)": "reqBizDesignCst",
"소요사업비_감리비(백만원)": "reqBizSpvsCst",
"소요사업비_시설부대비(백만원)": "reqBizSbsdCst",
"소요사업비_기타(백만원)": "reqBizEtc",
"재원조달계획_총(백만원)": "prcmPlanGrssCst",
"재원조달계획_국비(백만원)": "prcmPlanNlep",
"재원조달계획_지방비(백만원)": "prcmPlanLcep",
"재원조달계획_원인자부담(백만원)": "prcmPlanBrdnCst",
"재원조달계획_국고비율(백만원)": "prcmPlanEcqrRt",
"사업비 정산_총(백만원)": "clclnGrssCst",
"사업비 정산_국비(백만원)": "clclnNlep",
"사업비 정산_지방비(백만원)": "clclnLcep",
"사업비 정산_원인자부담(백만원)": "clclnBrdnCst",
"사업비 정산_국고비율(백만원)": "clclnEcqrRt"
}
interface PbnstSttnItem {
rvbsn: string;
lgbsn: number;
mdmbsn?: string;
rgnNm?: string;
sido?: number;
gugun?: number;
bscFctNm?: string;
efflWqltStndrdNm?: string;
fctCpc?: number;
tel?: string;
ruseUtztnFcltNm?: string;
fcltCpct?: number;
splyExtraCpct?: number;
ruseCpct?: number;
usageTotal?: number;
inUsage?: number;
outUsage?: number;
}
export const excelPbnstSttnParsing = (data: PbnstSttnItem[]): string[][] => {
const excelExportData: string[][] = [];
data.forEach((item) => {
excelExportData.push([
item.rvbsn,
String(item.lgbsn),
item.mdmbsn ?? "",
item.rgnNm ?? "",
String(item.sido ?? ""),
String(item.gugun ?? ""),
String(item.bscFctNm ?? ""),
String(item.efflWqltStndrdNm ?? ""),
String(item.fctCpc ?? ""),
String(item.tel ?? ""),
String(item.ruseUtztnFcltNm ?? ""),
String(item.fcltCpct ?? ""),
String(item.splyExtraCpct ?? ""),
String(item.ruseCpct ?? ""),
String(item.usageTotal ?? ""),
String(item.inUsage ?? ""),
String(item.outUsage ?? ""),
])
})
return excelExportData;
};
+103 -8
View File
@@ -1,24 +1,119 @@
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";
export type ruseCaseState = {
ruseCase: {
ruseHeadList:TableUpperProps[];
};
ruseCaseMng: {
ruseHeadList:TableUpperProps[];
ruseCaseMngList:TableUpperProps[];
};
type ruseCaseActions = {
};
export interface ruseCaseMngExcelProps {
ruseBizNm: string,
bizBgngDate: string,
bizEndDate: string,
splyFcltKnd: string,
splyFcltNm: string,
splyFcltCpct: number | 0 |'',
dmdFcltNm: string,
dmdFcltAddr: string,
ruseFcltCpct: number | 0 |'',
ruseFcltPplLen: string,
ruseFcltUsg: string,
ruseFcltSplyCpct: number | 0 |'',
ruseFcltPrcsMsrc: string,
grnteWqUsg: string,
grnteWqGrssClfg: number | 0 |'',
grnteWqCombResdch: number | 0 |'',
grnteWqTrbt: string,
grnteWqBod: string,
grnteWqSmll: string,
grnteWqCrmty: string,
grnteWqTn: string,
grnteWqTp: string,
grnteWqPh: string,
grnteWqClIonQnt: string,
grnteWqCndctStat: string,
grnteWqRmrk: string,
reqBizReuseFcltCst: number | 0 |'',
reqBizPplCst: number | 0 |'',
reqBizArchCst: number | 0 |'',
reqBizMchnCst: number | 0 |'',
reqBizMtrngCst: number | 0 |'',
reqBizGisCst: number | 0 |'',
reqBizCctvCst: number | 0 |'',
reqBizCmcnCst: number | 0 |'',
reqBizWstCst: number | 0 |'',
reqBizDesignCst: number | 0 |'',
reqBizSpvsCst: number | 0 |'',
reqBizSbsdCst: number | 0 |'',
reqBizEtc: string,
reqBizGrssCst: number | 0 |'',
prcmPlanNlep: number | 0 |'',
prcmPlanLcep: number | 0 |'',
prcmPlanBrdnCst: number | 0 |'',
prcmPlanEcqrRt: number | 0 |'',
prcmPlanGrssCst: number | 0 |'',
clclnNlep: number | 0 |'',
clclnLcep: number | 0 |'',
clclnBrdnCst: number | 0 |'',
clclnEcqrRt: number | 0 |'',
clclnGrssCst: number | 0 |'',
}
type ruseCaseActions = {
selectRuseCaseMng: (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => Promise<void>;
insertRuseCaseMng: (data: any) => Promise<void>;
};
export const fetchSearchRuseCaseMng = async (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => {
const tailUrl = (sidoCd && !sigunguCd ? `/${sigunguCd}` : sigunguCd ? `/${sigunguCd}` : '/0') + (usageCd ? `/${usageCd}` : '') + (name ? `/${name}` : '');
console.log('tailUrl : ', tailUrl);
const resp = await getFetch(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`);
console.log('resp : ', resp);
return resp;
};
export const fetchInsertRuseCaseMng = async (data: any) => {
console.log('data : ', data)
const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR);
return response.data;
};
export type ruseCaseType = ruseCaseState & ruseCaseActions;
export const ruseCaseStateExport: ruseCaseState = {
ruseCase: {
ruseHeadList:ruseCaseHeadListExport,
ruseCaseMng: {
ruseHeadList:ruseCaseHeadListExport,
ruseCaseMngList:[],
},
};
export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
set: any
) => {
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,
ruseCaseMngList: data,
},
}));
},
insertRuseCaseMng: async (data: any) => {
const response = await fetchInsertRuseCaseMng(data);
if(response.status === 200){
ToastFormUi({
body: "잠재수요처 등록이 완료되었습니다. ",
title: "잠재수요처 등록",
});
}
},
};
};
@@ -0,0 +1,172 @@
import { coRegExcel } from "@/app/store/matDmd/coSttn/headList";
import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index";
import { reUseCaseNames } from "@/app/store/matDmd/ruseCaseMng/excel";
import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import { Button } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import type { z } from "zod";
import { useWrStore } from "@/app/store";
export default function RuseCaseMngExcel() {
const { insertRuseCaseMng } = useWrStore((state) => state);
const form = useForm<z.infer<typeof coRegExcel>>({
resolver: zodResolver(coRegExcel),
});
const [file, setFiles] = useState<File | undefined>();
const onSubmit = () => {
if (file) {
filePicked(file, 0).then((res: unknown) => {
try {
const newArray: ruseCaseMngExcelProps[] = []
const dataArray = Array.isArray(res) ? res : [];
dataArray.forEach((item: Record<string, unknown>) => {
const object: ruseCaseMngExcelProps = {
ruseBizNm: '',
bizBgngDate: '',
bizEndDate: '',
splyFcltKnd: '',
splyFcltNm: '',
splyFcltCpct: 0,
dmdFcltNm: '',
dmdFcltAddr: '',
ruseFcltCpct: 0,
ruseFcltPplLen: '',
ruseFcltUsg: '',
ruseFcltSplyCpct: 0,
ruseFcltPrcsMsrc: '',
grnteWqUsg: '',
grnteWqGrssClfg: 0,
grnteWqCombResdch: 0,
grnteWqTrbt: '',
grnteWqBod: '',
grnteWqSmll: '',
grnteWqCrmty: '',
grnteWqTn: '',
grnteWqTp: '',
grnteWqPh: '',
grnteWqClIonQnt: '',
grnteWqCndctStat: '',
grnteWqRmrk: '',
reqBizReuseFcltCst: 0,
reqBizPplCst: 0,
reqBizArchCst: 0,
reqBizMchnCst: 0,
reqBizMtrngCst: 0,
reqBizGisCst: 0,
reqBizCctvCst: 0,
reqBizCmcnCst: 0,
reqBizWstCst: 0,
reqBizDesignCst: 0,
reqBizSpvsCst: 0,
reqBizSbsdCst: 0,
reqBizEtc: '',
reqBizGrssCst: 0,
prcmPlanNlep: 0,
prcmPlanLcep: 0,
prcmPlanBrdnCst: 0,
prcmPlanEcqrRt: 0,
prcmPlanGrssCst: 0,
clclnNlep: 0,
clclnLcep: 0,
clclnBrdnCst: 0,
clclnEcqrRt: 0,
clclnGrssCst: 0,
};
Object.keys(reUseCaseNames).forEach((key: string) => {
if (item && reUseCaseNames[key] !== undefined) {
const mappedKey = reUseCaseNames[key];
if (mappedKey && object.hasOwnProperty(mappedKey)) {
let value;
if(value === "-"){
value = '';
}
if(key === "사업시작일" || key === "사업종료일"){
if(item[key]?.toString().split(".")[1]?.length === 1){
value = `${item[key]?.toString().split(".")[0] || ""}${item[key]?.toString().split(".")[1]?.length === 1 ? "0" : ""}${item[key]?.toString().split(".")[1] || ""}`
}else{
value = `${item[key]?.toString().split(".")[0] || ""}${item[key]?.toString().split(".")[1] || ""}`
}
}else if(key === "재이용시설_관로(km)" || key?.toString().includes("보증수질_")){
value = item[key]?.toString().trim() || '';
}else{
if(item[key]?.toString().includes("%") && Number(item[key]?.toString().replaceAll("%", ""))){
value = item[key]?.toString().replaceAll("%", "");
}else{
value = item[key];
}
}
if(value === null || value?.toString().trim() === "-" || value?.toString().trim() === ""){
if (typeof object[mappedKey as keyof ruseCaseMngExcelProps] === 'number') {
(object as unknown as Record<string, unknown>)[mappedKey] = '';
} else {
(object as unknown as Record<string, unknown>)[mappedKey] = '';
}
}else if (typeof object[mappedKey as keyof ruseCaseMngExcelProps] === 'number' && Number(value?.toString().replaceAll("%", "").replaceAll(",", ""))) {
(object as unknown as Record<string, unknown>)[mappedKey] = Number(value?.toString().replaceAll("%", "").replaceAll(",", ""));
}else{
(object as unknown as Record<string, unknown>)[mappedKey] = value?.toString().trim() || '';
}
}
}
});
newArray.push(object);
});
console.log("newArray : ", newArray);
insertRuseCaseMng(newArray);
} catch (e) {
console.log(e)
}
});
};
}
return (
<>
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<Title title={"물재이용 사례 등록"}></Title>
</div>
<div className="flex justify-between">
<h3 className="text-2xl font-bold text-gray-900 mt-1 ml-7">
{"물재이용 사례 파일"}
</h3>
<Button className="" type="submit" size={"sm"}>
저장
</Button>
</div>
<div>
<FormField
control={form.control}
name="file"
render={() => (
<FormUi label="">
<FileUpload
files={file ? [file] : []}
setFiles={(files: File[] | null) => {
console.log("files", files)
if (files && files?.length === 1) setFiles(files[0]);
}}
fileTypes=""
maxFiles={1}
></FileUpload>
</FormUi>
)}
/>
</div>
</form>
</Form>
</div>
</>
);
}
Binary file not shown.