Merge commit '374336f0b9dab143a31fa7815b7cc44572498c03' into iipbs/wr/dev
iipbs_wr / build (push) Has been cancelled

This commit is contained in:
leedj312
2025-06-05 09:12:01 +09:00
57 changed files with 5551 additions and 1137 deletions
+409
View File
@@ -0,0 +1,409 @@
"use client";
import { useWrStore } from "@/app/store";
import { formTableName } from "@/components/lib/class/comClass";
import { getCity } from "@/components/lib/func/commom";
import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal";
import { ContentsDiv, Form, FormField, FormUi, ScrollArea, SearchDiv, SearchFormLeft, SearchFormRight, SearchInput, SelectBox, Title, UiTable, type SelectDataType } from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { Button, DatePicker, Input, Label } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
export default function coMatRvw() {
const { code, coRvw, } = useWrStore((state) => state);
const { coRvwHeadList, } = coRvw;
const [fromDate, setFromDate] = useState<Date>(new Date());
const [toDate, setToDate] = useState<Date>(new Date());
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>()
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const schema = z.object({
fcltCpct: z.number().optional(),
});
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
fcltCpct: 0,
}
});
const getSelCity = (val: string | number) => {
const cityValue = String(val); // Ensure val is treated as a string
const data = getCity(cityValue, code.sggCd);
setSelectSgg(data);
setObjSearch({ city: cityValue, sgg: '' });
};
const getSgg = (val: string | number) => {
console.log(val);
setObjSearch({ ...objSearch, sgg: String(val) })
};
const {
fields: [],
append,
remove,
} = useFieldArray({
control: form.control,
name: "",
});
useEffect(() => {
if (fromDate > toDate) {
const newFromDate = new Date(toDate);
newFromDate.setDate(toDate.getDate() - 1);
setFromDate(newFromDate);
}
}, [toDate]);
return (
<>
<ContentsDiv>
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<Title title={"수요매칭검토"} />
<SearchDiv>
<SearchFormLeft>
<div className="col-span-8 flex flex-row">
<SelectBox
className="col-span-2 w-1/3 h-full text-2xl"
label="시도"
selectClass={""}
selectArray={code.cityCd || []}
onSelectValue={getSelCity}
/>
<SelectBox
className="col-span-2 w-1/3 h-full text-2xl"
label="시군구"
selectClass={""}
selectArray={selectSgg || []}
onSelectValue={getSgg}
/>
<div className="col-span-5 w-full">
<div className="flex flex-row items-center">
<Label className="text-xl text-gray-600">수요신청년도</Label>
<div className="flex flex-row">
<DatePicker mode={'single'}
selected={fromDate}
onSelect={(date: Date | undefined) => { if (date) setFromDate(date) }} />
<span>~</span>
<DatePicker mode={'single'}
selected={toDate}
onSelect={(date: Date | undefined) => { if (date) setToDate(date) }} />
</div>
</div>
</div>
<SearchInput
className={"text-2xl"}
label="회사명"
textValue={""}
setText={(e) => {
}}
/>
</div>
</SearchFormLeft>
<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>
</div>
</div>
</SearchFormRight>
</SearchDiv>
</div>
</div>
<div className="grid grid-cols-6 gap-4 ">
<div className="col-span-6">
<ListDiv>
<UiTable
headName={"수요매칭검토현황"}
tableData={[]}
children={undefined}
headlist={coRvwHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
}}
onPageChange={() => { }}
></UiTable>
<hr />
<Form {...form}>
<h3 className="mt-4 ml-2 text-2xl font-medium text-label">표준공사비 산정</h3>
<div className="flex items-center mx-3 mb-3">
<h1 className="">처리시설 표준공사비 산정</h1>
<Button
size={"sm"}
onClick={(e) => { }}
>
계산
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
저장
</Button>
</div>
<div className="grid grid-cols-12 gap-4 ml-4 mr-4">
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="용도">
<Input
placeholder=""
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? ""}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="요구수량(M3/d)">
<Input
placeholder=""
type="number"
step="0.1"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<Label className="">용도에 따른 처리공정<i className="diveicon di-search" /></Label>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value || ""}
className="mt-2"
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="처리시설 용량(요구수량 * 365일)">
<Input
placeholder=""
type="number"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>처리시설용량에 따른 공사비(백만원)</Label>
<Button
size={"sm"}
onClick={(e) => { }}
className="bg-none"
>
공사비 산정표 보기
</Button>
</div>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="number"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>시설부대경비 요율(%)</Label>
<Button
size={"sm"}
onClick={(e) => { }}
>
요율표 보기
</Button>
</div>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="number"
step="0.01"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<Label>총사업비 (백만원)<i className="diveicon di-search" /></Label>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
className="mt-2"
type="number"
step="0.01"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>공급처 선정</Label>
<Button
size={"sm"}
onClick={(e) => { }}
className="bg-none"
>
후보지 선택
</Button>
</div>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? ""}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4 mt-4">
<div>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="공급관로 표준공사비(백만원)">
<Input
placeholder=""
type="text"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="mt-4">
<Label>관로 표준공사비 산정 프로그램</Label>
<Button
size={"sm"}
onClick={(e) => { }}
className="bg-none"
>
다운로드
</Button>
</div>
<div className="col-span-4 mt-4">
<FormField
control={form.control}
name=""
render={({ field }) => (
<Label className="text-xl">처리시설 + 공급관로 총사업비(백만원)
<FormUi label="">
<Input
placeholder=""
type="text"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
readOnly={true}
value={field.value ?? 0}
/>
</FormUi>
</Label>
)}
/>
</div>
</div>
</div>
</Form>
</ListDiv>
</div>
</div>
</ContentsDiv >
</>
)
}
@@ -0,0 +1,367 @@
"use client";
import { useWrStore } from "@/app/store";
import { formTableName } from "@/components/lib/class/comClass";
import { getCity } from "@/components/lib/func/commom";
import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal";
import { ContentsDiv, Form, FormField, FormUi, ScrollArea, SearchDiv, SearchFormLeft, SearchFormRight, SearchInput, SelectBox, Title, UiTable, type SelectDataType } from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { Button, DatePicker, Input, Label } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
export default function ruseRvwHstry() {
const { code, ruseHstry, } = useWrStore((state) => state);
const { ruseHstryHeadList, } = ruseHstry;
const [fromDate, setFromDate] = useState<Date>(new Date());
const [toDate, setToDate] = useState<Date>(new Date());
const schema = z.object({});
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>()
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const getSelCity = (val: string | number) => {
const cityValue = String(val); // Ensure val is treated as a string
const data = getCity(cityValue, code.sggCd);
setSelectSgg(data);
setObjSearch({ city: cityValue, sgg: '' });
};
const getSgg = (val: string | number) => {
console.log(val);
setObjSearch({ ...objSearch, sgg: String(val) })
};
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
fcltCpct: 0,
}
});
const {
fields: [],
append,
remove,
} = useFieldArray({
control: form.control,
name: "",
});
return (
<>
<ContentsDiv>
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<Title title={"재이용검토이력"} />
<SearchDiv>
<SearchFormLeft>
<div className="col-span-8 flex flex-row">
<SelectBox
className="col-span-2 w-1/3 h-full text-2xl"
label="시도"
selectClass={""}
selectArray={code.cityCd || []}
onSelectValue={getSelCity}
>
{""}
</SelectBox>
<SelectBox
className="col-span-2 w-1/3 h-full text-2xl"
label="시군구"
selectClass={""}
selectArray={selectSgg || []}
onSelectValue={getSgg}
>
{""}
</SelectBox>
<div className="col-span-5 w-full">
<div className="flex flex-row items-center">
<Label className="text-xl text-gray-600">수요신청년도</Label>
<div className="flex flex-row">
<DatePicker mode={'single'}
className="w-full"
selected={fromDate}
onSelect={(date: Date | undefined) => { if (date) setFromDate(date) }} />
<span>~</span>
<DatePicker mode={'single'}
selected={toDate}
onSelect={(date: Date | undefined) => { if (date) setToDate(date) }} />
</div>
</div>
</div>
<SearchInput
className={"text-2xl"}
label="시설명"
textValue={""}
setText={(e) => {
}}
/>
<SearchInput
className={"text-2xl"}
label="회사명"
textValue={""}
setText={(e) => {
}}
/>
</div>
</SearchFormLeft>
<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>
<Button
size={"sm"}
onClick={(e) => { }}
>
검토완료
</Button>
</div>
</div>
</SearchFormRight>
</SearchDiv>
</div>
</div>
<div className="grid grid-cols-6 gap-4 ">
<div className="col-span-6">
<ListDiv>
<UiTable
headName={"재이용검토현황"}
tableData={[]}
children={undefined}
headlist={ruseHstryHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
}}
onPageChange={() => { }}
></UiTable>
<hr />
<Form {...form}>
<h3 className="mt-4 ml-2 text-2xl font-medium text-label">수요신청 상세현황</h3>
<h1 className="mt-1 ml-3">표준공사비 산정</h1>
<div className="flex items-center mx-3 mb-3">
<h1 className="">처리시설 표준공사비 산정</h1>
<Button
size={"sm"}
onClick={(e) => { }}
>
재계산
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
저장
</Button>
</div>
<div className="grid grid-cols-12 gap-4 ml-4 mr-4">
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="용도">
<Input
placeholder=""
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? ""}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="요구수량(M3/d)">
<Input
placeholder=""
type="number"
step="0.1"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<Label className="">용도에 따른 처리공정<i className="diveicon di-search" /></Label>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value || ""}
className="mt-2"
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="처리시설 용량(요구수량 * 365일)">
<Input
placeholder=""
type="number"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>처리시설용량에 따른 공사비(백만원)</Label>
<Button
size={"sm"}
onClick={(e) => { }}
className="bg-none"
>
공사비 산정표 보기
</Button>
</div>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="number"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>시설부대경비 요율(%)</Label>
<Button
size={"sm"}
onClick={(e) => { }}
>
요율표 보기
</Button>
</div>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="number"
step="0.01"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<Label>총사업비 (백만원)<i className="diveicon di-search" /></Label>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
className="mt-2"
type="number"
step="0.01"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
<div className="flex items-center justify-between">
<Label>공급처 선정</Label>
<Button
size={"sm"}
onClick={(e) => { }}
className="bg-none"
>
후보지 선택
</Button>
</div>
<FormField
control={form.control}
name=""
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? ""}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4 mt-4">
<div className="mt-4">
<Label>관로 표준공사비 산정</Label>
</div>
</div>
</div>
</Form>
</ListDiv>
</div>
</div>
</ContentsDiv >
</>
)
}
+24
View File
@@ -41,6 +41,13 @@ export default function Home() {
<div>
<h2 className="text-xl font-semibold mb-4">지도점검 결과</h2>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-2">
<Button className="w-full">
<Link href="ruseFcltMng/prfmncReg/pbnstPrfmncReg">
하수재이용실적 등록
</Link>
</Button>
</div>
<div className="col-span-2">
<Button className="w-full">
<Link href="ruseFcltMng/prfmncReg/chkgdnReg">
@@ -112,6 +119,23 @@ export default function Home() {
</div>
</div>
</div>
{/* 수요처 매칭 */}
<div>
<h2 className="text-xl font-semibold mb-4">수요처 매칭</h2>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-2">
<Button className="w-full">
<Link href="matMng/matDmd/coMatRvw">수요처 매칭 검토</Link>
</Button>
</div>
<div className="col-span-2">
<Button className="w-full">
<Link href="matMng/matDmd/ruseRvwHstry">재이용검토이력</Link>
</Button>
</div>
</div>
</div>
</div>
</>
);
+120 -43
View File
@@ -6,6 +6,7 @@ import {
SearchFormLeft,
SearchInput,
Title,
UiModal,
UiTable,
} from "@common/business_components";
import { Button, Input } from "@common/components";
@@ -14,46 +15,91 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components";
import { useEffect, useState } from "react";
import ListDiv from "@common/business_components/ui/list_div";
import { tableData } from "@/components/lib/func/commom";
import type { customPbnst, ruseFcltType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import type {
customPbnst,
fcBizSchema,
fcBizType,
pubHasuType,
ruseFcltType,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { PbnstMngForm } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm";
import { PbnstBuseForm } from "@/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm";
import { z } from "zod";
import { PbnstRegModal } from "@/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal";
import { Toaster } from "react-hot-toast";
import { ToastFormUi } from "@/components/ui/toast/toastFromUi";
import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg";
export default function PublicReuseFaciltyInsert() {
const { code, pbnst, pubHasuInit } = useWrStore((state) => state);
const { pubFacHeadList,pbnstHasuRes } = pbnst;
const {pbnstRes} = pbnst
const {
code,
pbnst,
searchFclts,
pubHasuInit,
pbnstRegClick,
getSwtrmtFcltNm,
pbnstMngData,
pbnstRuseFcltUpdate,
pbnstRuseBizUpdate,
} = useWrStore((state) => state);
const { pubFacHeadList, pbnstHasuRes, pbnstRes } = pbnst;
const [currentTab, setCurrentTab] = useState<string>("mng");
const [fcltMng, setFcltMng] = useState<ruseFcltType[]>();
const [pbnstBiz, setPbnstBiz] = useState<fcBizType[]>();
const [isRuseReg, setIsRuseReg] = useState<{
isModal: boolean;
data: pubHasuType;
}>({ isModal: false, data: {} as pubHasuType });
const [searchTxt, setSearchTxt] = useState<string>();
const [bdstTable, setbdstTable] = useState<customPbnst[]>();
const [bdstTable, setbdstTable] = useState<pubHasuType[]>();
const [selectBdst, setSelectBdst] = useState<customPbnst | null>(null);
useEffect(() => {
useEffect(() => {
if (!pbnst.isInit) {
pubHasuInit(undefined);
pubHasuInit("1", "11");
}
}, [pbnst.isInit, pubHasuInit]);
useEffect(() => {
if (code.sggCd.length > 0) {
setbdstTable(tableData(pbnstHasuRes, { code }));
const saveRuseData = async ()=>{
const data = writeRgtrAndMdfr(pbnst.pbnstMngRegData)
let bizs =[] as fcBizType[]
data.forEach((fclt) =>{
bizs = [
...bizs,
...fclt.fcBizs
]
})
await pbnstRuseFcltUpdate(data)
console.log('bizs ', bizs)
await pbnstRuseBizUpdate(bizs)
console.log(' data ', data)
}
useEffect(() => {
if(code.sggCd.length){
if(searchFclts){
setbdstTable(tableData(searchFclts, { code }));
}else if (code.sggCd.length > 0 && pbnstHasuRes.length > 0) {
setbdstTable(tableData(pbnstHasuRes, { code }));
}
}
}, [code, pbnstHasuRes]);
}, [code, pbnstHasuRes,searchFclts]);
const onSelectValue = (val: string) => {
console.log(val);
};
const cellClick = (val: number | undefined) => {
if (val === undefined) return;
setFcltMng([]);
};
const resetContents = () => {
setFcltMng([]); // 빈 배열로 설정하여 신규 모드로 전환
const selectRow = pbnstHasuRes?.[val];
if (selectRow) {
setIsRuseReg({ ...isRuseReg, data: selectRow });
pbnstRegClick(selectRow);
}
};
const handleTabChange = (value: string) => {
@@ -63,7 +109,7 @@ useEffect(() => {
return (
<>
<ContentsDiv>
<div className="grid grid-cols-4 gap-4">
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<Title title={"하수재이용 시설 등록"} />
<SearchDiv>
@@ -81,6 +127,9 @@ useEffect(() => {
className=""
size={"sm"}
onClick={(e) => {
searchTxt && searchTxt?.length > 0
? getSwtrmtFcltNm(searchTxt)
: pubHasuInit("1", "11")
}}
>
조회
@@ -88,7 +137,15 @@ useEffect(() => {
<Button
size={"sm"}
className=""
onClick={() =>{}}
onClick={() => {
pbnstMngData([] as customPbnst[]);
isRuseReg.data.bscFctCd
? setIsRuseReg({ ...isRuseReg, isModal: true })
: ToastFormUi({
body: "하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요",
title: "재이용시설 등록",
});
}}
>
신규등록
</Button>
@@ -102,37 +159,57 @@ useEffect(() => {
<div className="col-span-4 h-full min-h-0">
<ListDiv className="min-h-0">
<ScrollArea className="h-[60vh]">
<UiTable
<UiTable
tableData={bdstTable || []}
headlist={pubFacHeadList}
pageSize={100}
total={10}
headName={"공공 하수처리시설 등록"}
cellClick={cellClick}
onPageChange={(page: number) => { } } children={undefined} />
</ScrollArea>
onPageChange={(page: number) => {}}
children={undefined}
/>
</ScrollArea>
</ListDiv>
</div>
<ListDiv className="col-span-2 pr-6 ">
<ScrollArea className="h-[60vh]">
<Tabs defaultValue="mng" onValueChange={handleTabChange}>
<TabsList className="">
<TabsTrigger className="!text-xl" value="mng">시설정보</TabsTrigger>
<TabsTrigger className="!text-xl" value="buse">차수별정보</TabsTrigger>
<ScrollArea className="h-[60vh]">
<Tabs defaultValue="mng" onValueChange={handleTabChange}>
<TabsList className="">
<TabsTrigger className="!text-xl" value="mng">
시설정보
</TabsTrigger>
<TabsTrigger className="!text-xl" value="buse">
차수별정보
</TabsTrigger>
<Button size={"sm"} onClick={ saveRuseData}className="!justify-end">
저장
</Button>
</TabsList>
<TabsContent className="max-h-full" value="mng">
<PbnstMngForm data={fcltMng || []} />
</TabsContent>
<TabsContent className="max-h-full" value="buse">
<PbnstBuseForm />
</TabsContent>
</Tabs>
</ScrollArea>
<TabsContent className="max-h-full" value="mng">
<PbnstMngForm formType="update" />
</TabsContent>
<TabsContent className="max-h-full" value="buse">
<PbnstBuseForm formType="update" />
</TabsContent>
</Tabs>
</ScrollArea>
</ListDiv>
<UiModal
body={
<PbnstRegModal
data={isRuseReg.data || ({} as customPbnst)}
></PbnstRegModal>
}
bodyClass="w-[68vw] max-w-none h-[90vh]"
open={isRuseReg.isModal}
onOpenChange={(open) => {
setIsRuseReg({ data: {} as pubHasuType, isModal: open });
}}
/>
</div>
<Toaster position="top-center" />
</ContentsDiv>
</>
);
}
@@ -14,23 +14,28 @@ import { Button } from "@common/components/ui";
import HeadChModal from "@/components/ui/modal/sttn/headChModal/headChModal";
import { useCallback, useEffect, useRef, useState } from "react";
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg";
import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { excelDownload, getCityOrSgg } from "@/components/lib/func/commom";
export default function PbnstSttn() {
const { code, pbnstSttn } = useWrStore(
(state) => state
);
const { code, pbnstSttn, pbnstSttnSearch } = useWrStore((state) => state);
const ref = useRef<ReturnType>({} as ReturnType);
const codeData = getCityOrSgg(ref);
const [pageNation, setPageNation] = useState(1);
const [isChHead, setIsChHead] = useState(false);
const [bdstTable, setBdstTable] = useState<customPbnst[]>();
// const nodeList = headMakeprfmnc(pbnstSttn.pbnSttHeadList, pbnstSttn.pbnSttHeadNm);
const excelClick = useCallback(() => {
// if (codeData) pbnstSttnSearch(1, "10000", codeData);
}, []);
excelDownload(
[[]],
"하수재이용시설 현황",
"sample_pbnstSttn.xlsx",
"A1:U2"
);
}, [pbnstSttnSearch, codeData]);
return (
<>
@@ -67,7 +72,7 @@ export default function PbnstSttn() {
// pbnstSttnSearch(1, "20", con);
}}
excelClick={excelClick}
initClick={function (): void {}}
initClick={function (): void { }}
btnClass={btnClass}
ref={ref}
/>
@@ -85,7 +90,7 @@ export default function PbnstSttn() {
headlist={pbnstSttn.pbnstSttHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined,headId:string)=>{} }
cellClick={(index: number | undefined, headId: string) => { }}
onPageChange={setPageNation}
></UiTable>
</ListDiv>
@@ -0,0 +1,155 @@
"use client";
import { useWrStore } from "@/app/store";
import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { excelDownload, yearOptions } from "@/components/lib/func/commom";
import PbnPrfExcelModal from "@/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal";
import {
ContentsDiv,
Title,
SearchDiv,
SearchFormLeft,
SelectBox,
SearchInput,
SearchFormRight,
ScrollArea,
UiTable,
UiModal,
} from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { Button, Label } from "@common/components/ui";
import { useState, useEffect, useCallback } from "react";
import { set } from "react-hook-form";
import { Toaster } from "react-hot-toast";
export default function prfmncPrfmncReg() {
const { code, prfmnc, prfmncHasuInit, } = useWrStore(
(state) => state
);
const { pbnstPrfmncHeadList, monthHeadList, prfmncRes } = prfmnc;
const [bdstTable, setbdstTable] = useState<pubHasuType[]>();
const [searchTxt, setSearchTxt] = useState<string>();
const [isExcelOpen, setIsExcelOPen] = useState<boolean>(false);
const [searchYear, setSearchYear] = useState<string>(
String(new Date().getFullYear())
);
const excelClick = useCallback(() => {
console.log("dd")
excelDownload(
[[]],
"하수재이용실적",
"sample_pbnstPrfmncReg.xlsx",
"A1:O1"
);
}, [prfmnc.excelList])
useEffect(() => {
if (!prfmnc.isInit) {
prfmncHasuInit("1", "11");
}
}, [prfmnc.isInit, prfmncHasuInit]);
useEffect(() => {
setbdstTable(prfmncRes)
console.log("prfmnc.prfmncRes", prfmncRes)
}, [prfmncRes]);
return (
<>
<ContentsDiv>
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<Title title={"하수재이용 시설 등록"} />
<SearchDiv>
<SearchFormLeft>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-48 mt-2 text-xl text-gray-600">
{"실적등록년도"}
</Label>
<SelectBox
className="w-full h-full text-2xl"
label=""
selectClass={""}
selectArray={yearOptions}
value={searchYear}
onSelectValue={(val: string | number) =>
setSearchYear(String(val))
}
/>
</div>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-28 mt-2 text-xl text-gray-600">
{"시설명"}
</Label>
<SearchInput
className={"w-full text-2xl"}
label=""
textValue={searchTxt || ""}
setText={(e) => {
setSearchTxt(e);
}}
/>
</div>
</SearchFormLeft>
<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>
<Button size={"sm"} onClick={() => {}}>
저장
</Button>
<Button size={"sm"} onClick={() => {setIsExcelOPen(true)}}>
파일등록
</Button>
<Button color="green" size={"sm"} onClick={excelClick}>
양식받기
</Button>
</div>
</div>
</SearchFormRight>
</SearchDiv>
</div>
</div>
<div className="grid grid-cols-12 gap-4 h-full">
<div className="col-span-4 h-full min-h-0">
<ListDiv className="min-h-0">
<ScrollArea className="h-[60vh]">
<UiTable
tableData={bdstTable || []}
headlist={pbnstPrfmncHeadList}
pageSize={100}
total={10}
headName={"하수재이용시설 목록"}
cellClick={() => {}}
onPageChange={(page: number) => {}}
children={undefined}
/>
</ScrollArea>
</ListDiv>
</div>
<ListDiv className="col-span-8 pr-6 ">
<ScrollArea className="h-[60vh]">
<UiTable
tableData={[]}
headlist={monthHeadList}
pageSize={100}
total={10}
headName={"하수처리수재이용 실적(m3) 등록결과"}
cellClick={() => {}}
onPageChange={() => {}}
children={undefined}
/>
</ScrollArea>
</ListDiv>
</div>
</ContentsDiv>
<UiModal bodyClass={""} body={<PbnPrfExcelModal></PbnPrfExcelModal>} open={isExcelOpen} onOpenChange={() => {setIsExcelOPen(!isExcelOpen)}}></UiModal>
<Toaster position="top-center" />
</>
);
}
+43
View File
@@ -89,6 +89,43 @@ const chBuCd = [
{ val: "개축", text: "개축" },
{ val: "재축", text: "재축" },
]
const dmdUszCd = [
{ val: "조경용수", text: "조경용수" },
{ val: "청소화장실용수", text: "청소화장실용수" },
{ val: "농업용수", text: "농업용수" },
{ val: "공업용수", text: "공업용수" },
{ val: "친수용수", text: "친수용수" },
{ val: "기타", text: "기타" },
]
const pplQulity = [
{ val: "도복장강관", text: "도복장강관" },
{ val: "PE관", text: "PE관" },
{ val: "스테인레스관", text: "스테인레스관" },
{ val: "덕타일주철관", text: "덕타일주철관" },
{ val: "유리섬유복합관", text: "유리섬유복합관" },
{ val: "기타", text: "기타" },
]
const pplLenght = [
{ val: "D100", text: "D100" },
{ val: "D200", text: "D200" },
{ val: "D250", text: "D250" },
{ val: "D300", text: "D300" },
{ val: "D350", text: "D350" },
{ val: "D400", text: "D400" },
{ val: "D450", text: "D450" },
{ val: "D500", text: "D500" },
{ val: "D700", text: "D700" },
{ val: "D900", text: "D900" },
{ val: "D1000​", text: "D1000​" },
]
export const buildingType = {
RST:"RST",
RNB:"RNB",
@@ -109,6 +146,9 @@ export type comCodeState = {
lclgvrnmntCd: EnvPartSelectDataType[];
orgwtKndCd: SelectDataType[];
yn: SelectDataType[];
pplQulity: SelectDataType[];
pplLenght: SelectDataType[];
dmdUszCd:SelectDataType[]
buildingType: {
RST:string,
RNB:string,
@@ -247,7 +287,10 @@ export const comCodeStateExport: comCodeState = {
mdmbsnCd: [],
lgbsnCd: [],
lclgvrnmntCd: [],
pplQulity:pplQulity,
pplLenght:pplLenght,
yn: yn,
dmdUszCd:dmdUszCd,
buildingType:buildingType,
},
};
@@ -0,0 +1,52 @@
import { fetchType, getFetch, postFectch } from "@/components/lib/api";
import type { pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type";
import type { DemandCompType } from "@common/business_components/ui";
export type comFcltState = {
searchFclts: pubHasuType;
demandCompList: DemandCompType[];
};
export type comFcltActions = {
getSwtrmtFcltNm: (searchNm: string) => void;
getDemandCompList :(searchNm: string,cnt:number) => void
regDemandCompany:(data:DemandCompType ) => void
};
export const searchActions: (set: any) => comFcltActions = (set: any) => {
return {
getSwtrmtFcltNm: async (searchNm: string) => {
const fcltNm = await getFetch(
"common",
`/prcsFclt/info/1/20/${searchNm}`
);
const fcltNmCd: pubHasuType[] = fcltNm.map((item: any) => ({
bscFctCd: item.bscFctCd,
sido: item.sido,
gugun: item.gugun,
bscFctNm: item.bscFctNm,
addr: item.addr,
fctCpc: item.fctCpc,
}));
return set((state: comFcltState) => ({
...state,
searchFclts: fcltNmCd,
}));
},
getDemandCompList: async (searchNm: string ,cnt:number) => {
const coList = await getFetch(
fetchType.WR,
`/reuseFc/request/${searchNm}/${cnt}`
);
return set((state: comFcltState) => ({
...state,
demandCompList: coList,
}));
},regDemandCompany: async (data:DemandCompType)=>{
try{
const response = await postFectch('/reuseFc/request',data,fetchType.WR )
}catch(err){
console.log(err)
}
}
};
};
@@ -0,0 +1,109 @@
import { z } from "zod";
import type { CommonTableProps } from "../../common/type/headder";
export const sttnHeadListType = z.array(
z.object({
id: z.string(),
title: z.string(),
visible: z.boolean(),
})
);
export const coRvwHeadListExport: z.infer<typeof sttnHeadListType> = [
{
id: "ctpv",
title: "시도",
visible: true,
},
{
id: "sgg",
title: "시군구",
visible: true,
},
{
id: "coNm",
title: "회사명",
visible: true,
},
{
id: "addr",
title: "공장주소",
visible: true,
},
{
id: "telNo",
title: "전화번호",
visible: true,
},
{
id: "regDt", //바꾸기
title: "신청일자",
visible: true,
},
{
id: "state", //바꾸기
title: "진행상태",
visible: true,
},
];
// id는 다확인해야함
export const coRvwNodeListExport: CommonTableProps[] = [
{
id: "ctpv",
title: "시도",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "sgg",
title: "시군구",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "coNm",
title: "회사명",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "addr",
title: "공장주소",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},{
id: "telNo",
title: "전화번호",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "regDt", //바꾸기
title: "신청일자",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "state", //바꾸기
title: "진행상태",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
];
@@ -0,0 +1,34 @@
import type { z } from "zod";
import type { sttnHeadListType } from "../coSttn/headList";
import { coRvwHeadListExport, coRvwNodeListExport } from "./headList";
import type { CommonTableProps } from "../../common/type/headder";
const coRvwHeadList = coRvwHeadListExport;
const coRvwNodeList = coRvwNodeListExport;
export type coRvwState = {
coRvw: {
isInit: boolean;
coRvwHeadList: z.infer<typeof sttnHeadListType>;
coRvwNodeList: CommonTableProps[];
};
};
export const coRvwStateExport: coRvwState = {
coRvw: {
isInit: false,
coRvwHeadList: coRvwHeadList,
coRvwNodeList: coRvwNodeList,
}
}
export type coRvwType = coRvwState & coRvwActions;
type coRvwActions = {
};
export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => {
return {
}
}
@@ -0,0 +1,185 @@
import { z } from "zod";
import type { CommonTableProps } from "../../common/type/headder";
export const sttnHeadListType = z.array(
z.object({
id: z.string(),
title: z.string(),
visible: z.boolean(),
})
);
export const ruseHstryHeadListExport: z.infer<typeof sttnHeadListType> = [
{
id: "ctpv",
title: "시도",
visible: true,
},
{
id: "sgg",
title: "시군구",
visible: true,
},
{
id: "coNm",
title: "회사명",
visible: true,
},
{
id: "addr",
title: "공장주소",
visible: true,
},
{
id: "telNo",
title: "전화번호",
visible: true,
},
{
id: "regDt", //바꾸기
title: "신청일자",
visible: true,
},
{
id: "rvwDt", //바꾸기
title: "검토일자",
visible: true,
},
{
id: "matDt", //바꾸기
title: "매칭 완료일자",
visible: true,
},
{
id: "gong", //바꾸기
title: "공급처",
visible: true,
},
{
id: "dmndQty", //바꾸기
title: "요구수량(M3/d)",
visible: true,
},
{
id: "useUsgCd", //바꾸기
title: "사용용도",
visible: true,
},
{
id: "state", //바꾸기
title: "진행상태",
visible: true,
},
{
id: "matYn", //바꾸기
title: "매칭완료 여부",
visible: true,
},
];
// id는 다확인해야함
export const ruseHstryNodeListExport: CommonTableProps[] = [
{
id: "ctpv",
title: "시도",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "sgg",
title: "시군구",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "coNm",
title: "회사명",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "addr",
title: "공장주소",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},{
id: "telNo",
title: "전화번호",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "regDt", //바꾸기
title: "신청일자",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "rvwDt", //바꾸기
title: "검토일자",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},{
id: "matDt", //바꾸기
title: "매칭 완료일자",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},{
id: "gong", //바꾸기
title: "공급처",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "dmndQty", //바꾸기
title: "요구수량(M3/d)",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "useUsgCd", //바꾸기
title: "사용용도",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "state", //바꾸기
title: "진행상태",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
{
id: "matYn", //바꾸기
title: "매칭완료 여부",
visible: true,
row: 1,
rowSpan: 1,
colSpan: 1,
},
];
@@ -0,0 +1,34 @@
import type { z } from "zod";
import type { sttnHeadListType } from "../coSttn/headList";
import type { CommonTableProps } from "../../common/type/headder";
import { ruseHstryHeadListExport, ruseHstryNodeListExport } from "./headList";
const ruseHstryHeadList = ruseHstryHeadListExport;
const ruseHstryNodeList = ruseHstryNodeListExport;
export type ruseHstryState = {
ruseHstry: {
isInit: boolean;
ruseHstryHeadList: z.infer<typeof sttnHeadListType>;
ruseHstryNodeList: CommonTableProps[];
};
};
export const ruseHstryStateExport: ruseHstryState = {
ruseHstry: {
isInit: false,
ruseHstryHeadList: ruseHstryHeadList,
ruseHstryNodeList: ruseHstryNodeList,
}
}
export type ruseHstryType = ruseHstryState & ruseHstryActions;
type ruseHstryActions = {
};
export const ruseHstryActionsExport: (set: any) => ruseHstryActions = (set: any) => {
return {
}
}
@@ -66,7 +66,11 @@ export const rswtChkgdnStateExport: rswtChkgdnState = {
rswtChkHeadList: rswtChkHeadList,
rswtDetailHeadList: rswtDetailHeadList,
rswtHeadNm: rswtHeadNm,
excelList: [{ data: [[]], headerNum: 0, headerRange: "A1:V3" }],
excelList: [{
data: [[]], headerNum: 0, headerRange: "A1:V3",
sheetName: "",
startCol: 0
}],
rswtChkRes: [],
rswtChkPolGuide: [],
isInit: false,
@@ -0,0 +1,43 @@
import { TEST_UUID } from "../../ruseFcltMng/fcltReg/rainReg";
const nameMap = {
"세척수": "wshwtr",
"냉각수": "coolUswtr",
"청소수": "cleanUswtr",
"식수대": "drwtUswtr",
"희석용수": "dltnUswtr",
"중수도": "ithlRswtr",
"기타": "ithlEtcUswtr",
"청소화장실용수": "cleanToilUswtr",
"세척살수용수": "resn",
"조경용수": "lndwtr",
"친수용수": "hydUswtr",
"지하수충전": "ugwtChr",
"농업용수": "farmUswtr",
"하천등유지용수": "rvrKeepUswtr",
"공업용수": "indstUswtr"
};
export const excelToPbnPrfData = (excelData: any[], year: string) => {
// 월별 데이터 하나로 병합
const merged = {} as any;
excelData.forEach((item: any) => {
const key = nameMap[item["용수명"]];
if (!key) return;
for (let i = 1; i <= 12; i++) {
const month = i.toString().padStart(2, "0");
const ym = year + month;
if (!merged[ym]) merged[ym] = { ym };
merged[ym][key] = Number(item[`${i}월`] ?? 0);
}
});
const pbnPrfData = Object.values(merged);
console.log(pbnPrfData);
return pbnPrfData;
};
@@ -0,0 +1,34 @@
import { z } from "zod";
import type { TableUpperProps } from "../../ruseFcltMng/fcltReg/rainReg";
export const pbnPrfRegExcelZod = z.object({
ym: z.string().default(""),
bscFctNm: z.string().default(""),
pbnstRuseFcltNm: z.string().default(""),
file: z.instanceof(File).optional(),
});
export const pbnstPrfmncHeadListExport: TableUpperProps[] = [
{ id: "sido", title: "시도" },
{ id: "gugun", title: "시군구" },
{ id: "bscFctNm", title: "시설명" },
{ id: "sisul", title: "재이용시설명" },
{ id: "sisulCd", title: "재이용시설코드" },
];
export const monthHeadListExport: TableUpperProps[] = [
{ id: "gubun", title: "구분" },
{ id: "total", title: "계" },
{ id: "mon1", title: "1월" },
{ id: "mon2", title: "2월" },
{ id: "mon3", title: "3월" },
{ id: "mon4", title: "4월" },
{ id: "mon5", title: "5월" },
{ id: "mon6", title: "6월" },
{ id: "mon7", title: "7월" },
{ id: "mon8", title: "8월" },
{ id: "mon9", title: "9월" },
{ id: "mon10", title: "10월" },
{ id: "mon11", title: "11월" },
{ id: "mon12", title: "12월" },
];
@@ -0,0 +1,79 @@
import { fetchType, getFetch, postFectch } from "@/components/lib/api";
import type { TableUpperProps } from "../../ruseFcltMng/fcltReg/rainReg";
import {
pbnstPrfmncHeadListExport,
monthHeadListExport,
} from "./headList";
import type { pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type";
const pbnstPrfmncHeadList = pbnstPrfmncHeadListExport;
const monthHeadList = monthHeadListExport;
export type pbnstPrfmncState = {
prfmnc: {
isInit: boolean;
pbnstPrfmncHeadList: TableUpperProps[];
monthHeadList: TableUpperProps[];
prfmncRes: pubHasuType[];
excelList: Number[];
}
}
export const pbnstPrfmncStateExport: pbnstPrfmncState = {
prfmnc: {
isInit: false,
pbnstPrfmncHeadList: pbnstPrfmncHeadList,
monthHeadList: monthHeadList,
prfmncRes: [],
excelList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
}
}
export type pbnstPrfmncType = pbnstPrfmncState & pbnstPrfmncActions;
type pbnstPrfmncActions = {
prfmncHasuInit: (page: string, sggOrCity: string) => void;
}
const checkWrRegList = async () => {
const wrRegList = await getFetch("wr", `/reuseFc/pubFc`);
return wrRegList;
};
const hasuListFetch = async (page: string, sggOrCity: string) => {
const checkWrRegListRes = await checkWrRegList();
const rainRes = await postFectch(
`/prcsFclt/ruseInfo/${page ? page : 1}/10/A/${sggOrCity ? sggOrCity : "11"}/0/1/SW`,
checkWrRegListRes,
fetchType.COMMON
);
const hasuList = rainRes.data.map((item: any) => {
return {
...item,
ruse: item.ruse === false ? "N" : "Y",
};
});
return hasuList;
};
export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = (set: any) => {
return {
prfmncHasuInit: async (page: string, sggOrCity: string) => {
try {
const prfmncRes = await hasuListFetch(page, sggOrCity);
console.log("prfmncRes", prfmncRes)
return set((state: pbnstPrfmncState) => ({
...state,
prfmnc: {
...state.prfmnc,
prfmncRes: prfmncRes,
isInit: false,
},
}));
} catch (errorHasu) {
console.log(errorHasu);
}
},
}
}
@@ -1,10 +1,6 @@
import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList";
import type { TableUpperProps } from "../rainReg";
export const pubFacHeadListExport: TableUpperProps[] = [
{ id: "sido", title: "시도" },
{ id: "gugun", title: "시군구" },
@@ -12,65 +8,128 @@ export const pubFacHeadListExport: TableUpperProps[] = [
{ id: "addr", title: "위치" },
{ id: "efflWqltStndrdNm", title: "지역구분" },
{ id: "fctCpc", title: "시설용량" },
{ id: "yn", title: "재이용여부" },
{ id: "authoDay", title: "등록여부" },
{ id: "ruse", title: "재이용여부" },
{ id: "rswtrYn", title: "등록여부" },
];
export const pubMngUpHeadListExport: TableUpperProps[] = [
{ id: "mng", title: "운영방식" },
{ id: "subCo", title: "대행업체" },
{ id: "startDay", title: "시작일", upName: "upChangeRe", upSequnce: 1 },
{ id: "endDay", title: "종료일", upName: "upChangeRe", upSequnce: 2 },
export const pbnstMngUpHeadListExport: TableUpperProps[] = [
{ id: "choose", title: "선택", type: "checkbox" },
{ id: "operMthd", title: "운영방식", edit: true },
{ id: "rprtvBzenty", title: "대행업체", edit: true },
{
id: "bgngDt",
title: "시작일",
upName: "upChangeRe",
upSequnce: 1,
type: "date",
},
{
id: "endDt",
title: "종료일",
upName: "upChangeRe",
upSequnce: 2,
type: "date",
},
];
const date = new Date();
export const pubMngNmExport: chHeadType[] = [
export const pplInstlsHeadListExport: TableUpperProps[] = [
{ id: "choose", title: "선택", type: "checkbox" },
{ id: "ppMqlt", title: "관재질", type: "select", selectData: "pplQulity" },
{ id: "pdmt", title: "관경", type: "select", selectData: "pplLenght" },
{ id: "cpct", title: "연장", edit: true, type: "number" },
];
export const pbnstDemandHeadListExport: TableUpperProps[] = [
{ id: "dmdCoNm", title: "수요처명" },
{ id: "daddr", title: "위치(주소)" },
{ id: "useUsgCd", title: "사용용도" },
{ id: "splyBgngDay", title: "공급시작일" },
{ id: "splyEndDay", title: "공급종료일" },
{ id: "splyCpct", title: "공급량(m³/일)" },
{ id: "splyFmYon", title: "공급유량계 유무" },
];
export const pbnstOrgwtHeadListRxport: TableUpperProps[] = [
{ id: "ruseUtztnFcltNm", title: "운영방식" },
{ id: "orgwtWtritQnt", title: "원수취수량" },
{ id: "tmsYn", title: "하수처리량TMS 전후" },
{ id: "dschgFmYn", title: "하수처리장 방류 유량계 전·후​" },
{ id: "dtlPstnExpln", title: "상세위치 설명" },
];
export const fcOrgwtsHeadListExport: TableUpperProps[] = [
{ id: "choose", title: "선택", type: "checkbox" },
{ id: "sido", title: "시도" },
{ id: "gugun", title: "시군구" },
{ id: "bscFctNm", title: "시설명" },
{ id: "addr", title: "위치" },
];
export const pbnstMngNmExport: chHeadType[] = [
{ headNm: "upChangeRe", text: "운영대행기간", length: 2 },
];
export type formDetailType ={
id:string,
lable:string
type?:string
}
export type formDetailType = {
id: string;
lable: string;
type?: string;
};
export const wasteWaterFcltINfo: formDetailType[] = [
{ id: "wwtEmsnFcltPrmsnYn", lable: "폐수배출물신고 허가여부", type: "selectYn" },
{ id: "tmsInstlYn", lable: "Tms설치여부", type: "selectYn" },
{ id: "pbnstDrngInflwExclYn", lable: "공공하수도유입제외 허가여부", type: "selectYn" },
];
export const wasteWaterFcltINfo:formDetailType[]=[
{id:'wwtEmsnFcltPrmsnYn' , lable:'폐수배출물신고 허가여부'},
{id:'tmsInstlYn' , lable:'Tms설치여부'},
{id:'pbnstDrngInflwExclYn' , lable:'공공하수도유입제외 허가여부'},
]
export const concentratedWaterInfo: formDetailType[] = [
{ id: "erctWtrOcrnCpct", lable: "눙축수 발생량(m³/일)", type: "number" },
{ id: "erctUswtrLinkSpt", lable: "눙축수연계지점" },
{ id: "erctUswtrFcltCpct", lable: "시설용량(m³/일)", type: "number" },
{ id: "erctUswtrPrcsMeth", lable: "눙축수처리방식" },
{ id: "erctUswtrPrcsMsrcCd", lable: "눙축수처리공법" },
];
export const concentratedWaterInfo:formDetailType[]=[
{id:'erctWtrOcrnCpct' , lable:'눙축수 발생량(m³/일)'},
{id:'erctUswtrLinkSpt' , lable:'눙축수연계지점'},
{id:'erctUswtrFcltCpct' , lable:'시설용량(m³/일)'},
{id:'erctUswtrPrcsMeth' , lable:'눙축수처리방식'},
{id:'erctUswtrPrcsMsrcCd' , lable:'눙축수처리공법'},
]
export const ruseFcltinfo: formDetailType[] = [
{ id: "rprcsFcltCpct", lable: "시설용량(m³/일)", type: "number" },
{ id: "majorPrcsMsrcCd", lable: "주처리공법" },
{ id: "addr", lable: "위치(주소)" },
{ id: "rprcsMthdCd", lable: "재처리방식" },
{ id: "inoutYn", lable: "위치(처리장 내외)" ,type: "selectYn" }, // 머가 내? 외?,
{ id: "mainSplyPumpCpct", lable: "메인공급펌프 용량", type: "number" },
];
export const ruseFcltinfo:formDetailType[]=[
{id:'rprcsFcltCpct' , lable:'시설용량(m³/일)'},
{id:'majorPrcsMsrcCd' , lable:'주처리공법'},
{id:'addr' , lable:'위치(주소)'},
{id:'rprcsMthdCd' , lable:'재처리방식'},
{id:'inoutYn' , lable:'위치(처리장 내외)'},// 머가 내? 외?,
{id:'mainSplyPumpCpct' , lable:'메인공급펌프 용량'},
]
export const businessMoneyInfo: formDetailType[] = [
{ id: "gvsbs", lable: "국고", type: "number" },
{ id: "lcep", lable: "지방비", type: "number" },
{ id: "cusrAlotm", lable: "민간투자비", type: "number" },
{ id: "etc", lable: "기타" },
{ id: "totalMoney", lable: "합계", type: "number" },
];
export const businessMoneyInfo:formDetailType[]=[
{id:'gvsbs' , lable:'국고'},
{id:'lcep' , lable:'지방비'},
{id:'username' , lable:'민간투자비'},
{id:'cusr_alotm' , lable:'원인자부담금'},
{id:'etc' , lable:'기타'},
{id:'totalMoney' , lable:'합계'},
]
export const businessSttnInfo: formDetailType[] = [
{ id: "seCd", lable: "구분" },
{ id: "bizMthdCd", lable: "사업방식" },
{ id: "bizCmcnDay", lable: "사업준공일", type: "date" },
{ id: "instlAprvSncnDt", lable: "설치승인인가일", type: "date" },
];
export const businessSttnInfo:formDetailType[]=[
{id:'seCd' , lable:'구분'},
{id:'bizMthdCd' , lable:'사업방식'},
{id:'bizCmcnDay' , lable:'사업준공일' ,type:'date'},
{id:'instlAprvSncnDt' , lable:'설치승인인가일' ,type:'date'} ,
]
export const ruseFcltIntakeList: formDetailType[] = [
{ id: "seCd", lable: "구분" },
{ id: "bizMthdCd", lable: "사업방식" },
{ id: "bizCmcnDay", lable: "사업준공일", type: "date" },
{ id: "instlAprvSncnDt", lable: "설치승인인가일", type: "date" },
];
export const ruseWaterQulityList: formDetailType[] = [
{ id: "bod", lable: "BOD(mg/L)", type: "number" },
{ id: "grssClfg", lable: "총대장균군수(개/mg)", type: "number" },
{ id: "combResdch", lable: "결합잔류염소(mh/L)", type: "number" },
{ id: "trbt", lable: "탁도(NTU)", type: "number" },
{ id: "smll", lable: "냄새", type: "number" },
{ id: "crmty", lable: "색도", type: "number" },
{ id: "tn", lable: "총질소(mg/L)", type: "number" },
{ id: "tph", lable: "총인(mg/L)", type: "number" },
{ id: "ph", lable: "수소이온농도", type: "number" },
{ id: "clIonQnt", lable: "염화물(mg/L)", type: "number" },
{ id: "cdtnv", lable: "전기전도도(μs/cm)​", type: "number" },
];
@@ -1,35 +1,75 @@
import { z } from "zod";
import type { TableUpperProps } from "../rainReg";
import { getFetch } from "@/components/lib/api";
import { TEST_UUID, type TableUpperProps } from "../rainReg";
import {
deleteFectch,
fetchType,
getFetch,
postFectch,
putFectch,
} from "@/components/lib/api";
import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList";
import { pubFacHeadListExport, pubMngNmExport, pubMngUpHeadListExport } from "./headList";
import { exampleData, fcBizSchema, type customPbnst, type pubHasuType } from "./type";
import {
exampleData,
fcBizSchema,
type customPbnst,
type fcBizType,
type pbnstInsertIndex,
type pubHasuType,
} from "./type";
import type { SelectDataType } from "@common/business_components/ui";
import {
pbnstMngUpHeadListExport,
pbnstOrgwtHeadListRxport,
pbnstMngNmExport,
pubFacHeadListExport,
pplInstlsHeadListExport,
pbnstDemandHeadListExport,
fcOrgwtsHeadListExport,
} from "./headList";
const selectLabel: SelectDataType[] = [{ text: "test", val: "1" }];
const pubFacHeadList= pubFacHeadListExport;
const pubMngUpHeadList = pubMngUpHeadListExport
const pubMngNm= pubMngNmExport;
const pubFacHeadList = pubFacHeadListExport;
const pbnstMngUpHeadList = pbnstMngUpHeadListExport;
const pbnstOrgwtHeadList = pbnstOrgwtHeadListRxport;
const pbnstMngNm = pbnstMngNmExport;
const pplInstlsHeadLisat = pplInstlsHeadListExport;
const pbnstDemandHeadList = pbnstDemandHeadListExport;
const fcOrgwtsHeadList = fcOrgwtsHeadListExport;
export type pubFacState = {
pbnst: {
isInit: boolean;
pubFacHeadList: TableUpperProps[];
pubMngUpHeadList: TableUpperProps[];
pubMngNm: chHeadType[];
pbnstHasuRes: pubHasuType[]
selectLabel: SelectDataType[];
pbnstRes: customPbnst[];
pbnstMngUpHeadList: TableUpperProps[];
pbnstMngNm: chHeadType[];
pbnstHasuRes: pubHasuType[];
pbnstRes: customPbnst[] | null; // 배열에서 단일 객체 또는 null로 변경
pbnstResObj: customPbnst | null; // 배열에서 단일 객체 또는 null로 변경
pbnstBizCycle: SelectDataType[];
selectLabel: SelectDataType[];
pbnstOrgwtHeadList: TableUpperProps[];
pplInstlsHeadLisat: TableUpperProps[];
pbnstDemandHeadList: TableUpperProps[];
fcOrgwtsHeadList: TableUpperProps[];
pbnstMngRegData: customPbnst[];
pbnsUpdateIndex: pbnstInsertIndex;
dmdComList: any[];
};
};
type pubFacActions = {
pbnstRegInit: (sgg: string) => void;
pbnstRegSearch: (txt: string | undefined, sgg: string) => void;
pubHasuInit: (page: string, sggOrCity: string) => void;
pbnstRegClick: (pubHasuRes: pubHasuType) => void;
pbnstReg: (txt: string | undefined) => void;
pubHasuInit: (txt: string | undefined) => void;
pbnstInsert(pbnstRes: customPbnst): Promise<void>;
appendRuseFclt(array: customPbnst[]): void;
fileDelete: (atflCd: string) => void;
fileDownLoad: (atflCd: string) => void;
pbnstMngData: (data: customPbnst[]) => void;
pbnstBizData(data: fcBizType): void;
getBizCycle: (pbnstObj: customPbnst) => void;
getpbnstInsertIndex: (data: pbnstInsertIndex) => void;
pbnstRuseFcltUpdate: (ruseRes: customPbnst[]) => void;
pbnstRuseBizUpdate: (ruseRes: fcBizType[]) => void;
};
export type pubFacType = pubFacState & pubFacActions;
@@ -38,45 +78,101 @@ export const pubFacStateExport: pubFacState = {
pbnst: {
isInit: false,
pubFacHeadList: pubFacHeadList,
pubMngUpHeadList: pubMngUpHeadList,
selectLabel: selectLabel,
pubMngNm: pubMngNm,
pbnstMngUpHeadList: pbnstMngUpHeadList,
pbnstMngNm: pbnstMngNm,
pbnstOrgwtHeadList: pbnstOrgwtHeadList,
pbnstHasuRes: [],
pbnstRes: [],
pbnstBizCycle:[]
pbnstResObj: null, // 초기값을 null로 변경
pbnstRes: [], // 초기값을 null로 변경
pbnstBizCycle: [{ text: "없음", val: "1" }],
selectLabel: selectLabel,
pplInstlsHeadLisat: pplInstlsHeadLisat,
pbnstDemandHeadList: pbnstDemandHeadList,
fcOrgwtsHeadList: fcOrgwtsHeadList,
pbnstMngRegData: [],
pbnsUpdateIndex: {} as pbnstInsertIndex,
dmdComList: [],
},
};
const initFetch = async (txt: string | undefined) => {
const rainRes = await getFetch(
"common",
`/prcsFclt/info/1/10/A/41220` + (txt ? `/${txt}` : "") + "/500" + "/1"
);
return rainRes;
const checkWrRegList = async () => {
const wrRegList = await getFetch("wr", `/reuseFc/pubFc`);
return wrRegList;
};
const getBizCycle = async (fcBizs: z.infer<typeof fcBizSchema>[] ) => {
const pbnstBizCycle =fcBizs.map((item: z.infer<typeof fcBizSchema>) => {
const hasuListFetch = async (page: string, sggOrCity: string) => {
const checkWrRegListRes = await checkWrRegList();
const rainRes = await postFectch(
`/prcsFclt/ruseInfo/${page ? page : 1}/10/A/${sggOrCity ? sggOrCity : "11"}/0/1/SW`,
checkWrRegListRes,
fetchType.COMMON
);
const hasuList = rainRes.data.map((item: any) => {
return {
text: `${item.cycl}차수`,
val: item.cycl,
...item,
ruse: item.ruse === false ? "N" : "Y",
};
});
return pbnstBizCycle;
}
return hasuList;
};
const initFetch = async (ruseFcltPk: string | undefined) => {
const pbnstRes = await getFetch("wr", `/reuseFc/pubFc/${ruseFcltPk}`);
return pbnstRes;
};
export const writeRgtrAndMdfr = (data: customPbnst[]) => {
data.forEach((pbnst) => {
pbnst.rgtr = TEST_UUID;
pbnst.mdfr = TEST_UUID;
pbnst.fcBizs = pbnst.fcBizs?.map((item) => {
item.rgtr = TEST_UUID;
item.mdfr = TEST_UUID;
item.fcOrgwts = item.fcOrgwts?.map((orgwt) => {
orgwt.mdfr = TEST_UUID;
orgwt.rgtr = TEST_UUID;
return orgwt;
});
item.fcDemands = item.fcDemands?.map((dmd) => {
dmd.mdfr = TEST_UUID;
dmd.rgtr = TEST_UUID;
return dmd;
});
return item;
});
pbnst.fcOpers = pbnst.fcOpers.map((oper) => {
oper.rgtr = TEST_UUID;
oper.mdfr = TEST_UUID;
return oper;
});
pbnst.fcFiles = pbnst.fcFiles.map((file) => {
file.rgtr = TEST_UUID;
file.mdfr = TEST_UUID;
return file;
});
});
return data;
};
export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
return {
pubHasuInit: async (txt: string | undefined) => {
pubHasuInit: async (page: string, sggOrCity: string) => {
try {
const pbnstHasuRes = await initFetch(txt);
const pbnstHasuRes = await hasuListFetch(page, sggOrCity);
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
pbnstHasuRes:pbnstHasuRes,
pbnstHasuRes: pbnstHasuRes,
isInit: false,
},
}));
@@ -85,39 +181,15 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
}
},
pbnstRegInit: async (txt: string | undefined) => {
pbnstRegClick: async (pubHasuRes: pubHasuType) => {
try {
//const pbnstRes = await initFetch(txt);
const pbnstRes = exampleData;
const pbnstBizCycle = getBizCycle(pbnstRes?.fcBizs);
const pbnstRes = await initFetch(pubHasuRes.bscFctCd);
console.log('pbnstRes ' , pbnstRes)
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
pbnstRes: pbnstRes,
pbnstBizCycle:pbnstBizCycle,
isInit: false,
},
}));
} catch (errorHasu) {
console.log(errorHasu);
}
},
pbnstRegSearch: async (txt: string | undefined, sgg: string) => {
try {
const pbnstRes = await getFetch(
"wr",
`/api/v1/reuseFc/pubFc` + (txt ? `/${txt}` : "") + `/${sgg}`
);
const pbnstBizCycle = getBizCycle(pbnstRes.fcBizs);
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
pbnstReg: pbnstRes,
pbnstBizCycle:pbnstBizCycle,
isInit: false,
},
}));
@@ -125,9 +197,10 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
console.log(errorHasu);
}
},
pbnstReg: async (txt: string | undefined) => {
try {
const pbnstRes = await initFetch(txt);
const pbnstRes = await hasuListFetch("11", "11");
return set((state: pubFacState) => ({
...state,
pbnst: {
@@ -140,5 +213,167 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
console.log(errorHasu);
}
},
pbnstInsert: async (pbnstRes: customPbnst) => {
try {
const response = await postFectch(
`/reuseFc/pubFc`,
JSON.stringify( writeRgtrAndMdfr([pbnstRes]))
);
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
pbnstRes: response.data,
isInit: false,
},
}));
} catch (error) {
console.error("Error updating pbnst:", error);
}
},pbnstRuseFcltUpdate : async(ruseRes:customPbnst[])=>{
try {
const response = await putFectch(
`/reuseFc/pubFc`,
ruseRes
);
} catch (error) {
console.error("Error pbnstRuseFcltUpdate:", error);
}
} ,pbnstRuseBizUpdate : async(ruseRes:fcBizType[])=>{
try {
console.log('ruseRes ', ruseRes)
const response = await putFectch(
`/reuseFc/pubFcBiz`,
ruseRes
);
const pbnstRes = await hasuListFetch("11", "11");
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
pbnstRes: pbnstRes,
isInit: false,
},
}));
} catch (error) {
console.error("Error pbnstRuseFcltUpdate:", error);
}
} ,
getBizCycle: async (pbnstObj: customPbnst) => {
const pbnstBizCycle = pbnstObj.fcBizs.map((item) => {
return {
text: `${item.cycl}차`,
val: item.cycl,
};
});
console.log(" pbnstBizCycle ", pbnstBizCycle);
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
pbnstBizCycle: pbnstBizCycle,
pbnstResObj: pbnstObj,
isInit: false,
},
}));
},
fileDelete: async (atflCd: string) => {
try {
const response = await deleteFectch(
`/attach-service/api/v1/atfl/attachments/atflCd//${atflCd}`,
undefined,
fetchType.CS
);
console.log("File deleted successfully:", response);
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
isInit: false,
},
}));
} catch (error) {
console.error("Error deleting file:", error);
}
},
fileDownLoad: async (atflCd: string) => {
try {
const response = await getFetch(
fetchType.CS,
`/attach-service/api/v1/atflcd/download/${atflCd}`
);
if (!response.ok) {
throw new Error(`Error downloading file: ${response.statusText}`);
}
} catch (error) {
console.error("Error downloading file:", error);
}
},
pbnstMngData: (data: customPbnst[]) => {
if (data.length > 0) {
}
return set((state: pubFacState) => {
state.pbnst.pbnstMngRegData = data;
state.pbnst.pbnstBizCycle =
data.length <= 0
? [{ text: "없음", val: "1" }]
: state.pbnst.pbnstBizCycle;
return state;
});
},
pbnstBizData: (data: fcBizType) => {
return set((state: pubFacState) => {
if (state.pbnst.pbnstMngRegData) {
const regData = state.pbnst.pbnstMngRegData.find(
(item) => data.pbnstRuseFcltFk === item.pbnstRuseFcltPk
);
if (regData) {
const bizIdx = regData.fcBizs.findIndex(
(item) => item.pbnstRuseFcltBizPk == data.pbnstRuseFcltBizPk
);
if (bizIdx >= 0) {
regData.fcBizs[bizIdx] = data;
const pbnstData = state.pbnst.pbnstMngRegData
state.pbnst.pbnstMngRegData= [...pbnstData]
}
}
}
return state;
});
},
getpbnstInsertIndex: async (data: pbnstInsertIndex) => {
return set((state: pubFacState) => {
state.pbnst.pbnsUpdateIndex = data;
return state;
});
},
appendRuseFclt: async (array: customPbnst[]) => {
try {
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
pbnstRes: array,
isInit: false,
},
}));
} catch (e) {
console.log(e);
}
},
};
};
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
import { getFetch, postFectch, putFectch } from "@/components/lib/api";
import { z } from "zod";
import { makeMdfr, makeRstr } from "../bdstReg";
import type { SelectDataType } from "@common/business_components/ui";
export const TEST_UUID= '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e'
export const rainZod = z.object({
@@ -90,6 +91,7 @@ export type TableUpperProps = {
visible?:boolean;
rowSpan?: true;
type?: string;
selectData ?:string;
};
export const upHeadList: TableUpperProps[] = [
@@ -0,0 +1,28 @@
import type { customPbnst } from "../../fcltReg/pbnstReg/type";
export const excelPbnstSttnParsing = (data: any[]): string[][] => {
const excelExportData: string[][] = [];
data.forEach((item) => {
excelExportData.push([
item.a,
String(item.aaa),
item.aaaa ?? "",
item.operMngCd ?? "",
String(item.cityText ?? ""),
String(item.sggText ?? ""),
String(item.bdnstNm ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
String(item.archArea ?? ""),
])
})
return excelExportData;
}
@@ -1,13 +1,14 @@
import type { CommonTableProps } from "@/app/store/common/type/headder";
import type { customPbnst } from "../../fcltReg/pbnstReg";
import type { z } from "zod";
import {
pbnSttHeadListExport,
pbnstSttNodeListExport
} from "./headList";
} from "./headList";
import { fetchType, getFetch } from "@/components/lib/api";
import { controlHeadView } from "../rainSttn";
import type { sttnHeadListType } from "../rainSttn/headList";
import type { customPbnst } from "../../fcltReg/pbnstReg/type";
import { excelPbnstSttnParsing } from "./excel";
const pbnstSttHeadList = pbnSttHeadListExport;
const pbnstSttNodeList = pbnstSttNodeListExport;
@@ -26,6 +27,7 @@ type pbnstSttnActions = {
pbnstChSttnHead: (
headList: z.infer<typeof sttnHeadListType>,
) => void;
pbnstSttnSearch: (page: number, total: string, con: string) => void;
};
export type pbnstSttnType = pbnstSttnState & pbnstSttnActions;
@@ -34,8 +36,8 @@ export const pbnstSttnStateExport: pbnstSttnState = {
pbnstSttn: {
pbnstSttHeadList: pbnstSttHeadList,
pbnstSttNodeList: pbnstSttNodeList,
pbnstSttnRes:[],
excelList:[],
pbnstSttnRes: [],
excelList: [],
isInit: false,
},
};
@@ -45,19 +47,52 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = (
) => {
return {
pbnstChSttnHead: async (
headList: z.infer<typeof sttnHeadListType>
): Promise<void> => {
console.log('head ', headList)
const changeNodeList= controlHeadView(headList,pbnstSttNodeList)
return set((state: pbnstSttnState) => ({
pbnstSttn: {
...state.pbnstSttn,
pbnstSttHeadList: headList,
pbnstSttNodeList: changeNodeList,
},
}));
headList: z.infer<typeof sttnHeadListType>
): Promise<void> => {
console.log('head ', headList)
const changeNodeList = controlHeadView(headList, pbnstSttNodeList)
return set((state: pbnstSttnState) => ({
pbnstSttn: {
...state.pbnstSttn,
pbnstSttHeadList: headList,
pbnstSttNodeList: changeNodeList,
},
}));
},
pbnstSttnSearch: async (page: number, total: string, con: string) => {
try {
const pbnstSttnRes = await getFetch(
fetchType.WR,
`/reuseFc/waterreuseFc/${page}/${total}/${con}`
);
if (Number(total) > 9000) {
const pbnstExcel = excelPbnstSttnParsing(pbnstSttnRes);
set((state: pbnstSttnState) => {
return {
...state,
pbnstSttn: {
...state.pbnstSttn,
excelList: pbnstExcel,
},
};
});
} else
set((state: pbnstSttnState) => {
return {
...state,
pbnstSttn: {
...state.pbnstSttn,
pbnstSttnRes: pbnstSttnRes,
isInit: false,
},
};
});
} catch (erTxt) {
console.log("error", erTxt);
}
},
}
}
+20 -3
View File
@@ -87,6 +87,10 @@ import { rswtChkStateExport, rswtChkSttnActionsExport, type rswtChkSttnState, ty
import { pbnstSttnActionsExport, pbnstSttnStateExport, type pbnstSttnState, type pbnstSttnType } from "./ruseFcltMng/fcltSttn/pbnstSttn";
import { coSttnActionsExport, coSttnStateExport, type coSttnState, type coSttnType } from "./matDmd/coSttn"; // 옮겨야함
import { ldrmntCoSttnActionsExport, ldrmntCoSttnStateExport, type ldrmntCoSttnState, type ldrmntCoSttnType } from "./matDmd/ldrmntCoSttn"; // 옮겨야함
import { searchActions, type comFcltActions, type comFcltState } from "./common/searchCode";
import { pbnstPrfmncActionsExport, pbnstPrfmncStateExport, type pbnstPrfmncState, type pbnstPrfmncType } from "./prfmncReg/pbnstPrfmncReg";
import { coRvwActionsExport, coRvwStateExport, type coRvwState, type coRvwType } from "./matDmd/coMatRvw";
import { ruseHstryActionsExport, ruseHstryStateExport, type ruseHstryState, type ruseHstryType } from "./matDmd/ruseRvwHstry";
export type WrStore = comCodeType &
pubFacType &
@@ -103,8 +107,12 @@ export type WrStore = comCodeType &
rainChkSttnType&
rswtChkSttnType&
pbnstSttnType&
comFcltActions&comFcltState&
coSttnType& // 옮겨야함
ldrmntCoSttnType // 옮겨야함
ldrmntCoSttnType& // 옮겨야함
pbnstPrfmncType&
coRvwType&
ruseHstryType
;
export const StoreZus = (
@@ -124,8 +132,10 @@ export const StoreZus = (
rswtChkSttnState &
pbnstSttnState&
coSttnState& // 옮겨야함
ldrmntCoSttnState // 옮겨야함
ldrmntCoSttnState& // 옮겨야함
pbnstPrfmncState&
coRvwState&
ruseHstryState
= {
...comCodeStateExport,
...pubFacStateExport,
@@ -144,6 +154,9 @@ export const StoreZus = (
...pbnstSttnStateExport,
...coSttnStateExport, // 옮겨야함
...ldrmntCoSttnStateExport, // 옮겨야함
...pbnstPrfmncStateExport,
...coRvwStateExport,
...ruseHstryStateExport,
}
) => {
return createStore<WrStore>()((set) => {
@@ -155,6 +168,7 @@ export const StoreZus = (
getEnvPartCd(set);
return {
...initState,
...searchActions(set),
...pubFacActionsExport(set),
...rainFacActionsExport(set),
...rswtFacActionsExport(set),
@@ -172,6 +186,9 @@ export const StoreZus = (
...pbnstSttnActionsExport(set),
...coSttnActionsExport(set), // 옮겨야함
...ldrmntCoSttnActionsExport(set), // 옮겨야함
...pbnstPrfmncActionsExport(set),
...coRvwActionsExport(set),
...ruseHstryActionsExport(set),
};
});
};
+80 -45
View File
@@ -1,67 +1,102 @@
//8880
import { DEV_URL_FO, DEV_URL_WR } from "../config/";
import { CS_URL, DEV_URL_FO, DEV_URL_WR, LOCAL_URL } from "../config/";
export const fetchType={COMMON: "common", WR: "wr"}
export const fetchType = { COMMON: "common", WR: "wr", LOCAL: "LOCAL" , CS: "CS" };
export const getFetch = async( type:string , url: string) => {
const div = type === fetchType.COMMON ? DEV_URL_FO : DEV_URL_WR
const response = await fetch( div+url , {
export const getFetch = async (type: string, url: string) => {
const div =
type === fetchType.COMMON
? DEV_URL_FO
: type === fetchType.LOCAL
? LOCAL_URL
: type === fetchType.CS
? ''
: DEV_URL_WR;
const response = await fetch(div + url, {
method: "GET",
headers: {
accept: "application/json",
accept: "application/json",
},
});
if(!response.ok) {
if (!response.ok) {
console.log(`오류 발생: ${response.status}`);
}
const contentType = response.headers.get("content-type") ?? "";
if (contentType.includes("application/json")) {
// JSON 응답 처리
const res = await response.json();
return res.data;
} else {
// 파일 응답 처리 (ex. PDF, Excel 등)
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement("a");
const filename = "downloaded_file.PDF";
a.href = blobUrl;
a.download = filename;
a.click();
window.URL.revokeObjectURL(blobUrl);
return null; // 다운로드이므로 반환값 없음
}
};
export const postFectch = async (url: string, params: any, type?: string) => {
const div = type === fetchType.COMMON ? DEV_URL_FO : DEV_URL_WR;
const response = await fetch(div + url, {
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
if (!response.ok) {
throw new Error(`오류 발생: ${response.status}`);
}
const res = await response.json()
return res.data;
}
return await response.json();
};
export const postFectch = async (url: string, params: any) => {
const response = await fetch(DEV_URL_WR+url, {
method: "POST",
export const putFectch = async (url: string, params: any) => {
const response = await fetch(DEV_URL_WR + url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
if(!response.ok) {
if (!response.ok) {
throw new Error(`오류 발생: ${response.status}`);
}
return response;
}
};
export const deleteFectch = async (url: string, params?: any, type?: string) => {
const div =
type === fetchType.COMMON
? DEV_URL_FO
: type === fetchType.LOCAL
? LOCAL_URL
: type === fetchType.CS
? ''
: DEV_URL_WR;
export const putFectch = async (url: string, params: any) => {
const response = await fetch(DEV_URL_WR+url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
if(!response.ok) {
throw new Error(`오류 발생: ${response.status}`);
}
return response;
}
export const deleteFectch = async (url: string, params: any) => {
const response = await fetch(DEV_URL_WR+url, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
if(!response.ok) {
throw new Error(`오류 발생: ${response.status}`);
}
return response;
}
console.log("deleteFectch url", div + url);
const response = await fetch(div + url, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: params?JSON.stringify(params):undefined,
});
if (!response.ok) {
throw new Error(`오류 발생: ${response.status}`);
}
return await response.text();
};
+4 -3
View File
@@ -1,16 +1,17 @@
const LOCAL_PORT = "3000";
const LOCAL_PORT = "3001";
const DEV_PORT = "11080";
const DEV_PORT_NEW="11082"
export const LOCAL_URL_WR = `http://localhost:${LOCAL_PORT}/api/v1`;
export const DEV_URL_WR = `http://con.mind-one.co.kr:${DEV_PORT}/api/v1`;
// export const DEV_URL_WR = `http://192.168.0.91:8880/api/v1`;
//export const DEV_URL_FO = `http://con.mind-one.co.kr:${DEV_PORT_FO}/api/v1`;
// export const DEV_URL_FO = `http://192.168.0.91:8882/api/v1`;
export const DEV_URL_FO = `http://con.mind-one.co.kr:${DEV_PORT_NEW}/api/v1`;
export const LOCAL_URL = `http://localhost:${LOCAL_PORT}`;
export const CS_URL= 'http://dev.onasys.co.kr:8000'
export const CS_TUS_UPLOAD= "/attach-service/api/v1/tus/upload";
+69 -44
View File
@@ -1,53 +1,78 @@
import { Upload, type HttpRequest, type HttpResponse, type OnSuccessPayload } from "tus-js-client";
import {
Upload,
type HttpRequest,
type HttpResponse,
type OnSuccessPayload,
type DetailedError,
} from "tus-js-client";
import { CS_TUS_UPLOAD, CS_URL } from "../config";
import type { FilesType } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm";
type UploadOptions = {
handleUploadStart?: (upload: Upload) => void;
handleProgress?: (percentage: string) => void;
handleSuccess?: (payload :OnSuccessPayload) => void;
handleResponse?: (req:HttpRequest, res: HttpResponse) => void;
handleSuccess?: (payload: OnSuccessPayload) => void;
handleResponse?: (req: HttpRequest, res: ReturnFileType) => void;
};
export type ReturnFileType = {
orgnlFileNm: string;
atflFlieSz: number;
atflType: string;
dwnldNocs: number;
delYn: string;
lnkgDmnId: string | null;
lnkgDmnNm: string | null;
regDt: Date | null;
atflCd: String;
atflSn: number;
atflId: String;
physFileNm: String;
};
export const TusClientUpload = (oFile: FilesType, options?: UploadOptions) =>
new Promise<ReturnFileType>((resolve, reject) => {
try {
const Authorization = "Authorization";
const AUTH_USER_ID = "token-id";
let fileInfo: string | undefined;
//CS_URL
const uploader = new Upload(oFile.file, {
endpoint: CS_TUS_UPLOAD,
chunkSize: 10 * 1024 * 1024,
headers: {
Authorization: "",
AUTH_USER_ID: "",
},
metadata: {
filename: oFile.file.name,
filetype: oFile.file.type,
},
onError(error) {
reject(error);
},
onProgress(bytesUploaded, bytesTotal) {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
options?.handleProgress?.(percentage);
},
onAfterResponse(req, res: HttpResponse) {
fileInfo = res.getHeader("X-File-Info");
if (fileInfo === undefined || fileInfo === null) return;
},
onSuccess(payload: OnSuccessPayload) {
options?.handleSuccess?.(payload);
if (fileInfo) {
console.log('fileInfo', fileInfo);
resolve(JSON.parse(fileInfo) as ReturnFileType);
} else reject({} as DetailedError);
},
});
export function TusClientUpload(oFile: File, setUpload: (upload: Upload) => void, options?: UploadOptions) {
const Authorization = "Authorization";
const AUTH_USER_ID = "token-id";
//CS_URL
const uploader = new Upload(oFile, {
endpoint: 'http://localhost:3001' + CS_TUS_UPLOAD,
chunkSize: 10* 1024 * 1024,
headers: {
Authorization: "",
AUTH_USER_ID: "",
},
metadata: {
filename: oFile.name,
filetype: oFile.type,
},
onError(error) {
console.error(`Failed because: ${error}`);
},
onProgress(bytesUploaded, bytesTotal) {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
options?.handleProgress?.(percentage);
},
onAfterResponse(req, res) {
const fileInfo = res.getHeader("X-File-Info");
if (fileInfo === undefined || fileInfo === null) return;
options?.handleResponse?.(res);
},
onSuccess(payload :OnSuccessPayload) {
options?.handleSuccess?.(payload);
},
options?.handleUploadStart?.(uploader);
uploader.start();
} catch (e) {
console.log(e);
reject(e as DetailedError);
}
// setUpload(uploader);
});
uploader.start();
options?.handleUploadStart?.(uploader);
console.log("upload start >>>>>", uploader);
// setUpload(uploader);
}
//
//
@@ -15,7 +15,8 @@ import {
SelectBox,
type SelectDataType,
} from "@common/business_components";
import { Button, Input } from "@common/components";
import { Button, Checkbox, Input } from "@common/components";
import { Check } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { useFieldArray } from "react-hook-form";
@@ -42,18 +43,15 @@ export default function BdstReg({ form }: { form: any }) {
// remove(fields.length - 1);
// }
// };
// 이건 삭제해도되는거
const removeSelected = () => {
// fields 배열에서 selectedIds 에 포함된 field.id 의 인덱스만 골라내기
const idxsToRemove = fields
.map((f, i) => selectedIds.includes(f.id) ? i : -1)
.filter(i => i > -1)
// 내림차순 정렬: 인덱스 뒤쪽부터 제거해야 안전합니다.
.sort((a,b) => b - a);
.map((item, idx) => selectedIds.includes(item.id) ? idx : -1)
.filter(index => index > -1)
.sort((prevIndex, nextIndex) => nextIndex - prevIndex);
remove(idxsToRemove);
setSelectedIds([]);
};
remove(idxsToRemove);
setSelectedIds([]);
};
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | undefined>();
@@ -89,9 +87,6 @@ export default function BdstReg({ form }: { form: any }) {
return () => subscription?.unsubscribe();
}, [form.form]);
// 여기서부터는 자유롭게 삭제 가능
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const toggleSelect = (id: string) => {
@@ -280,19 +275,13 @@ export default function BdstReg({ form }: { form: any }) {
</Button>
</div>
{fields.map((field, index) => (
//여기서부터 자유롭게 삭제 가능
<div key={field.id} className="grid grid-cols-[auto,1fr,1fr,1fr] gap-2 mt-4 items-center">
{/* 1) 체크박스 */}
<div className="col-span-1 flex justify-center mr-4">
<input
type="checkbox"
<Checkbox
checked={selectedIds.includes(field.id)}
onChange={() => toggleSelect(field.id)}
className="h-6 w-6"
onCheckedChange={() => toggleSelect(field.id)}
/>
</div>
{/* 2) 구분 */}
<div className="">
<FormField
control={form.control}
@@ -308,8 +297,6 @@ export default function BdstReg({ form }: { form: any }) {
)}
/>
</div>
{/* 3) 허가일 */}
<div className="">
<FormField
control={form.control}
@@ -328,8 +315,6 @@ export default function BdstReg({ form }: { form: any }) {
)}
/>
</div>
{/* 4) 면적 */}
<div className="">
<FormField
control={form.control}
@@ -1,135 +1,676 @@
"use client";
import { SelectBox } from "@common/business_components";
import {
FormUi,
} from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import { Button, Input, TableHead, TableRow } from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { use, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useWrStore } from "@/app/store";
import {
selectClass,
type TableUpperProps,
} from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import {
commonButonSize,
formH1Class,
formTableName,
} from "@/components/lib/class/comClass";
import MngRegFormDetail from "../mngReg/mngRegFormDetail/mngRegFormDetail";
import {
businessMoneyInfo,
businessSttnInfo,
concentratedWaterInfo,
ruseFcltinfo,
ruseWaterQulityList,
wasteWaterFcltINfo,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/headList";
import { pbnstRegZod } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import {
fcBizSchema,
type customPbnst,
type fcBizType,
type PbnstDemandComType,
type ppInstlType,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import {
commonButonSize,
formH1Class,
formTableName,
} from "@/components/lib/class/comClass";
import FcOrgwtsRegModal from "@/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal";
import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable";
import { SelectBox } from "@common/business_components";
import {
FormUi,
SearchInputSelect,
UiModal,
type SelectDataType,
} from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import {
Button,
DataTable,
DatePicker,
Input,
Label,
} from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useRef, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import MngRegFormDetail from "../mngReg/mngRegFormDetail/mngRegFormDetail";
import { safeDate } from "@/components/lib/func/commom";
import SearchInputText from "@/components/ui/searchInput/searchInput";
import SdmInsertForm, {
type DemandCompType,
} from "@common/business_components/ui/sdmInsertForm";
export function PbnstBuseForm({
formType,
firstBizData,
}: {
formType: string;
firstBizData?: fcBizType[];
}) {
const ref = useRef<ppInstlType[] | null>(null);
const {
code,
pbnst,
demandCompList,
pbnstBizData,
getpbnstInsertIndex,
appendRuseFclt,
getBizCycle,
getDemandCompList,
regDemandCompany,
} = useWrStore((state) => state);
const { pbnstBizCycle, pbnstRes } = pbnst;
const [bizCycle, setBizCycle] = useState("1");
const [ppiSelectList, setPpiSelectList] =
useState<[{ name: string; val: SelectDataType[] }]>();
const [ppiYn, setPpiYn] = useState("Y");
const [isFcOrgwts, setIsFcOrgwts] = useState<boolean>(false);
const [sumCpct, setSumCpct] = useState<number>(0);
const [isDmdModal, setIsDmdModal] = useState<{
isMoal: boolean;
data: DemandCompType;
}>({ isMoal: false, data: {} as DemandCompType });
export function PbnstBuseForm() {
const { code, pbnst } = useWrStore((state) => state);
const { pubMngUpHeadList,pbnstBizCycle } = pbnst;
const [getBizCycle, setBizCycle] = useState('');
const form = useForm<z.infer<typeof pbnstRegZod>>({
resolver: zodResolver(pbnstRegZod),
const form = useForm<fcBizType>({
resolver: zodResolver(fcBizSchema),
defaultValues:
formType === "reg" &&
pbnst.pbnstMngRegData &&
pbnst.pbnstMngRegData[0]?.fcBizs
? (pbnst.pbnstMngRegData[0].fcBizs[0] ?? {})
: formType === "update" &&
typeof pbnst.pbnsUpdateIndex.biz === "number" &&
pbnst.pbnsUpdateIndex.biz >= 0 &&
pbnst.pbnstRes
? pbnst.pbnstRes[pbnst.pbnsUpdateIndex.mng]?.fcBizs[
pbnst.pbnsUpdateIndex.biz
]
: fcBizSchema.parse({}),
});
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof pbnstRegZod>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
console.log(values);
function onSubmit(values: fcBizType) {
// console.log(values);
}
const onSelectValue = (val: string) => {
console.log(val);
// 2. Define a submit handler.
function addRuseFcltObj() {
const newItem = fcBizSchema.parse({});
const index = pbnst.pbnsUpdateIndex.mng;
let regData: fcBizType = {} as fcBizType;
if (
pbnst.pbnstRes &&
pbnst.pbnstRes[pbnst.pbnsUpdateIndex.mng] &&
pbnst.pbnstRes[pbnst.pbnsUpdateIndex.mng]?.fcBizs
) {
regData = {
pbnstRuseFcltFk:
String(pbnst.pbnstRes[0]?.swtrmtFcltFk) +
String(pbnst.pbnstRes.length + 1),
cycl: Number(
(pbnst.pbnstRes[pbnst.pbnsUpdateIndex.mng]?.fcBizs?.length || 0) + 1
),
} as fcBizType;
const newPbnstRes = [...(pbnst.pbnstRes as customPbnst[])];
newPbnstRes[index]?.fcBizs?.length &&
newPbnstRes[index]?.fcBizs?.push(regData);
getBizCycle(newPbnstRes[index] as customPbnst);
appendRuseFclt(newPbnstRes);
}
}
const {
fields: fcDemands,
append: appendDemand,
remove: removeDemand,
} = useFieldArray({
control: form.control,
name: "fcDemands",
});
const { fields: fcOrgwtFiled } = useFieldArray({
control: form.control,
name: "fcOrgwts",
});
const {
fields: pplInstls,
append,
remove,
} = useFieldArray({
control: form.control,
name: "pplInstls",
});
// 등록할때 신규 값을 계속 가지고 있으려고
useEffect(() => {
const formData = form.watch();
formData.pplInstls = ref.current ? [...ref.current] : []; // ← form 내부 상태 기준으로 복사
if (formType === "reg") {
if (
firstBizData &&
firstBizData[0] &&
(form.getValues("pbnstRuseFcltFk") === "" ||
form.getValues("pbnstRuseFcltFk") === undefined)
) {
form.setValue(
"pbnstRuseFcltFk",
String(firstBizData[0].pbnstRuseFcltFk)
);
form.setValue("cycl", firstBizData[0].cycl);
}
pbnstBizData(formData as fcBizType);
}else if(formType === "update"){
console.log('formdata ', formData)
pbnstBizData(formData as fcBizType);
}
}, [firstBizData, form.formState, formType, pbnst.pbnstRes]);
useEffect(() => {
if (ppiYn === "N") {
form.setValue("pplInstls", []);
}
}, [ppiYn]);
useEffect(() => {
console.log("pbnst. bug pbnstMngRegData ", pbnst.pbnstMngRegData);
}, [pbnst.pbnstMngRegData]);
const plusHist = () => {
const regData = {
ruseFcltBizFk: ref.current?.[0]?.ruseFcltBizFk ?? form.getValues('pbnstRuseFcltBizPk'),
choose: false,
ppMqlt: "",
pdmt: "",
cpct: 0,
};
const currentTableDataFromRef = ref.current ? [...ref.current] : [];
const newDataForRHF = [...currentTableDataFromRef, regData];
form.setValue("pplInstls", newDataForRHF, {
shouldValidate: true, // 필요하다면 유효성 검사 실행
shouldDirty: true, // 폼 상태를 dirty로 변경
});
const latestOpers = form.getValues("pplInstls");
ref.current = [...latestOpers];
};
const plusArray = () => {
appendDemand({
dmdCoNm: "",
daddr: "",
useUsgCd: "",
splyBgngDay: new Date(),
splyEndDay: new Date(),
splyCpct: 0,
splyFmYn: "Y",
pbnstRuseFcltBizPk: form.getValues('pbnstRuseFcltBizPk'),
pbnstRuseFcltFk: form.getValues('pbnstRuseFcltFk')??'',
//telno: "",
});
};
const removeSelected = () => {
if (!ref.current) return;
const selectedIdxs = ref.current
.map((item, idx) => (item.choose ? idx : -1))
.filter((idx) => idx !== -1);
selectedIdxs.reverse().forEach((idx) => remove(idx));
};
const [instlAprvDate, setInstlAprvDate] = useState(new Date());
const [bizCmcnDate, setBizCmcnDate] = useState(new Date());
const [files, setFiles] = useState(null);
useEffect(() => {
if (getBizCycle ) {
console.log("getBizCycle", getBizCycle);
if (bizCycle && formType !== "reg") {
const index = Number(bizCycle) - 1;
form.reset(pbnst.pbnstResObj?.fcBizs[index]);
ref.current = pbnst.pbnstResObj?.fcBizs[index]?.pplInstls ?? [];
}
}, [getBizCycle]);
}, [bizCycle, pbnst.pbnstResObj, formType]);
useEffect(() => {
const ppl = [] as { name: string; val: SelectDataType[] }[];
if (code.pplLenght) {
ppl.push({ name: "pplLenght", val: code.pplLenght });
}
if (code.pplLenght) {
ppl.push({ name: "pplQulity", val: code.pplQulity });
}
if (ppl.length > 0) {
setPpiSelectList(ppl);
} else {
setPpiSelectList(undefined);
}
}, [code]);
useEffect(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
return (
<div className=" !mx-[1.6rem] !p-0r">
{/* 차수별 등록 현황 폼 */}
<div className="justify-between">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 mx-auto w-full mt-7"
>
<div className="flex justify-between items-center mx-3 mb-3">
<h1 className=" text-xl"></h1>
<div className="flex items-center gap-2 mx-3">
<h1 className={formTableName}>등록차수</h1>
<SelectBox
label=""
selectArray={pbnstBizCycle}
value={pbnstBizCycle.length > 0 && pbnstBizCycle[0]? pbnstBizCycle[0].val : ""}
className={""}
selectClass={selectClass}
onSelectValue={(val) => {setBizCycle}}
>
<>
<div className=" !mx-[1.6rem] !p-0r">
{/* 차수별 등록 현황 폼 */}
<div className="justify-between">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 mx-auto w-full mt-7"
>
<div className="flex justify-between items-center mx-3 mb-3">
<h1 className=" text-xl"></h1>
<div className="flex items-center gap-2 mx-3">
<h1 className={formTableName}>등록차수</h1>
<SelectBox
label=""
selectArray={pbnstBizCycle}
value={bizCycle ?? pbnstBizCycle[0]?.val}
className={""}
selectClass={selectClass}
onSelectValue={(val) => {
setBizCycle(String(val));
getpbnstInsertIndex({
...pbnst.pbnsUpdateIndex,
biz: Number(val),
});
}}
></SelectBox>
</SelectBox>
<Button size={commonButonSize}>저장</Button>
<Button
onClick={addRuseFcltObj}
type="button"
size={commonButonSize}
>
저장
</Button>
</div>
</div>
</div>
<MngRegFormDetail
formList={businessSttnInfo}
h1Title={"사업현황"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={businessMoneyInfo}
h1Title={"사업비"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={ruseFcltinfo}
h1Title={"재처리시설"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={concentratedWaterInfo}
h1Title={"농축수관리"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={wasteWaterFcltINfo}
h1Title={"폐수배출시설 정보"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={businessSttnInfo}
h1Title={"사업현황"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={businessMoneyInfo}
h1Title={"사업비"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={ruseFcltinfo}
h1Title={"재처리시설"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={concentratedWaterInfo}
h1Title={"농축수관리"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<MngRegFormDetail
formList={wasteWaterFcltINfo}
h1Title={"폐수배출시설 정보"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<h1 className={formH1Class}>재이용 원수취수 현황</h1>
</form>
</Form>
<h1 className={formH1Class}>재이용 원수취수 현황</h1>
<div>
<div className="text-right mb-3">
<Button
type="button"
onClick={() => setIsFcOrgwts(true)}
size={commonButonSize}
className=""
>
추가등록
</Button>
</div>
<RuseMngTable
tableData={fcOrgwtFiled}
headList={pbnst.pbnstOrgwtHeadList}
upHeadList={[]}
/>
</div>
<h1 className={formH1Class}>재이용 공급관로 설치 현황</h1>
<div className="flex items-center gap-4 mx-3 mb-3">
<Label className="text-right">공급관로설치유무</Label>
<SelectBox
label=""
selectArray={code.yn}
value={ppiYn}
className="w-32"
selectClass="w-full"
onSelectValue={(val) => setPpiYn(String(val))}
/>
<Label className="text-right">합계</Label>
<Input
type="text"
value={sumCpct}
readOnly={true}
className="w-32 border-gray-300 rounded pr-8 "
/>
{ppiYn === "Y" ? (
<>
<Button
type="button"
size={commonButonSize}
className="!text-xl"
onClick={plusHist}
>
추가등록
</Button>
<Button
type="button"
size={commonButonSize}
className="!text-xl !bg-red-500 !text-white ml-1"
onClick={removeSelected}
>
삭제
</Button>
</>
) : (
<></>
)}
</div>
<div>
<RuseMngTable
tableData={pplInstls}
headList={pbnst.pplInstlsHeadLisat}
code={ppiSelectList ?? []}
ref={ref}
upHeadList={[]}
onTableSet={(val) => {
if (!ref.current) return;
let sum = 0;
ref.current.forEach((item) => {
sum += Number(item.cpct) || 0;
});
setSumCpct(sum);
}}
/>
</div>
<MngRegFormDetail
formList={ruseWaterQulityList}
h1Title={"하수처리수 재이용시설 보증수질"}
form={form}
moClass="grid grid-cols-12 gap-4"
sonClass="col-span-4"
/>
<div key={"hasu"}>
<h1 className={formH1Class}>{"하수처리수 재이용수 수요처"}</h1>
<div className="text-right mb-3">
<Button
type="button"
onClick={() => {
plusArray();
}}
size={commonButonSize}
className="mt-6 mr-3"
>
수요항목 추가
</Button>
<Button
type="button"
onClick={() => {
setIsDmdModal({
isMoal: true,
data: {} as DemandCompType,
});
}}
size={commonButonSize}
className="mt-6"
>
수요처 추가
</Button>
</div>
{fcDemands.map((field, index) => (
<div
key={index + "loot"}
className="grid grid-cols-12 gap-4 mt-3"
>
<div key={index + "keySon1"} className="col-span-4">
<FormField
control={form.control}
name={`fcDemands.${index}.dmdCoNm`}
render={({ field: f }) => (
<FormUi label="수요처명">
<SearchInputText
label={undefined}
initValue={f.value}
setValue={(val) => {
f.onChange(val);
if (val) getDemandCompList(val, 3);
}}
setRtnData={(valList: PbnstDemandComType[]) => {
console.log('valList[0] ', index)
if (
valList.length > 0 &&
valList['0']
) {
form.setValue(
`fcDemands.${index}.pbnstRuseFcltBizPk`,
form.getValues('pbnstRuseFcltBizPk')
);
form.setValue(
`fcDemands.${index}.pbnstRuseFcltFk`,
form.getValues('pbnstRuseFcltFk')??''
);
form.setValue(
`fcDemands.${index}.dmdCoNm`,
valList['0'].dmdCoNm
);
form.setValue(
`fcDemands.${index}.daddr`,
valList['0'].daddr
);
form.setValue(
`fcDemands.${index}.useUsgCd`,
valList['0'].useUsgCd
);
form.setValue(
`fcDemands.${index}.splyCpct`,
valList['0'].splyCpct
);
form.setValue(
`fcDemands.${index}.splyFmYn`,
valList['0'].splyFmYn ?? "Y"
);
form.setValue(
`fcDemands.${index}.dmdInfoPk`,
valList['0'].dmdInfoPk ?? "Y"
);
}
}}
></SearchInputText>
</FormUi>
)}
/>
</div>
<div key={index + "keySon2"} className="col-span-4">
<FormField
control={form.control}
name={`fcDemands.${index}.daddr`}
render={({ field: f }) => (
<FormUi label="위치(주소)">
<Input
placeholder="-"
type="text"
readOnly
onChange={(e) => f.onChange(e.target.value)}
value={f.value}
className={selectClass}
/>
</FormUi>
)}
/>
</div>
<div key={index + "keySon3"} className="col-span-4">
<FormField
control={form.control}
name={`fcDemands.${index}.useUsgCd`}
render={({ field: f }) => (
<FormUi label="사용용도">
<SelectBox
label={""}
selectClass={""}
className="w-full"
selectArray={code.dmdUszCd}
onSelectValue={(val: string | number) =>
f.onChange(val)
}
value={f.value}
/>
</FormUi>
)}
/>
</div>
<div key={index + "keySon4"} className="col-span-3">
<FormField
control={form.control}
name={`fcDemands.${index}.splyBgngDay`}
render={({ field: f }) => (
<FormUi label="공급시작일">
<DatePicker
mode={"single"}
selected={safeDate(f.value) || new Date()}
onSelect={(date: Date | undefined) =>
f.onChange(date)
}
className={"rounded-lg !w-full"}
/>
</FormUi>
)}
/>
</div>
<div key={index + "keySon5"} className="col-span-3">
<FormField
control={form.control}
name={`fcDemands.${index}.splyEndDay`}
render={({ field: f }) => (
<FormUi label="공급종료일">
<DatePicker
mode={"single"}
selected={safeDate(f.value) || new Date()}
onSelect={(date: Date | undefined) =>
f.onChange(date)
}
className={"rounded-lg !w-full"}
/>
</FormUi>
)}
/>
</div>
<div key={index + "keySon6"} className="col-span-3">
<FormField
control={form.control}
name={`fcDemands.${index}.splyCpct`}
render={({ field: f }) => (
<FormUi label="(계약)공급량(m3/일)">
<Input
placeholder=""
type="number"
value={f.value ?? 0}
onChange={(e) => {
f.onChange(Number(e.target.value));
}}
/>
</FormUi>
)}
/>
</div>
<div key={index + "keySon7"} className="col-span-3">
<FormField
control={form.control}
name={`fcDemands.${index}.splyFmYn`}
render={({ field: f }) => (
<FormUi label="공급 유량계 유무">
<SelectBox
label={""}
selectClass={""}
className="w-full"
selectArray={code.yn}
onSelectValue={(val: string | number) =>
f.onChange(val)
}
value={f.value}
/>
</FormUi>
)}
/>
</div>
</div>
))}
</div>
</form>
</Form>
</div>
<UiModal
open={isFcOrgwts}
onOpenChange={(open: boolean) => setIsFcOrgwts(open)}
bodyClass={"w-[50vw] max-w-none h-[60vh]"}
body={
<FcOrgwtsRegModal
closeModal={() => {
setIsFcOrgwts(false);
}}
onRightSet={(val) => {
form.setValue("fcOrgwts", val);
}}
></FcOrgwtsRegModal>
}
></UiModal>
<UiModal
bodyClass={"w-[40vw] max-w-none h-[90vh]"}
open={isDmdModal.isMoal}
onOpenChange={() => {
setIsDmdModal({ isMoal: false, data: {} as DemandCompType });
}}
body={
<SdmInsertForm
selectSidoData={code.cityCd}
selectSigunData={code.sggCd}
dataList={demandCompList}
rtnValue={(searchText: string, pageCnt: number) => {
getDemandCompList(searchText, pageCnt);
}}
rtnInputData={(data) => {
console.log(" rtn data ", data);
regDemandCompany(data as DemandCompType);
setIsDmdModal({ isMoal: false, data: data as DemandCompType });
}}
bodyClassNm={"w-full mx-auto bg-white rounded-lg shadow-md p-6"}
></SdmInsertForm>
}
></UiModal>
</div>
</div>
</>
);
}
@@ -1,146 +1,314 @@
"use client";
import { SelectBox } from "@common/business_components";
import { FileUpload, FormUi, UiTable } from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import { Button, Input, TableHead, TableRow } from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { set, useForm } from "react-hook-form";
import { nullable, z } from "zod";
import { useWrStore } from "@/app/store";
import {
selectClass,
type TableUpperProps,
} from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { FileUpload, FormUi, SelectBox } from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import { Button, Input } from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useRef, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
import {
commonButonSize,
formH1Class,
formH2Class,
formTableName,
} from "@/components/lib/class/comClass";
import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable";
import { TusClientUpload } from "@/components/lib/useTus_wr/useTusWr";
import type { Upload } from "tus-js-client";
import type { fcltOperInfoType, ruseFcltType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable";
import {
pbnstRegZod,
type customPbnst,
type fcBizType,
type fcFileType,
type operType,
type selectLabel,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { Label } from "@common/business_components/ui/form/lib/label";
import React from "react";
export type FilesType = {
atflCd: string;
file: File;
};
const formSchema = z.object({
fcltNm: z.string().min(2).max(50),
psysScdg: z.string(),
designScdg: z.string(),
fcltCpct: z.number(),
swtrmtCpct: z.number(),
ruseCpct: z.number(),
splyExtraCpct: z.number(),
});
export function PbnstMngForm({
formType,
firstFcltData,
}: {
formType: string;
firstFcltData?: customPbnst;
}) {
const {
pbnst,
pbnstInsert,
pbnstMngData,
fileDelete,
fileDownLoad,
getBizCycle,
getpbnstInsertIndex,
appendRuseFclt
} = useWrStore((state) => state);
export function PbnstMngForm({ data }: { data: ruseFcltType[] }) {
const { pbnst } = useWrStore((state) => state);
const { pubMngUpHeadList, isInit } = pbnst;
const [ruseOperData, setRuseOperData] = useState<fcltOperInfoType[]>();
const [planFiles, setPlanFiles] = useState<File[] | null>(null);
const [relationfiles, setRelationFiles] = useState<File[] | null>(null);
const [regFileList, setRegFileList] = useState<fcFileType[] | null>(null);
const [planFiles, setPlanFiles] = useState<FilesType[] | null>(null);
const [relationfiles, setRelationFiles] = useState<FilesType[] | null>(null);
const [fcltNm, setFcltNm] = useState<selectLabel[] | null>(null);
const [fcOpresData, setFcOpresData] = useState<operType[] | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [upload, setUpload] = useState<Upload | null>(null);
const [progress, setProgress] = useState(0);
//const new File([] as BlobPart[], "") 파일 다운로드 활용
const ref = useRef<operType[] | null>(null);
const form = useForm<z.infer<typeof pbnstRegZod>>({
resolver: zodResolver(pbnstRegZod),
defaultValues:
formType === "reg" && pbnst.pbnstMngRegData
? pbnst.pbnstMngRegData[0]
: formType === "update" &&
typeof pbnst.pbnsUpdateIndex.mng === "number" &&
pbnst.pbnsUpdateIndex.mng >= 0 &&
pbnst.pbnstRes
? pbnst.pbnstRes[pbnst.pbnsUpdateIndex.mng]
: pbnstRegZod.parse({}),
});
const {
fields: fcOpersField,
append,
remove,
} = useFieldArray({
control: form.control,
name: "fcOpers",
});
const handleProgress = (percentage: string) => {
console.log("handleProgress", percentage);
setProgress(Number(percentage));
};
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
fcltNm: "",
psysScdg: "",
designScdg: "",
fcltCpct: 0,
swtrmtCpct: 0,
ruseCpct: 0,
splyExtraCpct: 0,
},
});
const selectFclt = (val: number) => {
if (pbnst.pbnstRes && pbnst.pbnstRes[val]) {
getBizCycle(pbnst.pbnstRes[val]);
form.reset(pbnst.pbnstRes[val]);
getpbnstInsertIndex({ ...pbnst.pbnsUpdateIndex, mng: val });
}
};
const selectOptions = data?.map((item) => ({
text: item.fcltNm,
val: item.pbnstRuseFcltPk,
})) || [{ text: "없음", val: "1" }];
const onSelectValue = (val: string) => {};
const plusHist = () => {
const currentTableDataFromRef = ref.current ? [...ref.current] : [];
const regData = {
choose: false,
pbnstRuseFcltFk:form.getValues('pbnstRuseFcltPk')??"",
rgtr: "",
mdfr: "",
operMthd: "",
rprtvBzenty: "",
bgngDt: new Date(),
endDt: new Date(),
};
const newDataForRHF = [...currentTableDataFromRef, regData];
form.setValue("fcOpers", newDataForRHF, {
shouldValidate: true, // 필요하다면 유효성 검사 실행
shouldDirty: true, // 폼 상태를 dirty로 변경
});
};
const removeSelected = () => {
if (!ref.current) return;
const selectedIdxs = ref.current
.map((item, idx) => (item.choose ? idx : -1))
.filter((idx) => idx !== -1);
selectedIdxs.reverse().forEach((idx) => remove(idx));
};
const fileHandler = (files: File[], type: string) => {
let fileList = [] as FilesType[];
if (type === "relation" && relationfiles) {
fileList = relationfiles;
} else if (type === "plan" && planFiles) {
fileList = planFiles;
}
let setReFiles = [...(fileList || [])];
if (files && (!fileList || fileList.length < files.length)) {
files.find((item) => {
if (
!fileList ||
fileList?.findIndex((reFile) => reFile.file === item) < 0
) {
TusClientUpload({ atflCd: "", file: item })
.then((tusFile) => {
setReFiles.push({
atflCd: tusFile.atflCd.toString(),
file: item,
});
type === "relation"
? setRelationFiles(setReFiles)
: setPlanFiles(setReFiles);
})
.catch((e) => {
console.log(e);
});
return item;
}
});
} else if (fileList && (!files || fileList.length > files.length)) {
if (!files) {
fileList.forEach((file) => {
fileDelete(file.atflCd as string);
});
setReFiles = [];
setReFiles.length = 0;
} else {
fileList?.forEach((item, index) => {
const idx = files?.indexOf(item.file);
if (idx < 0) {
setReFiles.splice(index);
fileDelete(item.atflCd as string);
}
});
}
type === "relation"
? setRelationFiles(setReFiles)
: setPlanFiles(setReFiles);
}
};
const regFileFilter = (
files: FilesType[],
fileKnd: string,
regFile: fcFileType[]
) => {
const exFileDatas =
regFile?.filter((item) => item.fileKnd !== fileKnd) || [];
const currentdata = files.map((item) => ({
fileTkn: item.atflCd,
filePath: item.file.name,
fileKnd: fileKnd,
rgtr: TEST_UUID,
mdfr: TEST_UUID,
}));
return [...exFileDatas, ...currentdata];
};
// 등록할때 신규 값을 계속 가지고 있으려고
useEffect(() => {
if (
firstFcltData &&
(form.getValues("swtrmtFcltFk") === "" ||
form.getValues("swtrmtFcltFk") === undefined)
) {
form.setValue("swtrmtFcltFk", firstFcltData.swtrmtFcltFk);
form.setValue(
"pbnstRuseFcltPk",
String(firstFcltData.swtrmtFcltFk + "1")
);
form.setValue("fcBizs", firstFcltData.fcBizs);
}
}, [firstFcltData]);
useEffect(() => {
if (isInit) {
form.reset();
setRuseOperData([{}] as fcltOperInfoType[]);
const defaultFiles = form.getValues("fcFiles");
if (defaultFiles && defaultFiles.length > 0) {
const relationFileList = defaultFiles.filter(
(item) => item.fileKnd === "계통도"
);
const planFileList = defaultFiles.filter(
(item) => item.fileKnd === "평면도"
);
if (relationFileList.length > 0)
setRelationFiles(
relationFileList.map((item) => ({
atflCd: item.fileTkn,
file: new File([] as BlobPart[], item.filePath),
}))
); // 또는 파일 형식 변환 로직이 있다면 여기에 추가
if (planFileList.length > 0)
setPlanFiles(
planFileList.map((item) => ({
atflCd: item.fileTkn,
file: new File([] as BlobPart[], item.filePath),
}))
);
}
}, [isInit]);
}, [form.formState.defaultValues]); // 초기화 이후 1회 실행
// 등록할때 신규 값을 계속 가지고 있으려고
useEffect(() => {
if (
formType === "update" &&
pbnst.pbnsUpdateIndex.biz >= 0 &&
pbnst.pbnstRes
) {
const bizData = pbnst.pbnstRes[pbnst.pbnsUpdateIndex.biz];
if (bizData) {
getBizCycle(bizData);
}
}
}, [pbnst.pbnsUpdateIndex, pbnst.pbnstRes, formType, getBizCycle]);
//신규 등록시 입력할때 데이터 넣기
useEffect(() => {
const formData = form.watch();
formData.fcOpers = ref.current ? [...ref.current] : [];
formData.fcFiles = regFileList ? [...regFileList] : [];
if (formType === "reg") {
pbnstMngData([formData] as customPbnst[]);
} else if (formType === "update"){
const updateRes = pbnst.pbnstRes?.map((item)=>{
if(item.pbnstRuseFcltPk === item.pbnstRuseFcltPk){
item=formData as customPbnst
}
return item
})
pbnstMngData(updateRes as customPbnst[]);
}
}, [form.formState, regFileList, formType, pbnst.pbnstRes]);
useEffect(() => {
pbnstMngData(pbnst.pbnstMngRegData);
}, [pbnst.pbnstMngRegData]);
useEffect(() => {
if ( pbnst.pbnstRes && pbnst.pbnstRes?.length<= 0) return;
const fcltNmList = pbnst.pbnstRes?.map((item, index) => ({
text: item.ruseUtztnFcltNm,
val: String(index),
}));
if(fcltNmList)
setFcltNm(fcltNmList);
}, [pbnst.pbnstRes]);
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values);
function onSubmit(values: z.infer<typeof pbnstRegZod>) {
values.fcOpers = fcOpresData || [];
values.fcFiles = regFileList || [];
pbnstInsert(values);
}
const testFile = () => {
if (relationfiles && relationfiles[0])
TusClientUpload(relationfiles[0], setUpload, {
handleUploadStart: () => {
console.log("handleUploadStart");
},
handleProgress,
handleResponse: (req, res) => {
console.log(" handleResponse req", req);
console.log("handleResponse res", res);
},
handleSuccess: (a) => {
console.log("req", a);
},
});
};
useEffect(() => {
if (relationfiles && relationfiles[0])
console.log("relationfiles ", relationfiles[0]);
// TusClientUpload(
// relationfiles[0],{
// handleUploadStart: setUpload,
// handleProgress,
// handleSuccess: setUpload,
// })
if (relationfiles) {
setRegFileList(regFileFilter(relationfiles, "계통도", regFileList || []));
}
}, [relationfiles]);
const headMakeColSpan = (upHeadList: TableUpperProps[]) => {
return (
<>
<TableRow>
{upHeadList.map((head: TableUpperProps) =>
!head.upSequnce ? (
<TableHead rowSpan={2} key={head.id}>
{head.title}
</TableHead>
) : head.upName === "upChangeRe" && head.upSequnce === 1 ? (
<TableHead key={head.id} rowSpan={1} colSpan={2}>
{"운영대행기간"}
</TableHead>
) : null
)}
</TableRow>
<TableRow>
{upHeadList.map((head: TableUpperProps) =>
head.upName === "upChangeRe" ? (
<TableHead key={head.id}>{head.title}</TableHead>
) : null
)}
</TableRow>
</>
);
};
const nodeList = headMakeColSpan(pubMngUpHeadList);
useEffect(() => {
if (planFiles) {
setRegFileList(regFileFilter(planFiles, "평면도", regFileList || []));
}
}, [planFiles]);
useEffect(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
return (
<div className=" !mx-[1.6rem] !p-0r">
@@ -149,91 +317,152 @@ export function PbnstMngForm({ data }: { data: ruseFcltType[] }) {
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex justify-between items-center mx-3 mb-3">
<div className="col-span-2 flex items-top">
<h2 className={formTableName + "pr-3 col-span-10 "}>
<Label className={formTableName + "pr-3 col-span-2 "}>
재이용 시설명
</h2>
<FormField
control={form.control}
name="fcltNm"
render={({ field }) => (
<FormUi label="">
<SelectBox
label=""
selectArray={selectOptions}
className={""}
selectClass={selectClass}
onSelectValue={() => {}}
{...field}
/>
</FormUi>
)}
/>
</Label>
{formType === "reg" ? (
<FormField
control={form.control}
name="ruseUtztnFcltNm"
render={({ field }) => (
<FormUi label="">
<Input
className="!text-2xl"
placeholder=""
type="text"
onChange={(e) => {
field.onChange(String(e.target.value));
}}
value={field.value ?? ""}
/>
</FormUi>
)}
/>
) : (
<SelectBox
label=""
className="mt-4 ml-2"
selectClass={undefined}
selectArray={fcltNm ?? []}
onSelectValue={(val) => {
selectFclt(Number(val));
}}
value={""}
/>
)}
</div>
{/* <Button onClick={addRuseFcltObj} type="button" size={commonButonSize}>
추가
</Button> */}
</div>
<div>
<div className="h-[200px] overflow-y-auto">
<div className="h-[200px] overflow-y-auto">
<div className="flex justify-between items-center mx-3 mb-3">
<span className={formTableName}>재이용시설 운영정보</span>
<Button
type="button"
size={commonButonSize}
className="!text-xl"
onClick={() => testFile()}
>
추가등록
</Button>
<div>
<Button
type="button"
size={commonButonSize}
className="!text-xl"
onClick={plusHist}
>
추가등록
</Button>
<Button
type="button"
size={commonButonSize}
className="!text-xl !bg-red-500 !text-white ml-1"
onClick={removeSelected}
>
삭제
</Button>
</div>
</div>
<RuseMngTable
tableData={[]}
headList={pbnst.pubMngUpHeadList}
upHeadList={pbnst.pubMngNm}
tableData={fcOpersField}
headList={pbnst.pbnstMngUpHeadList}
upHeadList={pbnst.pbnstMngNm}
onTableSet={(val) => {
setFcOpresData(val);
}}
ref={ref}
></RuseMngTable>
</div>
</div>
<div>
<h1 className={formH1Class}>재이용수 공급 가능량</h1>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-3">
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name="fcltCpct"
render={({ field }) => (
<FormUi label="시설용량(m³/일)">
<Input placeholder="" type="number" {...field} />
<Input
placeholder=""
type="number"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name="swtrmtCpct"
name="hasuhasu"
render={({ field }) => (
<FormUi label="하수처리량(m³/일)">
<Input placeholder="" type="number" {...field} />
<Input
placeholder=""
type="number"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name="ruseCpct"
render={({ field }) => (
<FormUi label="재이용량(m³/일)">
<Input placeholder="" type="number" {...field} />
<Input
placeholder=""
type="number"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<div className="col-span-3 mt-4">
<FormField
control={form.control}
name="splyExtraCpct"
render={({ field }) => (
<FormUi label="공급여유량(m³/일)">
<Input placeholder="" type="number" {...field} />
<Input
placeholder=""
type="number"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
@@ -244,34 +473,40 @@ export function PbnstMngForm({ data }: { data: ruseFcltType[] }) {
</div>
<div className="grid grid-cols-12 gap-4 my-4">
<div className="col-span-6">
<FormField
control={form.control}
name="psysScdg"
render={({}) => (
<FormUi label="계통도">
<FileUpload
files={relationfiles || []}
setFiles={setRelationFiles}
fileTypes="PDF"
></FileUpload>
</FormUi>
)}
/>
<FormUi label="계통도">
<FileUpload
files={relationfiles?.map((item) => item.file) || []}
setFiles={(files: File[] | null) => {
if (files) fileHandler(files, "relation");
}}
fileTypes="PDF"
onFileClick={(index) => {
fileDownLoad(
relationfiles?.find(
(item, fileIndex) => fileIndex === index
)?.atflCd as string
);
}}
></FileUpload>
</FormUi>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="designScdg"
render={({}) => (
<FormUi label="평면도">
<FileUpload
files={planFiles || []}
setFiles={setPlanFiles}
fileTypes="PDF"
></FileUpload>
</FormUi>
)}
/>
<FormUi label="평면도">
<FileUpload
files={planFiles?.map((item) => item.file) || []}
setFiles={(files: File[] | null) => {
if (files) fileHandler(files, "plan");
}}
fileTypes="PDF"
onFileClick={(index) => {
fileDownLoad(
planFiles?.find(
(item, fileIndex) => fileIndex === index
)?.atflCd as string
);
}}
></FileUpload>
</FormUi>
</div>
</div>
</div>
@@ -1,8 +1,17 @@
'use client'
import { useWrStore } from "@/app/store";
import type { formDetailType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/headList";
import { formH1Class } from "@/components/lib/class/comClass";
import { safeDate } from "@/components/lib/func/commom";
import { FormField, FormUi } from "@common/business_components/ui";
import {
FormField,
FormUi,
SearchInputSelect,
SelectBox,
type SelectDataType,
} from "@common/business_components/ui";
import { DatePicker, Input } from "@common/components/ui";
import { useState } from "react";
export default function MngRegFormDetail({
formList,
@@ -10,23 +19,34 @@ export default function MngRegFormDetail({
form,
moClass,
sonClass,
cycle,
onIputData,
searchSelectData
}: {
formList: formDetailType[];
h1Title: string;
h1Title?: string;
form: any;
moClass?: string;
cycle?: string;
sonClass?: string;
onIputData?: (data: any) => void;
searchSelectData?: SelectDataType[];
}) {
const { code } = useWrStore((state) => state);
const [searchData, setSearchData]= useState<string>('')
return (
<>
<h1 className={formH1Class}>{h1Title}</h1>
{h1Title ? <h1 className={formH1Class}>{h1Title}</h1> : <></>}
<div className={moClass}>
{formList?.map((item, index) => {
const fieldId = cycle ? `${cycle}.${item.id}` : item.id;
return (
<div className={sonClass} key={index + `${item.id}`}>
<FormField
control={form.control}
name={item.id}
name={fieldId}
render={({ field }) => (
<FormUi label={item.lable}>
{item.type === "select" ? (
@@ -34,17 +54,39 @@ export default function MngRegFormDetail({
) : item.type === "date" ? (
<DatePicker
mode={"single"}
field={{
value: safeDate(field.value) || new Date(),
onChange: (date: Date | undefined) =>
field.onChange(date),
}}
selectClass={"rounded-lg w-full"}
></DatePicker>
selected={safeDate(field.value) || new Date()}
onSelect={(date: Date | undefined) =>
field.onChange(date)
}
className={"rounded-lg !w-full"}
/>
) : item.type === "number" ? (
<Input placeholder="" type="number" {...field} />
<Input
placeholder=""
type="number"
value={field.value ?? 0}
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
/>
) : item.type === "selectYn" ? (
<SelectBox
label={""}
selectClass={""}
className="w-full"
selectArray={code.yn}
onSelectValue={(val: string | number) =>
field.onChange(val)
}
value={field.value}
/>
) : (
<Input placeholder="" type="" {...field} />
<Input
placeholder=""
type="text"
value={field.value ?? ""}
onChange={field.onChange}
/>
)}
</FormUi>
)}
@@ -56,3 +98,4 @@ export default function MngRegFormDetail({
</>
);
}
@@ -0,0 +1,168 @@
import { useWrStore } from "@/app/store";
import { excelToPbnPrfData } from "@/app/store/prfmncReg/pbnstPrfmncReg/excel";
import { pbnPrfRegExcelZod } from "@/app/store/prfmncReg/pbnstPrfmncReg/headList";
import { yearOptions } from "@/components/lib/func/commom";
import { ToastFormUi } from "@/components/ui/toast/toastFromUi";
import {
FileUpload,
FormField,
FormUi,
SelectBox,
Form,
Title,
type SelectDataType,
filePicked,
} from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import type { z } from "zod";
export default function PbnPrfExcelModal() {
const { code, prfmnc, searchFclts } = useWrStore((state) => state);
const [bscFctNmList, setBscFctNmList] = useState<SelectDataType[] | null>(null);
const [files, setFiles] = useState<File[] | null>(null);
const [excelData, setExcelData] = useState<any[]>();
const form = useForm<z.infer<typeof pbnPrfRegExcelZod>>({
resolver: zodResolver(pbnPrfRegExcelZod),
defaultValues: {
ym: String(new Date().getFullYear()),
},
});
const onSubmit = (values: z.infer<typeof pbnPrfRegExcelZod>) => {
console.log(values);
};
useEffect(() => {
if(prfmnc.prfmncRes.length > 0){
const basFctNmList = prfmnc.prfmncRes.map((item)=>(
{
val:item.bscFctCd,
text:item.bscFctNm
}
))
setBscFctNmList(basFctNmList)
}
}, [prfmnc.prfmncRes]);
useEffect(()=>{
if(files){
const file = files[0];
if (form.getValues("ym").length < 1) {
ToastFormUi({
body: "파일업로드 이전에 등록년도를 작성해주세요 ",
title: "지도점검 등록",
});
setFiles(null);
return;
}
if(file)
filePicked(file, 0).then((res) => {
if (res !== undefined)
console.log(excelToPbnPrfData(res, form.getValues("ym")))
});
}
},[files])
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">
저장
</Button>
</div>
<div>
<FormField
control={form.control}
name="ym"
render={({ field }) => (
<FormUi label="등록년도">
<SelectBox
className="w-1/4 text-3xl"
selectClass={""}
label=""
selectArray={yearOptions}
value={field.value}
onSelectValue={(val) => field.onChange(val)}
/>
</FormUi>
)}
/>
<FormField
control={form.control}
name="bscFctNm"
render={({ field }) => (
<FormUi label="시설명">
<SelectBox
className="w-1/4 text-3xl"
selectClass={""}
label=""
selectArray={bscFctNmList || [] }
value={field.value}
onSelectValue={(val) => field.onChange(String(val))}
/>
</FormUi>
)}
/>
<FormField
control={form.control}
name="pbnstRuseFcltNm"
render={({ field }) => (
<FormUi label="재이용시설명">
<SelectBox
className="w-1/4 text-3xl"
selectClass={""}
label=""
selectArray={yearOptions}
value={field.value}
onSelectValue={(val) => field.onChange(String(val))}
/>
</FormUi>
)}
/>
<FormField
control={form.control}
name="file"
render={({ field }) => (
<FormUi label="점검결과 파일">
<FileUpload
files={files || []}
setFiles={(files: File[] | null) => {
if (files) setFiles(files);
}}
fileTypes="xlsx"
></FileUpload>
</FormUi>
)}
/>
</div>
</form>
</Form>
</div>
</>
);
}
@@ -0,0 +1,150 @@
"use client";
import { useWrStore } from "@/app/store";
import type { pubHasuType, fcBizType, fcOrgwtType,} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import type { TableUpperProps } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { tableData } from "@/components/lib/func/commom";
import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable";
import { Title, UiTable } from "@common/business_components/ui";
import { Label } from "@common/business_components/ui/form/lib/label";
import { Button } from "@common/business_components/ui/table/lib/button";
import { Input } from "@common/components/ui";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { useFieldArray } from "react-hook-form";
export default function FcOrgwtsRegModal({ onRightSet, closeModal }: { onRightSet?: (rightData:fcOrgwtType[]) => void,closeModal?:(val:boolean) =>void }) {
const { code, pbnst, getSwtrmtFcltNm, searchFclts } = useWrStore((state) => state);
const { fcOrgwtsHeadList, pbnstHasuRes } = pbnst;
const [bdstTable, setbdstTable] = useState<pubHasuType[]>();
const rightRef = useRef<pubHasuType[] | null>([] as pubHasuType[]);
const leftRef = useRef<pubHasuType[] | null>([] as pubHasuType[]);
const [rightData, setRightData] = useState<pubHasuType[]>();
const [inputVal, setInputVal] = useState<string>("");
useEffect(()=>{
console.log("searchFclts", searchFclts);
if(Array.isArray(searchFclts)){
const filteredData = searchFclts.filter((item) => {
if(rightData){
if(rightData.find(rightItem =>rightItem.bscFctCd===item.bscFctCd))
return false;
}
return true
})
setbdstTable(Array.isArray(filteredData) ? [...filteredData] : [])
}
}, [searchFclts])
useEffect(() => {
if (code.sggCd.length > 0 && pbnstHasuRes.length > 0) {
setbdstTable(tableData(pbnstHasuRes, { code }));
console.log('bdstTable ', bdstTable)
}
}, [code, pbnstHasuRes]);
useEffect(() => {
console.log('rightData ', rightData)
if(onRightSet)
onRightSet(
(rightData as pubHasuType[])?.map(item => ({
swtrmtFcltFk: item.bscFctCd, // 임시값, 실제 데이터에 맞게 수정
orgwtWtritQnt: Number(item.fctCpc), // 임시값, 실제 데이터에 맞게 수정
tmsYn: "N", // 임시값, 실제 데이터에 맞게 수정
dschgFmYn: "N", // 임시값, 실제 데이터에 맞게 수정
dtlPstnExpln: item.addr, // 임시값, 실제 데이터에 맞게 수정
choose: false, // 임시값, 실제 데이터에 맞게 수정
// 필요한 경우 다른 속성들도 추가
})) as unknown as fcOrgwtType[]
);
}, [rightData])
return (
<>
<div className="flex flex-col h-full">
<div className="flex items-center justify-between">
<Title title={"취수하수처리장 추가"} />
<Button className="m-2" size={"sm"}
onClick={() => {closeModal && closeModal(false) }}>저장</Button>
</div>
<div className="flex flex-row flex-grow gap-4 mt-4">
<div className="w-3/7">
<div className="flex items-center justify-between">
<Label className="w-full text-2xl text-gray-600 text-left">공공하수처리시설 목록</Label>
<div className="flex items-center">
<Label className="w-full text-xl text-gray-600 text-right">시설명</Label>
<Input className="w-1/2 !text-xl ml-2" type="text" value={inputVal} onChange={(e) => {setInputVal(e.target.value)}} />
<Button className="m-2" size={"sm"} onClick={() => getSwtrmtFcltNm(inputVal)}>조회</Button>
</div>
</div>
<div className="border-t border-gray-300 my-4"></div>
<RuseMngTable
tableData={bdstTable || []}
headList={fcOrgwtsHeadList}
upHeadList={[]} // TODO: Replace with actual upHeadList data if available
ref={leftRef}
/>
</div>
<div className="flex flex-col items-center justify-center w-1/7 gap-2">
<ArrowRight
type="button"
onClick={() => {
if (leftRef && typeof leftRef === "object" && "current" in leftRef) {
const checked = leftRef.current?.filter((item) => ((item as unknown as pubHasuType)?.choose))
const unchecked = leftRef.current?.filter((item) => !(item as unknown as pubHasuType)?.choose)
if (checked && checked.length > 0) {
checked.forEach(item=>(item as unknown as pubHasuType).choose = false)
setRightData([...(checked as unknown as pubHasuType[]),
...((rightRef.current ?? []) as unknown as pubHasuType[])
])
}
if (unchecked && unchecked.length > 0) {
setbdstTable([...(unchecked as unknown as pubHasuType[])])
}else{
setbdstTable([])
}
}
}
}
className="w-12 h-12 text-gray-500 cursor-pointer"
/>
<ArrowLeft
type="button"
onClick={() => {
if (rightRef && typeof rightRef === "object" && "current" in rightRef) {
const checked = rightRef.current?.filter((item) => ((item as unknown as pubHasuType)?.choose))
const unchecked = rightRef.current?.filter((item) => !(item as unknown as pubHasuType)?.choose)
if (checked && checked.length > 0) {
checked.forEach(item=>(item as unknown as pubHasuType).choose = false)
setbdstTable([
...(checked as unknown as pubHasuType[]),
...((leftRef.current ?? []) as unknown as pubHasuType[])
])
}
if (unchecked && unchecked.length > 0) {
setRightData([...(unchecked as unknown as pubHasuType[])])
}else{
setRightData([])
}
}
}}
className="w-12 h-12 text-gray-500 cursor-pointer mt-5" />
</div>
{/* Right Table container: Takes 2/5 of the width */}
<div className="w-3/7">
<Label className="w-full text-2xl text-gray-600 text-right">취수하수처리장 목록</Label>
<div className="border-t border-gray-300 my-4"></div>
<RuseMngTable
tableData={rightData || []}
headList={fcOrgwtsHeadList}
upHeadList={[]} // TODO: Replace with actual upHeadList data if available
ref={rightRef}
/>
</div>
</div>
</div>
</>
);
}
@@ -0,0 +1,90 @@
"use client";
import { useWrStore } from "@/app/store";
import type {
customPbnst,
fcBizType,
pubHasuType,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { PbnstMngForm } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm";
import { ScrollArea } from "@common/business_components";
import {
Button,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@common/components/ui";
import { useEffect, useState } from "react";
import { PbnstBuseForm } from "@/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm";
export function PbnstRegModal({ data }: { data: pubHasuType }) {
const { pbnst, pbnstInsert } = useWrStore((state) => {
return state;
});
const [pbnstBdstForm, setPbnstBdstForm] = useState<customPbnst>(
{} as customPbnst
);
const [currentTab, setCurrentTab] = useState<string>("mng");
const pbnstRegTotal = () => {
if (pbnst.pbnstMngRegData[0]) pbnstInsert(pbnst.pbnstMngRegData[0]);
};
const handleTabChange = (value: string) => {
setCurrentTab(value);
};
useEffect(() => {
if (data) {
let regData = {
swtrmtFcltFk: String(data.bscFctCd),
pbnstRuseFcltPk: String(data.bscFctCd + "1"),
fcBizs: [
{
pbnstRuseFcltFk: String(data.bscFctCd + "1"),
cycl: 1,
} as unknown as fcBizType,
],
};
setPbnstBdstForm(regData as unknown as customPbnst);
}
}, [data]);
useEffect(() => {}, [pbnstBdstForm]);
useEffect(() => {}, [pbnst.pbnstMngRegData]);
return (
<>
<h1 className="text-2xl font-medium text-label pl-8">
하수재이용 시설 신규등록
</h1>
<div className="border-t border-gray-300 my-4"></div>
<ScrollArea className="h-[75vh]">
<Tabs defaultValue="mng" onValueChange={handleTabChange}>
<TabsList className="">
<TabsTrigger className="!text-xl" value="mng">
시설정보
</TabsTrigger>
<TabsTrigger className="!text-xl" value="buse">
차수별정보
</TabsTrigger>
<Button
onClick={pbnstRegTotal}
size={"sm"}
className="!justify-end"
>
저장
</Button>
</TabsList>
<TabsContent className="max-h-full" value="mng">
<PbnstMngForm formType={"reg"} firstFcltData={pbnstBdstForm} />
</TabsContent>
<TabsContent className="max-h-full" value="buse">
<PbnstBuseForm formType={"reg"} firstBizData={pbnstBdstForm.fcBizs} />
</TabsContent>
</Tabs>
</ScrollArea>
</>
);
}
@@ -0,0 +1,92 @@
import { useWrStore } from '@/app/store';
import type { PbnstDemandComType } from '@/app/store/ruseFcltMng/fcltReg/pbnstReg/type';
import type { DemandCompType } from '@common/business_components/ui';
import React, { useCallback, useEffect, useRef, useState } from 'react';
type thisProps = {
label: string | undefined;
initValue: string | undefined;
setValue : (dmdCoNm?: string) => void;
setRtnData : (rtnData: PbnstDemandComType[]) => void;
}
export default function SearchInputText({label=undefined, initValue=undefined, setValue=()=>{}, setRtnData=()=>{}} :thisProps) {
const { demandCompList } = useWrStore((state) => state);
const [isOpen, setIsOpen] = useState(false);
const [textValue, setTextValue] = useState(initValue || '');
const wrapperRef = useRef<HTMLDivElement>(null);
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setTextValue(value);
setValue(value);
if (value) {
setIsOpen(true);
} else {
setIsOpen(false);
}
}, [ setValue]);
const handleSuggestionClick = useCallback((dmdCoNm: string) => {
setTextValue(dmdCoNm);
setValue(dmdCoNm);
setIsOpen(false);
setRtnData(demandCompList);
}, [demandCompList, setRtnData, setValue]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
return (
<div ref={wrapperRef}>
<input
type="text"
value={textValue}
onChange={handleInputChange}
/* onFocus={() => setIsOpen(true)} */
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder={`${label}을(를) 입력하세요`}
/>
{isOpen && (
<div className=" z-1 w-full mt-2 bg-white border border-gray-300 rounded-md shadow-lg h-90 overflow-auto">
<div className="max-h-72">
{demandCompList && demandCompList.length > 0 ? demandCompList.map((item : DemandCompType, index : number) => (
<div
key={index}
className="!text-xl w-full text-left px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto"
onClick={() => handleSuggestionClick(item.dmdCoNm)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleSuggestionClick(item.dmdCoNm);
}
}}
>
{item.dmdInfoPk.substring(0,10)}&nbsp;<span className="float-right">{item.dmdCoNm}</span>
</div>
)) : (
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
검색 결과가 없습니다.
</div>
)}
</div>
</div>
)}
</div>
)
}
@@ -1,51 +1,151 @@
import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead";
import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList";
import type { TableUpperProps } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { Input, Table, TableBody, TableCell, TableHeader, TableRow } from "@common/components/ui";
import type { customPbnst as OriginalCustomPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
export default function RuseMngTable({
tableData,
headList,
upHeadList,
}: {
import { selectClass, type TableUpperProps } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { DatePicker, SelectBox, type SelectDataType } from "@common/business_components/ui";
import {
Checkbox,
Input,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@common/components/ui";
import React, { useEffect, useState } from "react";
type RuseMngTableProps = {
tableData: any[];
headList: TableUpperProps[];
upHeadList: chHeadType[];
}) {
return (
<>
<Table variant="secondary">
<TableHeader className="font-bold">
{headMakeprfmnc(headList, upHeadList)}
</TableHeader>
code?: { name: string, val: SelectDataType[] }[];
onTableSet?:(item:any)=> void;
};
<TableBody>
{tableData.map((item, index) => (
<TableRow key={index}>
{headList.map((head, index) => (
<TableCell
key={`${head.id}-${index}`}
className="text-center whitespace-nowrap"
onClick={() => {}}
>
{head.edit === true ? (
<Input
className={`${head.type === "number" ? "text-right" : "text-center"} !text-xl`}
type={head.type}
onChange={(e) => {
;
}}
value={item[head.id] || ""}
/>
) : (
`${item[head.id] || ""}`
)}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</>
);
}
const RuseMngTable = React.forwardRef<any, RuseMngTableProps>(
({
tableData,
headList,
upHeadList,
code,
onTableSet
}, ref) => {
const [tableState, setTableState] = useState([...tableData]);
useEffect(() => {
if (ref && typeof ref === "object" && "current" in ref) {
ref.current = tableState;
}
if(onTableSet)
onTableSet (tableState)
}, [tableState,onTableSet]);
useEffect(() => {
if(tableData)
setTableState(tableData);
}, [tableData]);
return (
<>
<Table variant="secondary">
<TableHeader className="font-bold">
{upHeadList ? (
headMakeprfmnc(headList, upHeadList)
) : (
<TableRow key={"table-row-test"}>
{headList.map((head) => (
<TableHead key={"table-head-row " + head.id} className={`border border-gray-600 px-2 py-1 `}>
{head.title}
</TableHead>
))}
</TableRow>
)}
</TableHeader>
<TableBody>
{tableState.map((item, i) => (
<TableRow key={i}>
{headList.map((head, j) => (
<TableCell
key={`${head.id}-${j}`}
className="text-center whitespace-nowrap"
onClick={() => { }}
>
{head.edit === true ? (
<Input
className={`${head.type === "number" ? "text-right" : "text-center"} !text-xl`}
type={head.type}
onChange={(txt) => {
setTableState(prev => {
const next = [...prev];
next[i] = { ...next[i], [head.id]: txt.target.value };
return next;
})
}}
value={head.type === "number" ?
Number(tableState[i][head.id])||0 : tableState[i][head.id] || ""
}
/>
) : head.type === "select" ? (
<SelectBox
label=""
selectArray={code.find(item => item.name === head.selectData)?.val ?? []}
value={
tableState[i][head.id] || ""
}
className={""}
selectClass={selectClass}
onSelectValue={(val) => {
setTableState((prev) => {
prev[i][head.id] = val
return [...prev]
})
}}
></SelectBox>
) : head.type === "date" ? (
<DatePicker
mode="single"
field={{
value:
tableState[i][head.id], // Provide a default Date value
onChange: (date) => {
setTableState(prev => {
const next = [...prev];
next[i] = { ...next[i], [head.id]: date };
return next;
})
}
}
}
selectClass={"w-full"}
/>
) : head.type === "checkbox" ? (
<Checkbox
className=""
checked={!!tableState[i][head.id]}
onCheckedChange={(checked) => {
console.log("checked", checked);
setTableState((prev) => {
prev[i][head.id] = checked
return [...prev]
})
}}
>
</Checkbox>
) :
(
`${item[head.id] || ""}`
)}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</>
);
})
export default RuseMngTable;
+12 -10
View File
@@ -2,10 +2,9 @@ import { NextRequest, NextResponse } from "next/server";
import { CS_URL } from "./components/lib/config";
//import { CLAIM_NAME, AUTH_USER_ID } from "./src/shared/config";
const CLAIM_NAME = "Authorization"
const AUTH_USER_ID = "token-id"
const serverApiUrl =
CS_URL;
const CLAIM_NAME = "Authorization";
const AUTH_USER_ID = "token-id";
const serverApiUrl = CS_URL;
export function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname;
console.log(`[Middleware] Request URL: ${req.url}`);
@@ -13,29 +12,32 @@ export function middleware(req: NextRequest) {
if (pathname.startsWith("/server")) {
const queryParams = searchParams.toString();
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${queryParams ? `?${queryParams}` : ""
}`;
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
queryParams ? `?${queryParams}` : ""
}`;
console.log(`[Middleware] Rewriting from '/server' path to: ${newUrl}`);
return NextResponse.rewrite(new URL(newUrl));
}
if (pathname.startsWith("/attach-service")) {
const requestHeaders = new Headers(req.headers);
const token = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhMGVjN2EwNy03NTJhLTRmN2YtODlhNi04NTRlNjNkMmUzYTAiLCJpYXQiOjE3NDc4MTM2MDksImV4cCI6MTc0NzgxMzkwOX0.XGiDHC-ljuEdN6M638q0nDet6XRQcO8o66LKvZZ07r4rGykn2ba8yagC_-8LVpVjrkPzIVZnW10I2fQWgAGT5Q"
const userId = 'a0ec7a07-752a-4f7f-89a6-854e63d2e3a0';
const token =
"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhMGVjN2EwNy03NTJhLTRmN2YtODlhNi04NTRlNjNkMmUzYTAiLCJhdXRob3JpdGllcyI6IlJPTEVfQURNSU4sUk9MRV9BTk9OWU1PVVMiLCJtbmdyWW4iOiJZIiwiZXhwIjoxNzQ4ODQ4NDkxfQ.ZiYpKxAMyyoPuUquxsWxfQbMBzYN4tsa7itZ49_OEsdGvbUUmKzrIV6Or7VVbEjtAqhh7W97nqB5EP6GRzFC9Q";
const userId = "a0ec7a07-752a-4f7f-89a6-854e63d2e3a0";
//const token = req.cookies.get(CLAIM_NAME)?.value;
//const userId = req.cookies.get(AUTH_USER_ID)?.value;
const newUrl = `${serverApiUrl}${pathname}`;
console.log("serverApiUrl ", serverApiUrl);
console.log("pathname ", pathname);
//wr:dev: pathname /attach-service/api/v1/tus/upload/5e60b70a-956e-40c3-be5a-cdd3100bf8ad
//http://dev.onasys.co.kr:8000/attach-service/api/v1/tus/upload/5e60b70a-956e-40c3-be5a-cdd3100bf8ad
if (token) requestHeaders.set(CLAIM_NAME, token);
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
console.log(
`[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`,
`[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`
);
return NextResponse.rewrite(new URL(newUrl), {
headers: requestHeaders,
+1
View File
@@ -17,6 +17,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.474.0",
"moment": "^2.30.1",
"next": "15.1.7",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.5.2",
Binary file not shown.
Binary file not shown.
@@ -64,10 +64,10 @@ const CustomLegend = (props: any) => {
const { payload } = props;
return (
<div style={customStyles.legendContainer}>
{payload.map((entry, index: number) => (
{payload.map((entry: unknown, index: number) => (
<div key={`legend-${index}`} className="flex items-center gap-2">
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: entry.color }}></div>
<span className="text-[12px]">{entry.value}</span>
<div className="w-[14px] h-[14px] ml-10" style={{ backgroundColor: (entry as {color: string}).color }}></div>
<span className="text-[12px]">{(entry as {value: string}).value}</span>
</div>
))}
</div>
@@ -113,20 +113,21 @@ export default function Linechart({
}, []);
// 데이터의 최대값 계산
const getMaxValue = (data: ChartDataType, keyPrefix: string) => {
return Math.max(...Object.values(data).map(item =>
Object.keys(item)
.filter(key => key.includes(keyPrefix))
.map(key => item[key] as unknown as number)
).flat());
const getMaxValue = (data: ChartDataType): number => {
return Math.max(...Object.values(data).flatMap(dataset =>
dataset.map(entry => {
const val = parseFloat(entry.val);
return isNaN(val) ? 0 : val;
})
));
};
const lineMaxValue = getMaxValue(chartData, 'lineVal');
const lineMaxValue = getMaxValue(chartData);
return (
<div className={chartClassName}>
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
<div style={{ textAlign: "left", fontSize: "13px", fontWeight: "bold", marginTop:"13px", marginBottom: "10px" }}>
<div className="text-left text-[13px] font-bold mt-[13px] mb-[10px]">
{chartTitle}
</div>
) : null}
@@ -1,5 +1,6 @@
import type { sheetParameter } from '@/index';
// import type { sheetParameter } from '@/index';
import * as XlsxPopulate from '@xlsx/xlsx-populate';
import type { SheetParameter } from './index';
@@ -38,13 +39,13 @@ export const excelStyle = async (data: ArrayBuffer, range: string = "A1:V3", fil
}
export const excelArrayStyle = async (data: ArrayBuffer, range: Array<typeof sheetParameter>, fileNm:string) => {
export const excelArrayStyle = async (data: ArrayBuffer, range: SheetParameter[], fileNm: string) => {
const workbook = await XlsxPopulate.Workbook.fromData(data);
if(range){
range.forEach((item , index)=>{
if (range) {
range.forEach((item, index) => {
const rangeCells = workbook.activeSheet(index)?.sheet(index)?.range(item.headerRange);
console.log(rangeCells);
rangeCells?.style((styleQ)=>{
rangeCells?.style((styleQ) => {
makeCellStyle(styleQ)
});
@@ -5,7 +5,7 @@ type ExcelDownloadProps = {
data: JSON | JSON[];
fileName: string;
sheetNameArray: string[];
headerRange: string;
headerRange?: string;
}
export const downloadExcel = ({ data, fileName = new Date().getTime().toString() + '.xlsx', sheetNameArray = ["Sheet1"], headerRange='A1:V3'}: ExcelDownloadProps) => {
@@ -15,17 +15,21 @@ export const downloadExcel = ({ data, fileName = new Date().getTime().toString()
excelStyle(XLSX.write(workbook, { bookType: "xlsx", type: "buffer" }), headerRange, fileName);
};
export const sheetParameter ={
data:[[]], headerNum:0, headerRange:'A1:V3', sheetName:'Sheet1', startCol:0
}
export type SheetParameter = {
data: any[][];
headerNum: number;
headerRange: string;
sheetName: string;
startCol?: string;
};
export const downloadMultipleExcel = (fileName = new Date().getTime().toString() + '.xlsx', sheetArray: Array<typeof sheetParameter>): Promise<JSON | undefined> => {
export const downloadMultipleExcel = (fileName = new Date().getTime().toString() + '.xlsx', sheetArray: SheetParameter[]): Promise<JSON | undefined> => {
const workbook = XLSX.utils.book_new();
for (let i = 0; i < sheetArray.length; i++) {
// Create a new worksheet for each sheet
const worksheet = XLSX.utils.aoa_to_sheet(sheetArray[i]?.data || [[]]);
// Add data to the worksheet starting from the specified header number
if (sheetArray&&sheetArray[i]?.headerNum) {
if (sheetArray && sheetArray[i]?.headerNum) {
XLSX.utils.sheet_add_aoa(worksheet, sheetArray[i]?.data || [[]], { origin: `A${sheetArray[i]?.headerNum}` });
}
// Append the worksheet to the workbook with the specified sheet name
@@ -37,7 +41,6 @@ export const downloadMultipleExcel = (fileName = new Date().getTime().toString()
return Promise.resolve(undefined);
};
export const filePicked=(oFile: File, headerNum:number=0, dateType="yyyymmdd"):Promise<JSON | undefined> =>{
// Create A File Reader HTML5
const reader = new FileReader();
@@ -80,8 +83,6 @@ export const filePicked=(oFile: File, headerNum:number=0, dateType="yyyymmdd"):P
});
}
export const filePickedArray=(oFile: File, headerNum:number=0):Promise<JSON[] | undefined> =>{
// Create A File Reader HTML5
const reader = new FileReader();
@@ -123,8 +124,6 @@ export const filePickedArray=(oFile: File, headerNum:number=0):Promise<JSON[] |
});
}
export const filePickedBlob=(oFile: ArrayBuffer, data:[][],fileNm:string, headerNum:number=0, headerRange='A1:V3', startCol?:number):Promise<JSON | undefined> =>{ // Create A File Reader HTML5
return new Promise<JSON | undefined>(() => {
// Ready The Event For When A File Gets Selected
@@ -149,28 +148,20 @@ export const filePickedBlob=(oFile: ArrayBuffer, data:[][],fileNm:string, header
});
}
export const filePickedBlobArray= (oFile: ArrayBuffer,fileNm:string, sheetArray: [typeof sheetParameter]):Promise<JSON | undefined> =>{ // Create A File Reader HTML5
return new Promise<JSON | undefined> (async () => {
// Ready The Event For When A File Gets Selected
const wb = XLSX.read(oFile, { type: 'binary' });
// Loop Over Each Sheet
wb.SheetNames.forEach( function (sheetName, index) {
// Obtain The Current Row As CSV
if(sheetArray[index]){
const sheet = wb.Sheets[sheetName];
if (sheet) {
//오리진 확인 test 필요
// const sheet = XLSX.utils.json_to_sheet(data);
const startCol = sheetArray[index].startCol?sheetArray[index].startCol.toString():'A';
XLSX.utils.sheet_add_aoa(sheet, sheetArray[index].data, { origin: startCol+sheetArray[index].headerNum });
// XLSX.utils.book_append_sheet(wb, sheet, 'test');
} else {
console.error(`Sheet "${sheetName}" is undefined.`);
}
}
});
await excelArrayStyle(XLSX.write(wb, { bookType: "xlsx", type: "buffer" }), sheetArray, fileNm)
});
}
export const filePickedBlobArray = (oFile: ArrayBuffer, fileNm: string, sheetArray: SheetParameter[]): Promise<JSON | undefined> => {
return new Promise<JSON | undefined>(async () => {
const wb = XLSX.read(oFile, { type: 'binary' });
wb.SheetNames.forEach(function (sheetName, index) {
if (sheetArray[index]) {
const sheet = wb.Sheets[sheetName];
if (sheet) {
const startCol = sheetArray[index].startCol ? sheetArray[index].startCol : 'A';
XLSX.utils.sheet_add_aoa(sheet, sheetArray[index].data, { origin: startCol + sheetArray[index].headerNum });
} else {
console.error(`Sheet "${sheetName}" is undefined.`);
}
}
});
await excelArrayStyle(XLSX.write(wb, { bookType: "xlsx", type: "buffer" }), sheetArray, fileNm)
});
}
@@ -12,10 +12,12 @@ const dropZoneConfig = {
files: File[]; // 파일 배열
setFiles: (files: File[] | null) => void; // 파일 배열 세터
fileTypes?: string; // 옵션 (파일 확장자 문자열)
onFileClick?:(index:number)=> void; // 파일 클릭 핸들러
}
export const FileUpload =
({ files, setFiles, fileTypes }: FileUploadProps) => {
({ files, setFiles, fileTypes ,onFileClick}: FileUploadProps) => {
return (
<FileUploader
value={files}
@@ -43,9 +45,9 @@ export const FileUpload =
{files&&
files.length > 0 &&
files.map((file:File, i:number) => (
<FileUploaderItem key={i} index={i}>
<FileUploaderItem key={i} index={i} onFileClick={onFileClick }>
<Paperclip className="h-4 w-4 stroke-current" />
<span>{file.name}</span>
<span className="w-4/5 overflow-hidden">{file.name}</span>
</FileUploaderItem>
))}
</FileUploaderContent>
@@ -271,7 +271,7 @@ export const FileUploaderContent = forwardRef<
{...props}
ref={ref}
className={cn(
"flex rounded-xl gap-1",
"w-full flex rounded-xl gap-1",
orientation === "horizontal" ? "flex-raw flex-wrap" : "flex-col",
className,
)}
@@ -286,31 +286,37 @@ FileUploaderContent.displayName = "FileUploaderContent";
export const FileUploaderItem = forwardRef<
HTMLDivElement,
{ index: number } & React.HTMLAttributes<HTMLDivElement>
>(({ className, index, children, ...props }, ref) => {
const { removeFileFromSet, activeIndex, direction } = useFileUpload();
{ index: number } & React.HTMLAttributes<HTMLDivElement> &{onFileClick?:(index:number)=>void}
>(({ className, index, children, onFileClick, ...props }, ref) => {
const { removeFileFromSet , activeIndex, direction } = useFileUpload();
const isSelected = index === activeIndex;
return (
<div
ref={ref}
className={cn(
buttonVariants({ variant: "ghost" }),
"h-6 p-1 justify-between cursor-pointer relative",
"h-6 p-1 justify-between cursor-pointer relative w-full",
className,
isSelected ? "bg-muted" : "",
)}
{...props}
>
<div className="font-medium leading-none tracking-tight flex items-center gap-1.5 h-full w-full">
<div className="font-medium leading-none tracking-tight flex items-center gap-1.5 h-full !w-4/5 overflow-hidden "
onClick={() =>{onFileClick?.(index)
}}>
{children}
</div>
<button
type="button"
className={cn(
"absolute",
"!w-1/5 z-50",
direction === "rtl" ? "top-1 left-1" : "top-1 right-1",
)}
onClick={() => removeFileFromSet(index)}
onClick={() =>{
removeFileFromSet(index)}}
>
<span className="sr-only">remove item {index}</span>
<RemoveIcon className="w-4 h-4 hover:stroke-destructive duration-200 ease-in-out" />
@@ -1,19 +1,70 @@
import React from "react";
import React, { useCallback, useEffect, useState } from "react";
import { FormControl, FormItem, FormLabel, FormMessage } from "../form";
import { Button, Checkbox } from "@common/components/ui";
const FormUi = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {label:string|undefined}
>(({children, label},ref)=>{
export const BZNM_TOT_NO = '종사업자등록번호';
export const CANDIDATE = '공급처 후보지 선택(필수아님)';
interface FormUiProps extends React.HTMLAttributes<HTMLDivElement> {
label?: string;
onCheckboxChange?: (checked: boolean) => void;
isDisabled?: boolean;
}
interface FormElementProps extends React.HTMLAttributes<HTMLElement> {
disabled?: boolean;
}
const FormUi = React.forwardRef<HTMLDivElement, FormUiProps>(
({ children, label, onCheckboxChange, isDisabled }, ref) => {
const [isChecked, setIsChecked] = useState(isDisabled);
useEffect(() => {
setIsChecked(isDisabled);
}, [isDisabled]);
const handleCheckboxChange = useCallback((checked: boolean) => {
setIsChecked(checked);
onCheckboxChange?.(checked);
}, [onCheckboxChange]);
return (
<FormItem ref={ref}>
{label?<FormLabel className="text-xl">{label}</FormLabel>:<></>}
<FormControl>
{children}
</FormControl>
<FormMessage />
</FormItem>
)
})
<FormItem ref={ref}>
{label && <FormLabel className="text-xl">{label}</FormLabel>}
{label === BZNM_TOT_NO && (
<>
<div className="float-right">
<div className="flex items-center gap-2">
<Checkbox
id="check"
checked={isChecked}
onCheckedChange={handleCheckboxChange}
/>
<label className="w-20 text-xl text-gray-600" htmlFor="check">해당없음</label>
</div>
</div>
{children}
</>
)}
{label === CANDIDATE && (
<FormControl>
{children}
</FormControl>
)}
{label !== BZNM_TOT_NO && label !== CANDIDATE && (
<FormControl>
{React.isValidElement(children) && isDisabled
? React.cloneElement(children, {
disabled: isDisabled,
...(children.props as FormElementProps)
})
: children}
</FormControl>
)}
<FormMessage />
</FormItem>
);
}
);
FormUi.displayName = 'FormUi';
export { FormUi };
export { FormUi };
@@ -19,4 +19,4 @@ export * from "./tab";
export * from "./table";
export * from "./table_div";
export * from "./title";
export * from "./sdmInsertForm";
@@ -0,0 +1,287 @@
import { FormField } from "../form";
import { SelectBox } from "../select";
import { Input } from "@common/components";
import { FormUi } from "../form_ui";
import { useCallback, useState } from "react";
import type { ControllerRenderProps, UseFormReturn } from "react-hook-form";
import { SdmSearchForm } from "./sdmNameBox";
import type { DemandCompType } from "../../../../../../apps/dm/app/store/sdmMng";
type SelectDataType = {
text: string;
val: string;
};
interface FormSchema {
ctpvCd: string;
sggCd: string;
dmdCoNm: string;
dmdInfoPk: string;
daddr: string;
telno: string;
bznmTotNo?: string;
rgtr?: string;
dmdAplyPswd?: string;
}
interface SdmRegFormProps {
form: UseFormReturn<FormSchema>;
onCheckboxChange?: (checked: boolean) => void;
selectSidoData: SelectDataType[];
selectSigunData: SelectDataType[];
dataList: DemandCompType[];
rtnValue: (searchText: string, pageCnt: number) => void;
setIsDuplicate: (value: boolean) => void;
}
type FieldComponent = (field: ControllerRenderProps<FormSchema, keyof FormSchema>) => JSX.Element;
export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigunData, dataList, rtnValue, setIsDuplicate }: SdmRegFormProps) {
const [sidoValue, setSidoValue] = useState<string>('');
const [sigunValue, setSigunValue] = useState<string>('');
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(true);
const [orgBznmNo, setOrgBznmNo] = useState<string>('');
const initializeSigunData = useCallback((sidoCd: string) => {
if (sidoCd === '0') {
setSigunValue('0');
setFilteredSigunData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectSigunData.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd)
];
setSigunValue('0');
setFilteredSigunData(newFilteredData);
}
}, [selectSigunData]);
const onSelectValue = useCallback((
val: string,
type: string,
field: ControllerRenderProps<FormSchema, keyof FormSchema> | undefined
) => {
switch (type) {
case "ctpvCd":
initializeSigunData(val);
setSidoValue(val);
field?.onChange(val);
form.setValue('dmdInfoPk', '');
form.setValue('bznmTotNo', '');
break;
case "sggCd":
setSigunValue(val);
field?.onChange(val);
form.setValue('dmdInfoPk', '');
form.setValue('bznmTotNo', '');
break;
}
}, [initializeSigunData, form]);
const handleCheckboxChange = useCallback((checked: boolean) => {
setIsBznmTotNoDisabled(checked);
onCheckboxChange?.(checked);
}, [onCheckboxChange]);
const setRtnValue = useCallback((searchText: string, pageCnt: number) => {
rtnValue(searchText, pageCnt);
}, [rtnValue]);
const setRtnData = useCallback((item: DemandCompType) => {
const rtnCompData = item;
form.setValue('dmdCoNm', rtnCompData?.dmdCoNm || '');
form.setValue('dmdInfoPk', rtnCompData?.dmdInfoPk.substring(0,10) || '');
form.setValue('bznmTotNo', rtnCompData?.dmdInfoPk.substring(10,14)==='0000' ? '' : rtnCompData?.dmdInfoPk.substring(10,14));
// 수요처 불러온 후 사업자번호를 바꿀경우를 대비함
setOrgBznmNo(rtnCompData?.dmdInfoPk);
const sidoCd = rtnCompData?.ctpvCd.trim() || '';
setSidoValue(sidoCd);
form.setValue('ctpvCd', sidoCd);
initializeSigunData(sidoCd);
setTimeout(() => {
const sigunCd = rtnCompData?.sggCd.trim() || '';
setSigunValue(sigunCd);
form.setValue('sggCd', sigunCd);
}, 100);
if(rtnCompData?.dmdInfoPk.substring(10,14) === '0000') {
handleCheckboxChange(true);
} else {
handleCheckboxChange(false);
}
form.setValue('daddr', rtnCompData?.daddr || '');
form.setValue('telno', rtnCompData?.telno || '');
// 선택을 했을 때 중복 상태를 true로 설정
setIsDuplicate(true);
}, [form, handleCheckboxChange, initializeSigunData, setIsDuplicate]);
const setRtnText = useCallback((text: string) => {
form.setValue('dmdCoNm', text);
}, [form]);
const bznNoChange = useCallback((e: React.ChangeEvent<HTMLInputElement>, noType: string) => {
const value = e.target.value;
console.log('form value : '+form.getValues('dmdInfoPk'));
console.log('org value : '+orgBznmNo);
if(noType === 'dmdInfoPk') {
if(orgBznmNo.substring(0,10) === value) {
setIsDuplicate(true);
} else {
setIsDuplicate(false);
}
form.setValue('dmdInfoPk', value);
} else {
if(orgBznmNo.substring(10,14) === value) {
setIsDuplicate(true);
} else {
setIsDuplicate(false);
}
form.setValue('bznmTotNo', value);
}
}, [form, orgBznmNo, setIsDuplicate]);
const formFields: Array<{
name: keyof FormSchema;
label: string;
component: FieldComponent;
}> = [
{
name: "ctpvCd",
label: "시도",
component: (field) => (
<SelectBox
selectClass={undefined}
label=""
selectArray={selectSidoData}
onSelectValue={(val) => onSelectValue(val.toString(), 'ctpvCd', field)}
className=""
value={sidoValue}
/>
)
},
{
name: "sggCd",
label: "시군구",
component: (field) => (
<SelectBox
selectClass={undefined}
label=""
selectArray={filteredSigunData}
onSelectValue={(val) => onSelectValue(val.toString(), 'sggCd', field)}
className=""
value={sigunValue}
/>
)
},
{
name: "dmdCoNm",
label: "회사명",
component: (field) => (
<SdmSearchForm
rtnData={setRtnData}
label="수요처명"
initValue={''}
dataList={dataList}
rtnValue={setRtnValue}
rtnText={setRtnText}
{...field}
/>
)
},
{
name: "dmdInfoPk",
label: "사업자등록번호",
component: (field) => (
<Input
placeholder="사업자등록번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
{...field}
onChange={(e) => bznNoChange(e, 'dmdInfoPk')}
/>
)
},
{
name: "bznmTotNo",
label: "종사업자등록번호",
component: (field) => (
<FormUi
label="종사업자등록번호"
onCheckboxChange={handleCheckboxChange}
isDisabled={isBznmTotNoDisabled}
>
<Input
placeholder="여러사업장이 있을 경우 입력해주세요"
type="text"
className="!mt-5 !text-xl text-gray-600"
disabled={isBznmTotNoDisabled}
{...field}
onChange={(e) => bznNoChange(e, 'bznmTotNo')}
/>
</FormUi>
)
},
{
name: "daddr",
label: "사업자주소",
component: (field) => (
<Input
placeholder="주소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
{...field}
/>
)
},
{
name: "telno",
label: "전화번호",
component: (field) => (
<Input
placeholder="전화번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
{...field}
/>
)
},
{
name: "dmdAplyPswd",
label: "패스워드",
component: (field) => (
<Input
placeholder="패스워드를 입력하세요"
type="password"
className="!text-xl text-gray-600"
{...field}
/>
)
}
];
return (
<>
{formFields.map(({ name, label, component }) => (
<FormField
key={String(name)}
control={form.control}
name={name}
render={({ field }) => (
name === 'bznmTotNo' ? component(field) : (
<FormUi label={label}>
{component(field)}
</FormUi>
)
)}
/>
))}
</>
);
}
@@ -0,0 +1,221 @@
"use client";
/* import { useDmStore } from "@/app/store"; */
import { SdmRegForm } from "./form";
import { Button } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
/* import { useRouter } from "next/navigation"; */
import { useCallback, useEffect, useState } from "react";
import { FormProvider, useForm, type SubmitHandler } from "react-hook-form";
import { toast, Toaster } from "sonner";
/* import { Toaster, toast } from "react-hot-toast"; */
import * as z from "zod";
import type { SelectDataType } from "../search_input_select";
export type DemandCompType = {
dmdInfoPk : string | '';
bzmnNo : string | '';
dmdCoNm : string | '';
daddr : string | '';
telno : string | '';
rgtr : string | '';
useUsgCd : string | '';
ctpvCd : string | '';
sggCd : string | '';
dmdAplyPswd: string | '';
}
type thisProps = {
selectSidoData: SelectDataType[];
selectSigunData: SelectDataType[];
dataList: DemandCompType[];
rtnValue: (searchText: string, pageCnt: number) => void;
rtnInputData: (data: DemandCompType) => void;
bodyClassNm?: string;
}
const createFormSchema = (isBznmTotNoDisabled: boolean, isDuplicate: boolean) => z.object({
ctpvCd: z.string().min(1, "시/도를 선택해주세요"),
sggCd: z.string().min(1, "시/군/구를 선택해주세요"),
dmdCoNm: z.string()
.min(1, "회사명을 입력해주세요")
.max(20, "회사명은 20자 이내로 입력해주세요"),
dmdInfoPk: z.string()
.min(1, "사업자등록번호를 입력해주세요")
.regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다")
.refine(() => isBznmTotNoDisabled ? !isDuplicate : true, "이미 등록된 사업자등록번호입니다"),
bznmTotNo: isBznmTotNoDisabled
? z.string().optional()
: z.string()
.length(4, "종사업자등록번호는 4자리 숫자만 입력 가능합니다")
.refine(() => !isDuplicate, "이미 등록된 사업자등록번호입니다"),
daddr: z.string()
.min(1, "주소를 입력해주세요")
.min(6, "주소는 6자 이상 입력해주세요")
.max(40, "주소는 40자 이내로 입력해주세요"),
telno: z.string()
.min(8, "전화번호는 8자 이상 입력해주세요")
.max(12, "전화번호는 12자 이내로 입력해주세요")
.regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"),
rgtr: z.string().optional(),
dmdAplyPswd : z.string()
.min(1, "패스워드를 입력해주세요")
.min(8, "패스워드는 8자 이상 입력해주세요")
.max(16, "패스워드는 16자 이내로 입력해주세요"),
});
type FormSchema = ReturnType<typeof createFormSchema>;
export type FormData = z.infer<FormSchema>;
const defaultValues: FormData = {
ctpvCd: '',
sggCd: '',
dmdCoNm: '',
dmdInfoPk: '',
bznmTotNo: '',
daddr: '',
telno: '',
rgtr: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
dmdAplyPswd : '',
};
/**
* 수요처 등록 폼
* @param selectSidoData 시/도 데이터
* @param selectSigunData 시/군/구 데이터
* @param dataList 수요처 목록
* @param rtnValue 검색 결과(텍스트) 콜백
* @param rtnInputData 수요처 리스트 콜백
* @returns
*/
/**
* 페이지에서 작업
const {Common, initializeCommonData, getDemandCompList, insertSdmData, Sdm} = useDmStore((state) => state);
const { selectSidoData, selectSigunData } = Common;
const { demandCompList } = Sdm;
// 공통데이터 (시도, 시군구 데이터 호출)
useEffect(() => {
initializeCommonData();
}, [initializeCommonData]);
// 컴포넌트에서 텍스트 검색 호출
const callback = useCallback((searchText: string, pageCnt: number) => {
searchText = searchText.trim();
if(searchText.length > 0) {
getDemandCompList(searchText, pageCnt);
}
}, [getDemandCompList]);
// 컴포넌트에서 수요처 등록 호출
const handleInsertSdmData = useCallback((data: DemandCompType) => {
insertSdmData(data);
}, [insertSdmData]);
*/
export default function SdmInsertForm({selectSidoData, selectSigunData, dataList, rtnValue, rtnInputData,bodyClassNm}: thisProps) {
/* const { insertSdmData } = useDmStore((state) => state);
const router = useRouter(); */
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(true);
const [isDuplicate, setIsDuplicate] = useState(false);
const formSchema = createFormSchema(isBznmTotNoDisabled, isDuplicate);
const form = useForm<z.infer<FormSchema>>({
resolver: zodResolver(formSchema),
defaultValues,
mode: "onChange",
criteriaMode: "all",
shouldFocusError: false,
shouldUnregister: false
});
// 체크박스 상태가 변경될 때마다 bznmTotNo 필드의 에러를 초기화
useEffect(() => {
if (isBznmTotNoDisabled) {
form.clearErrors('bznmTotNo');
form.setValue('bznmTotNo', '');
}
}, [isBznmTotNoDisabled, form]);
const setRtnValue = useCallback((searchText: string, pageCnt: number) => {
rtnValue(searchText, pageCnt);
}, [rtnValue]);
const onSubmit: SubmitHandler<z.infer<FormSchema>> = async (data) => {
try {
// form 에러 체크
const formErrors = form.formState.errors;
if (Object.keys(formErrors).length > 0) {
const errorMessage = Object.values(formErrors)[0]?.message || '입력값을 확인해주세요.';
toast.error(errorMessage);
return;
}
const dmdInfoPk = data.dmdInfoPk + (data.bznmTotNo || '0000');
const formData = {
...data,
dmdInfoPk,
};
console.log(formData);
/* await insertSdmData(formData); */
rtnInputData(formData as DemandCompType);
const queryParams = new URLSearchParams({
dmdCoNm: data.dmdCoNm,
ctpvCd: data.ctpvCd,
sggCd: data.sggCd,
dmdInfoPk,
bznmTotNo: data.bznmTotNo || '',
daddr: data.daddr,
telno: data.telno,
});
//router.push(`/pages/sdmMng/sdmInfoInsert?${queryParams.toString()}`);
toast.success('수요처가 성공적으로 등록되었습니다.');
} catch (error) {
console.error('Error submitting form:', error);
toast.error('수요처 등록에 실패했습니다.');
}
};
return (
<>
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 underline">
수요처등록
</h1>
</div>
<div className= {bodyClassNm?bodyClassNm:"!max-w-3xl mx-auto bg-white rounded-lg shadow-md p-6"}>
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<SdmRegForm
form={form}
onCheckboxChange={setIsBznmTotNoDisabled}
selectSidoData={selectSidoData}
selectSigunData={selectSigunData}
dataList={dataList}
rtnValue={setRtnValue}
setIsDuplicate={setIsDuplicate}
/>
<div className="flex justify-center pt-4">
<Button
type="submit"
className="w-32 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200"
>
수요처등록
</Button>
</div>
</form>
</FormProvider>
</div>
<Toaster position="top-center" />
</>
);
}
@@ -0,0 +1,105 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import type { DemandCompType } from "../../../../../../apps/dm/app/store/sdmMng";
export type thisProps = {
label: string | undefined;
initValue: string | undefined;
dataList : DemandCompType[];
rtnValue : (searchText: string, pageCnt: number) => void;
rtnData : (item: DemandCompType) => void;
rtnText : (text: string) => void;
}
/**
* 수요처 검색 컴포넌트
* @param label 라벨
* @param initValue 초기값
* @param rtnValue 검색 결과 반환 함수
* @param dataList 검색할 때 넘기는 데이터 리스트
* @param rtnData 검색 결과 반환 함수
*/
export const SdmSearchForm = ({label=undefined, initValue=undefined, rtnValue=()=>{}, dataList=[], rtnData=()=>{}, rtnText=()=>{} } :thisProps) => {
const [isOpen, setIsOpen] = useState(false);
const [textValue, setTextValue] = useState(initValue || '');
const wrapperRef = useRef<HTMLDivElement>(null);
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setTextValue(value);
rtnValue(value, 10);
rtnText(value);
if (value) {
setIsOpen(true);
} else {
setIsOpen(false);
}
}, [rtnValue, rtnText]);
const handleSuggestionClick = useCallback((item: DemandCompType) => {
setTextValue(item.dmdCoNm);
rtnData(item);
setIsOpen(false);
}, [rtnData]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
return (
<div ref={wrapperRef}>
<input
type="text"
value={textValue}
onChange={handleInputChange}
/* onFocus={() => setIsOpen(true)} */
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder={`${label}을(를) 입력하세요`}
/>
{isOpen && (
<div className=" z-1 w-full mt-2 bg-white border border-gray-300 rounded-md shadow-lg h-90 overflow-auto">
<div className="max-h-72">
{dataList && dataList.length > 0 ? dataList.map((item : DemandCompType, index : number) => (
<div
key={index}
className="!text-xl w-full text-left px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto"
onClick={() => handleSuggestionClick(item)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleSuggestionClick(item);
}
}}
>
{item.dmdInfoPk.substring(10,14) !== '0000' ? (
<>
<span className="text-gray-500">사업자번호 : </span> {item.dmdInfoPk.substring(0,10)}&nbsp;
<span className="text-gray-500">종사업자번호 : </span> {item.dmdInfoPk.substring(10,14)}&nbsp;
</>
) : (
<>
<span className="text-gray-500">사업자번호 : </span> {item.dmdInfoPk.substring(0,10)}&nbsp;
</>
)}
<span className="float-right">{item.dmdCoNm}</span>
</div>
)) : (
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
검색 결과가 없습니다.
</div>
)}
</div>
</div>
)}
</div>
)
}
@@ -1,3 +1,5 @@
"use client"
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useState } from "react";
import { SearchFormLeft, type EnvPartSelectDataType } from "../search_form_left";
@@ -1,4 +1,6 @@
import React, { useEffect, useState } from 'react';
"use client"
import React, { useEffect, useState } from 'react';
import type { SelectDataType } from '../search_input_select';
import { SelectBox } from '../select';
@@ -6,37 +6,41 @@ interface TabItem {
label: string;
}
interface UITabProps extends React.HTMLAttributes<HTMLDivElement> {
interface UITabProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'dir'> {
defaultValue?: string;
tabList: TabItem[];
children: React.ReactNode;
onValueChange?: (value: string) => void;
}
const UITab = React.forwardRef<
const UITab = React.forwardRef<
HTMLDivElement,
UITabProps
>(({defaultValue, tabList, children, onValueChange})=>{
// Exclude 'dir' if it exists and is not of the correct type
return (
<Tabs
defaultValue={defaultValue || tabList[0]?.value}
className={"w-full space-x-1 border-b border-gray-200"}
onValueChange={onValueChange}
>
<TabsList className="flex space-x-1">
{tabList.map((tab) => (
<TabsTrigger
key={tab.value}
value={tab.value}
className="py-2 px-4 text-sm font-medium border-b-2 focus:outline-none data-[state=active]:border-blue-500 data-[state=active]:text-blue-600 data-[state=inactive]:border-transparent data-[state=inactive]:text-gray-500 hover:text-gray-700 hover:border-gray-300"
>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{children}
</Tabs>
)
})
>(({defaultValue, tabList, children, onValueChange, ...props}, ref) => {
return (
<Tabs
defaultValue={defaultValue || tabList[0]?.value}
className={"w-full space-x-1 border-b border-gray-200"}
onValueChange={onValueChange}
ref={ref}
{...props}
>
<TabsList className="flex space-x-1">
{tabList.map((tab) => (
<TabsTrigger
key={tab.value}
value={tab.value}
className="py-2 px-4 text-sm font-medium border-b-2 focus:outline-none data-[state=active]:border-blue-500 data-[state=active]:text-blue-600 data-[state=inactive]:border-transparent data-[state=inactive]:text-gray-500 hover:text-gray-700 hover:border-gray-300"
>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{children}
</Tabs>
);
});
UITab.displayName = 'UITab';
export { UITab };
@@ -1,3 +1,5 @@
"use client"
import { Table, TableBody, TableCell, TableHeader, TableRow } from "@common/components";
import { useEffect, useState } from "react";
import { HeadMakeColSpan } from "./HeadMakeColSpan";
@@ -8,7 +8,7 @@ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
className={cn("mx-auto flex w-full justify-center text-lg mt-2", className)}
{...props}
/>
)
@@ -42,7 +42,7 @@ type PaginationLinkProps = {
const PaginationLink = ({
className,
isActive,
size = "icon",
size = "default",
...props
}: PaginationLinkProps) => (
<a
@@ -52,6 +52,7 @@ const PaginationLink = ({
variant: isActive ? "outline" : "ghost",
size,
}),
"px-4 py-2 text-lg",
className
)}
{...props}
@@ -66,10 +67,10 @@ const PaginationPrevious = ({
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
className={cn("gap-2 pl-4 text-lg", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<ChevronLeft className="h-6 w-6" />
<span>Previous</span>
</PaginationLink>
)
@@ -82,11 +83,11 @@ const PaginationNext = ({
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
className={cn("gap-2 pr-4 text-lg", className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
<ChevronRight className="h-6 w-6" />
</PaginationLink>
)
PaginationNext.displayName = "PaginationNext"
@@ -97,10 +98,10 @@ const PaginationEllipsis = ({
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
className={cn("flex h-12 w-12 items-center justify-center text-lg", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<MoreHorizontal className="h-6 w-6" />
<span className="sr-only">More pages</span>
</span>
)
@@ -7,7 +7,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
return (
<input
className={cn(
"flex h-[3.2rem] w-full rounded-md border border-input bg-form px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-placeholder focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"flex h-[3.2rem] w-full rounded-md border border-input bg-form px-3 py-2 text-xl ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-placeholder focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-xl",
className,
)}
ref={ref}