Merge commit '7c920303aeea733ad77df5c8c2c510056a78cd30' into iipbs/wr/form_fuction

This commit is contained in:
jyha00
2025-05-28 13:11:18 +09:00
9 changed files with 331 additions and 128 deletions
@@ -45,12 +45,19 @@ export const pbnstDemandHeadListExport: TableUpperProps[] = [
export const pbnstOrgwtHeadListRxport: TableUpperProps[] = [
{ id: "ruseUtztnFcltNm", title: "운영방식" },
{ id: "ruseUtztnFcltNm", title: "운영방식" },
{ id: "orgwtWtritQnt", title: "원수취수량" },
{ id: "tmsYn", title: "하수처리량TMS 전후", },
{ id: "dschgFmYn", title: "하수처리장 방류 유량계 전·후​", },
{ id: "dtlPstnExpln", title: "상세위치 설명", },
];
export const fcOrgwtsHeadListExport: TableUpperProps[] = [
{ id: "sido", title: "운영방식" },
{ id: "gugun", title: "원수취수량" },
{ id: "bscFctNm", title: "시설명" },
{ id: "addr", title: "상세위치 설명" },
]
export const pbnstMngNmExport: chHeadType[] = [
{ headNm: "upChangeRe", text: "운영대행기간", length: 2 },
@@ -126,4 +133,3 @@ export const ruseWaterQulityList:formDetailType[]=[
]
@@ -16,6 +16,7 @@ import {
pubFacHeadListExport,
pplInstlsHeadListExport,
pbnstDemandHeadListExport,
fcOrgwtsHeadListExport,
} from "./headList";
const selectLabel: SelectDataType[] = [{ text: "test", val: "1" }];
@@ -25,6 +26,8 @@ const pbnstOrgwtHeadList = pbnstOrgwtHeadListRxport;
const pbnstMngNm = pbnstMngNmExport;
const pplInstlsHeadLisat = pplInstlsHeadListExport;
const pbnstDemandHeadList = pbnstDemandHeadListExport;
const fcOrgwtsHeadList=fcOrgwtsHeadListExport
export type pubFacState = {
pbnst: {
@@ -39,6 +42,7 @@ export type pubFacState = {
pbnstOrgwtHeadList: TableUpperProps[];
pplInstlsHeadLisat: TableUpperProps[];
pbnstDemandHeadList: TableUpperProps[];
fcOrgwtsHeadList: TableUpperProps[];
};
};
type pubFacActions = {
@@ -64,6 +68,7 @@ export const pubFacStateExport: pubFacState = {
selectLabel: selectLabel,
pplInstlsHeadLisat: pplInstlsHeadLisat,
pbnstDemandHeadList: pbnstDemandHeadList,
fcOrgwtsHeadList: fcOrgwtsHeadList,
},
};
@@ -206,6 +211,17 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
} catch (error) {
console.error("Error updating pbnst:", error);
}
},
},fileDelete(atflCd:string){
try {
const response = postFectch(
`/ratfl/attachments/atflCd/${atflCd}`,
null,
fetchType.COMMON
);
return response;
} catch (error) {
}
}
};
};
+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);
}
//
//
@@ -1,12 +1,16 @@
"use client";
import { SelectBox } from "@common/business_components";
import { FormUi, type SelectDataType } from "@common/business_components/ui";
import {
FormUi,
UiModal,
type SelectDataType,
} from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import { Button, Input, Label, TableHead, TableRow } from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { set, useFieldArray, useForm } from "react-hook-form";
import { use, useEffect, useRef, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
import { useWrStore } from "@/app/store";
import {
@@ -35,6 +39,7 @@ import {
type ppInstlType,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable";
import FcOrgwtsRegModal from "@/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal";
export function PbnstBuseForm({ data }: { data: fcBizType[] }) {
// const ref = useRef<ppInstlType[] | null>(data[0]?.pplInstls ?? null);
@@ -47,6 +52,10 @@ export function PbnstBuseForm({ data }: { data: fcBizType[] }) {
const [ppiSelectList, setPpiSelectList] =
useState<[{ name: string; val: SelectDataType[] }]>();
const [ppiYn, setPpiYn] = useState("Y");
const [ isFcOrgwts, setIsFcOrgwts] = useState<boolean>(false);
const form = useForm<fcBizType>({
resolver: zodResolver(fcBizSchema),
});
@@ -66,6 +75,8 @@ export function PbnstBuseForm({ data }: { data: fcBizType[] }) {
name: "pplInstls",
});
const plusHist = () => {
append({
choose: false,
@@ -140,7 +151,7 @@ export function PbnstBuseForm({ data }: { data: fcBizType[] }) {
}}
></SelectBox>
<Button size={commonButonSize}>저장</Button>
<Button type ="submit" size={commonButonSize}>저장</Button>
</div>
</div>
<MngRegFormDetail
@@ -182,7 +193,7 @@ export function PbnstBuseForm({ data }: { data: fcBizType[] }) {
<h1 className={formH1Class}>재이용 원수취수 현황</h1>
<div>
<Button size={commonButonSize}>추가등록</Button>
<Button type="button" onClick={() => {setIsFcOrgwts(true)} }size={commonButonSize}>추가등록</Button>
<RuseMngTable
tableData={fcOrgwtFiled}
headList={pbnst.pbnstOrgwtHeadList}
@@ -252,6 +263,13 @@ export function PbnstBuseForm({ data }: { data: fcBizType[] }) {
</form>
</Form>
</div>
<UiModal
open={isFcOrgwts}
onOpenChange={(open: boolean) => setIsFcOrgwts(open)}
bodyClass={"w-[68vw] max-w-none h-[60vh]"}
body={<FcOrgwtsRegModal></FcOrgwtsRegModal>}
></UiModal>
</div>
);
}
@@ -9,42 +9,51 @@ import { useEffect, useRef, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
import { pbnstRegZod, type customPbnst, type operType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import {
commonButonSize,
formH1Class,
formTableName
formTableName,
} from "@/components/lib/class/comClass";
import { TusClientUpload } from "@/components/lib/useTus_wr/useTusWr";
import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable";
import type { Upload } from "tus-js-client";
import {
pbnstRegZod,
type customPbnst,
type operType,
type ruseFcltType,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
export type FilesType = {
atflCd: String;
file: File;
};
export function PbnstMngForm({ data }: { data: customPbnst }) {
const { pbnst ,pbnstUpdate} = useWrStore((state) => state);
const { pbnstMngUpHeadList} = pbnst;
const { pbnst, pbnstUpdate } = useWrStore((state) => state);
const { pbnstMngUpHeadList } = pbnst;
const [ruseOperData, setRuseOperData] = useState<operType[]>();
const [planFiles, setPlanFiles] = useState<File[] | null>(null);
const [relationfiles, setRelationFiles] = useState<File[] | null>(null);
const [relationfiles, setRelationFiles] = useState<FilesType[] | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [upload, setUpload] = useState<Upload | null>(null);
const [progress, setProgress] = useState(0);
const handleProgress = (percentage: string) => {
setProgress(Number(percentage));
};
const ref = useRef<operType[] | null>(data?.fcOpers ?? null);
const form = useForm<z.infer<typeof pbnstRegZod>>({
resolver: zodResolver(pbnstRegZod),
defaultValues: pbnstRegZod.parse({}),
});
const { fields: fcOpersField, append, remove } = useFieldArray({
const {
fields: fcOpersField,
append,
remove,
} = useFieldArray({
control: form.control,
name: "fcOpers",
});
@@ -64,63 +73,95 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
};
const removeSelected = () => {
console.log("ref", ref.current);
console.log("fcOpersField", fcOpersField);
if(!ref.current) return;
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));
.map((item, idx) => (item.choose ? idx : -1))
.filter((idx) => idx !== -1);
selectedIdxs.reverse().forEach((idx) => remove(idx));
};
const onSelectValue = (val: string) => {};
useEffect(() => {
if (!data) return;
form.reset(data);
setRuseOperData(data.fcOpers);
}, [data]);
// 2. Define a submit handler.
function onSubmit(values: z.infer<typeof pbnstRegZod>) {
console.log("submit", values);
// pbnstUpdate(values);
values.fcOpers = ref.current || [];
console.log("form values:", values);
pbnstUpdate(values);
}
const fileHandler = (files: File[]) => {
console.log('fileHandler', files);
let setReFiles = [...(relationfiles || [])];
if (files && (!relationfiles || relationfiles.length < files.length)) {
files.find((item) => {
if (
!relationfiles ||
relationfiles?.findIndex((reFile) => reFile.file === item) < 0
) {
TusClientUpload({ atflCd: "", file: item })
.then((tusFile) => {
setReFiles.push({ atflCd: tusFile.atflCd, file: item });
console.log("after async setReFiles", setReFiles);
setRelationFiles(setReFiles);
})
.catch((e) => {
console.log(e);
});
return item;
}
});
} else if (
relationfiles &&
(!files || relationfiles.length > files.length)
) {
if (!files) {
//TODO: 파일삭제
setReFiles = [];
setReFiles.length = 0;
} else {
//TODO: 파일삭제
relationfiles?.forEach((item, index) => {
const idx = files?.indexOf(item.file);
if (idx < 0) {
setReFiles.slice(index);
}
});
}
setRelationFiles(setReFiles)
}
};
useEffect(() => {
console.log("relationfiles changed:", relationfiles);
}, [relationfiles]);
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);
},
});
// 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(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
useEffect(() => {
if (relationfiles && relationfiles[0])
console.log("relationfiles ", relationfiles[0]);
// TusClientUpload(
// relationfiles[0],{
// handleUploadStart: setUpload,
// handleProgress,
// handleSuccess: setUpload,
// })
}, [relationfiles]);
return (
<div className=" !mx-[1.6rem] !p-0r">
<div>
@@ -131,11 +172,16 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
<h2 className={formTableName + "pr-3 col-span-10 "}>
재이용 시설명
</h2>
<span className="mt-4 ml-2">{form.getValues("ruseUtztnFcltNm") ? ": " + form.getValues("ruseUtztnFcltNm") : ""}</span>
<span className="mt-4 ml-2">
{form.getValues("ruseUtztnFcltNm")
? ": " + form.getValues("ruseUtztnFcltNm")
: ""}
</span>
</div>
<Button type="submit" size={commonButonSize}>저장</Button>
<Button type="submit" size={commonButonSize}>
저장
</Button>
</div>
<div>
@@ -143,22 +189,22 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
<div className="flex justify-between items-center mx-3 mb-3">
<span className={formTableName}>재이용시설 운영정보</span>
<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>
<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
@@ -178,7 +224,12 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
name="fcltCpct"
render={({ field }) => (
<FormUi label="시설용량(m³/일)">
<Input placeholder="" type="number" onChange={field.onChange} value={field.value} />
<Input
placeholder=""
type="number"
onChange={field.onChange}
value={field.value}
/>
</FormUi>
)}
/>
@@ -189,7 +240,12 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
name="hasuhasu"
render={({ field }) => (
<FormUi label="하수처리량(m³/일)">
<Input placeholder="" type="number" onChange={field.onChange} value={field.value} />
<Input
placeholder=""
type="number"
onChange={field.onChange}
value={field.value}
/>
</FormUi>
)}
/>
@@ -200,7 +256,12 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
name="ruseCpct"
render={({ field }) => (
<FormUi label="재이용량(m³/일)">
<Input placeholder="" type="number" onChange={field.onChange} value={field.value} />
<Input
placeholder=""
type="number"
onChange={field.onChange}
value={field.value}
/>
</FormUi>
)}
/>
@@ -211,7 +272,12 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
name="splyExtraCpct"
render={({ field }) => (
<FormUi label="공급여유량(m³/일)">
<Input placeholder="" type="number"onChange={field.onChange} value={field.value} />
<Input
placeholder=""
type="number"
onChange={field.onChange}
value={field.value}
/>
</FormUi>
)}
/>
@@ -228,8 +294,10 @@ export function PbnstMngForm({ data }: { data: customPbnst }) {
render={({}) => (
<FormUi label="계통도">
<FileUpload
files={relationfiles || []}
setFiles={setRelationFiles}
files={relationfiles?.map((item) => item.file) || []}
setFiles={(files: File[] | null) => {
if (files) fileHandler(files);
}}
fileTypes="PDF"
></FileUpload>
</FormUi>
@@ -0,0 +1,64 @@
"use client";
import { useWrStore } from "@/app/store";
import { Title, UiTable } from "@common/business_components/ui";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { useEffect, useState } from "react";
export default function FcOrgwtsRegModal() {
const { code, pbnst } = useWrStore((state) => state);
const { fcOrgwtsHeadList } = pbnst;
const [isFcOrgwtsData, setIsFcOrgwtsData] = useState<boolean>(false);
useEffect(() => {
console.log("isFcOrgwtsData", isFcOrgwtsData);
}, [isFcOrgwtsData]);
return (
<>
<div className="flex flex-col h-full">
<div>
<Title title={"취수하수처리장 추가"} />
</div>
<div className="flex flex-row flex-grow gap-4 mt-4">
<div className="w-3/7">
<UiTable
headName={"공공하수처리시설 목록"}
tableData={[]}
headlist={fcOrgwtsHeadList}
pageSize={0}
total={0}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}
></UiTable>
</div>
<div className="flex flex-col items-center justify-center w-1/7 gap-2">
<ArrowRight
type="button"
onClick={() => {
setIsFcOrgwtsData(!isFcOrgwtsData);
}}
className="w-12 h-12 text-gray-500"
/>
<ArrowLeft className="w-12 h-12 text-gray-500" />
</div>
{/* Right Table container: Takes 2/5 of the width */}
<div className="w-3/7">
<UiTable
headName={"취수하수처리장 목록"}
tableData={[]}
headlist={fcOrgwtsHeadList}
pageSize={0}
total={0}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}
></UiTable>
</div>
</div>
</div>
</>
);
}
+5 -2
View File
@@ -21,16 +21,18 @@ export function middleware(req: NextRequest) {
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.eyJzdWIiOiJhMGVjN2EwNy03NTJhLTRmN2YtODlhNi04NTRlNjNkMmUzYTAiLCJhdXRob3JpdGllcyI6IlJPTEVfQURNSU4sUk9MRV9BTk9OWU1PVVMiLCJleHAiOjE3NDgzOTk3ODl9.wbYXSIFNSvVJKyofFW0C3Yg99AZ99oLYSCMjcoRvZB6DqfxCbqxHPxUgwWyFE6oHJWYrX6q4BvMY2TdVOtzxnA"
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);
@@ -48,3 +50,4 @@ export function middleware(req: NextRequest) {
export const config = {
matcher: ["/server/:path*", "/attach-service/:path*"],
};
@@ -45,7 +45,7 @@ export const FileUpload =
files.map((file:File, i:number) => (
<FileUploaderItem key={i} index={i}>
<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,
)}
@@ -295,22 +295,25 @@ export const FileUploaderItem = forwardRef<
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}
onClick={() =>{
console.log('onclick');
removeFileFromSet(index)}}
>
<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 ">
{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)}
>
<span className="sr-only">remove item {index}</span>
<RemoveIcon className="w-4 h-4 hover:stroke-destructive duration-200 ease-in-out" />