diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx index a0cde9d80..e0079aea7 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx @@ -28,7 +28,7 @@ export type Buildingtype = { export const buildingToExcelType = ( buildings: Buildingtype[], sidoCode: SelectDataType[], - sggCdoe: SelectDataType[], + sggCdoe: SelectDataType[] ) => { const excelData: string[][] = []; buildings.forEach((building) => { @@ -37,8 +37,8 @@ export const buildingToExcelType = ( building.bdstInfoSn.toString(), sidoCode.find((sido) => sido.val === building.ctpvCd.toString()) ?.text || "", - sggCdoe.find((sido) => sido.val === building.sggCd.toString()) - ?.text || "", + sggCdoe.find((sido) => sido.val === building.sggCd.toString())?.text || + "", building.daddr, building.bdstNm, "", @@ -80,121 +80,128 @@ export const buildingToExcelType = ( }); }); return excelData; - }; -export const excelToRainChkData = (excelData: any, year: string) => { - // const errors: string[] = []; - const rainChkgdData: rainChkZodType[] = excelData.map((item: any) => { - if (item["점검일"] !== undefined) { - const checkYmd = item["점검일"].replace(/\./g, "") - let vltnDt = item["과태료 부과일(위반일)"] - if (vltnDt !== undefined) { - vltnDt = vltnDt ? vltnDt.replace(/\./g, "") : ""; - } - const makeGuidDetail = Array.from({ length: 12 }, (_, i) => { - let month = `${i + 1}`; - const splyData = item[`대체_${month}월`]; - const ruseData = item[`빗물_${month}월`]; - if (month.length < 2) month = "0" + month; - return { - utztnUsgCd: item["이용용도"], - ym: year + month, - chckYmd: checkYmd, - splyUswtrUtztnQnt: Number(splyData) || 0, - ruseFcltUtztnQnt: Number(ruseData) || 0, - flg: "RNB", - }; - }); - return { - bdstNm:item["시설명"] ?? "", - bdstRuseFcltSn: Number(item["NO"]), - chckYmd: checkYmd, - spwtPositn: item["유량측정방법"] ?? "", - rgtr: TEST_UUID, - oilqMeasMthd: item["운영관리형태"] ?? "", - anlOperCst: Number(item["연간운영비(원/년)"]) ?? 0, - operMngCd: item["유량측정방법"] ?? "", - guideDetails: makeGuidDetail, - rainyDetails: [ - { - utztnUsgCd: item["이용용도"] ?? "", - ph: Number(item["PH"] || 0), - trbt: Number(item["탁도"] || 0), - grssClfg: Number(item["총대장균군수"] || 0), - etc: item["기타"] || "", // 필드 누락 방지 - vltnDt: vltnDt || "-", - vltnMttr: item["위반사항"] || "-", - levyAmt: Number(item["과태료부과금액(천원)"] || 0), +export const excelToRainChkData = (excelData: any, year: string) => { + // const errors: string[] = []; + const rainChkgdData: rainChkZodType[] = excelData + .map((item: any) => { + if (item["점검일"] !== undefined) { + const checkYmd = item["점검일"].replace(/\./g, ""); + let vltnDt = item["과태료 부과일(위반일)"]; + if (vltnDt !== undefined) { + vltnDt = vltnDt ? vltnDt.replace(/\./g, "") : ""; + } + const makeGuidDetail = Array.from({ length: 12 }, (_, i) => { + let month = `${i + 1}`; + const splyData = item[`대체_${month}월`]; + const ruseData = item[`빗물_${month}월`]; + if (month.length < 2) month = "0" + month; + return { + utztnUsgCd: item["이용용도"], + ym: year + month, + chckYmd: checkYmd, + splyUswtrUtztnQnt: Number(splyData) || 0, + ruseFcltUtztnQnt: Number(ruseData) || 0, + flg: "RNB", + }; + }); + + if (checkYmd.length > 7) { + return { + bdstNm: item["시설명"] ?? "", + bdstRuseFcltSn: Number(item["NO"]), + chckYmd: checkYmd, + spwtPositn: item["유량측정방법"] ?? "", rgtr: TEST_UUID, - }, - ], - }; - } - }).filter((el: any) => el !== undefined); + oilqMeasMthd: item["운영관리형태"] ?? "", + anlOperCst: Number(item["연간운영비(원/년)"]) ?? 0, + operMngCd: item["유량측정방법"] ?? "", + guideDetails: makeGuidDetail, + rainyDetails: [ + { + utztnUsgCd: item["이용용도"] ?? "", + ph: Number(item["PH"] || 0), + trbt: Number(item["탁도"] || 0), + grssClfg: Number(item["총대장균군수"] || 0), + etc: item["기타"] || "", // 필드 누락 방지 + vltnDt: vltnDt || "-", + vltnMttr: item["위반사항"] || "-", + levyAmt: Number(item["과태료부과금액(천원)"] || 0), + rgtr: TEST_UUID, + }, + ], + }; + } + } + }) + .filter((el: any) => el !== undefined); - return rainChkgdData; - // // ✨ 오류 검사 - 가공된 데이터 기준 - // const isInvalidNumber = (val: any) => - // val !== undefined && val !== null && val !== "" && isNaN(Number(val)); - // const isInvalidDate = (val: any) => - // typeof val === "string" && !/^\d{4}\.\d{2}\.\d{2}$/.test(val.trim()); + //return rainChkgdData; + const issues: string[] = []; - // const buildErrorMessage = (item: any, issues: string[]): string => { - // const 시설명 = item.bdstNm ?? ""; - // const 점검일 = item.chckYmd ?? ""; - // const 이용용도 = item.utztnUsgCd ?? ""; - // return `시설명: ${시설명}, 점검일: ${점검일}, 이용용도: ${이용용도} (=> ${issues.join(", ")})`; - // }; + if (rainChkgdData.length > 0) { + rainChkgdData.forEach((rain: any) => { + const defaultMessage =`[시설명: ${rain.bdstNm}, 점검일: ${rain.chckYmd}, 이용용도: ${rain.guideDetails[0]?.utztnUsgCd}] 항목에서 ` - // if (rainChkgdData.length > 0) { - // rainChkgdData.forEach((rain: any) => { - // const item = rain.__origin; // 원본 item - // const issues: string[] = []; - // if (rain.chckYmd && isInvalidDate(rain.chckYmd)) { - // issues.push(`점검일: 날짜 형식 아님 (${rain.chckYmd})`); - // } + if (rain.chckYmd && isInvalidDate(rain.chckYmd)) { + issues.push(defaultMessage+`점검일: 날짜 형식 아님 (${rain.chckYmd})`); + } + if (rain.anlOperCst && isInvalidNumber(rain.anlOperCst)) { + issues.push(defaultMessage+` 연간운영비(원/년): 숫자 형식 아님 (${rain.anlOperCst})`); + } + rain.rainyDetails?.forEach((key) => { + if (isInvalidNumber(key.ph)) { + issues.push(defaultMessage+`PH: 숫자 형식 아님 (${key.ph})`); + } else if (isInvalidNumber(key.trbt)) { + issues.push(defaultMessage+`탁도: 숫자 형식 아님 (${key.trbt})`); + } else if (isInvalidNumber(key.grssClfg)) { + issues.push(defaultMessage+`총대장균군수: 숫자 형식 아님 (${key.grssClfg})`); + } else if (isInvalidNumber(key.levyAmt)) { + issues.push(defaultMessage+`과태료부과금액(천원): 숫자 형식 아님 (${key.levyAmt})`); + } else if (isInvalidDate(key.vltnDt)) { + issues.push(defaultMessage+`과태료 부과일(위반일): 날짜 형식 아님 (${key.vltnDt})`); + } + }); + rain.guideDetails?.forEach((guide) => { + const mm = guide.ym.substring(4); + if (isInvalidNumber(guide.splyUswtrUtztnQnt)) { + issues.push( + defaultMessage+`대체_${mm}: 숫자 형식 아님 (${guide.splyUswtrUtztnQnt})` + ); + } + if (isInvalidNumber(guide.ruseFcltUtztnQnt)) { + issues.push(defaultMessage+`빗물_${mm}: 숫자 형식 아님 (${guide.ruseFcltUtztnQnt})`); + } + }); + + }); + } - // if (rain.vltnDt && isInvalidDate(rain.vltnDt)) { - // issues.push(`과태료 부과일(위반일): 날짜 형식 아님 (${rain.vltnDt})`); - // } + if (issues.length > 0) { + return { rainChkgdData: [], issues }; + } - // // [ - // // "연간운영비(원/년)", "PH", "탁도", "총대장균군수", "과태료부과금액(천원)" - // // ].forEach((key) => { - // // if (isInvalidNumber(item[key])) { - // // issues.push(`${key}: 숫자 형식 아님 (${item[key]})`); - // // } - // // } - - // // ); + return { rainChkgdData, issues: [] }; - // for (let i = 1; i <= 12; i++) { - // const month = `${i}월`; - // if (isInvalidNumber(item[`대체_${month}`])) { - // issues.push(`대체_${month}: 숫자 형식 아님 (${item[`대체_${month}`]})`); - // } - // if (isInvalidNumber(item[`빗물_${month}`])) { - // issues.push(`빗물_${month}: 숫자 형식 아님 (${item[`빗물_${month}`]})`); - // } - // } - - // if (issues.length > 0) { - // errors.push(buildErrorMessage(item, issues)); - // } - // }); - // } - - // if (errors.length > 0) { - // return { rainChkgdData: [], errors }; - // } - - // return { rainChkgdData, errors: [] }; - - // return rainChkgdData; + //return rainChkgdData; }; +const buildErrorMessage = (item: any, issues: string[]) :string=> { + const 시설명 = item.bdstNm ?? ""; + const 점검일 = item.chckYmd ?? ""; + const 이용용도 = item.guideDetails[0]?.utztnUsgCd ?? ""; + return `시설명: ${시설명}, 점검일: ${점검일}, 이용용도: ${이용용도} (=> ${issues.join(", ")})`; +}; +// ✨ 오류 검사 - 가공된 데이터 기준 +const isInvalidNumber = (val: any) => { + return !val !== undefined && val !== null && val !== "" && isNaN(Number(val)); +}; +const isInvalidDate = (val: any) => { + //yyyymmdd 즉 20201212 이런형식만 허용 + return !(typeof val === "string" && /^\d{8}$/.test(val.trim())); +}; diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx index 3be274c1b..29a2de0db 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx @@ -36,6 +36,7 @@ export const rainChkZod = z.object({ cityText:z.string().nullish(), sggText:z.string().nullish(), prcsCpct: z.number().nullable(), + anlOperCst:z.number().nullish(), spwtPositn: z.string().nullable(), regDt: z.string().nullable(), rgtr: z.string().nullable(), diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx index 36e3b1812..d2c8e18e1 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx @@ -84,6 +84,7 @@ type rainChkgdnActions = { sggCd: string, txt: string ) => void; + setRainChkLoading:(status:boolean) =>void }; export type rainChkgdnType = rainChkgdnState & rainChkgdnActions; @@ -375,6 +376,17 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( } catch (error) { console.log(error); } - }, + },setRainChkLoading:(status:boolean) =>{ + set((state: rainChkgdnState) => ({ + ...state, + rainChkgdn: { + ...state.rainChkgdn, + isLoading: status, + }, + })); + } + + + }; }; diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index 5c2ce97ff..2f3df1699 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -11,9 +11,8 @@ import type { RefObject } from "react"; import toast from "react-hot-toast"; export const getCity = (val: string, sggCd: SelectDataType[]) => { - - let data: any - if (val !== '0') { + let data: any; + if (val !== "0") { data = sggCd .filter((item) => String(item.val).substring(0, 2) === val) .map((item) => ({ @@ -21,11 +20,10 @@ export const getCity = (val: string, sggCd: SelectDataType[]) => { text: item.text, // 한국어 텍스트 })); } else { - data = sggCd - .map((item) => ({ - val: item.val, - text: item.text, // 한국어 텍스트 - })); + data = sggCd.map((item) => ({ + val: item.val, + text: item.text, // 한국어 텍스트 + })); } data.unshift({ val: "0", text: "전체" }); return data; @@ -118,7 +116,6 @@ export const handleCommaNumberChange = ( onChange(raw === "" ? "" : Number(raw)); }; - // onchange 텍스트를 숫자로 and ,제거 export const handleMinusNumberChange = ( e: React.ChangeEvent, @@ -170,55 +167,78 @@ export const yearOptions: { val: string; text: string }[] = Array.from( export const getdmdMatFlag = (flag: string | undefined | null) => { const cleanFlag = (flag ?? "").trim(); - switch (cleanFlag) { case "Sub": return "검토"; case "Matched": return "매칭완료"; case "Checked": - return '검토완료'; + return "검토완료"; default: return "신청"; } }; - export const formatPhoneNumber = (tel: string) => { - const onlyNums = tel.replace(/[^0-9]/g, ''); + const onlyNums = tel.replace(/[^0-9]/g, ""); if (onlyNums.length === 10) { - return onlyNums.replace(/(\d{2})(\d{4})(\d{4})/, '$1-$2-$3'); + return onlyNums.replace(/(\d{2})(\d{4})(\d{4})/, "$1-$2-$3"); } else if (onlyNums.length === 11) { - return onlyNums.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3'); + return onlyNums.replace(/(\d{3})(\d{4})(\d{4})/, "$1-$2-$3"); } else { return tel; // 유효하지 않으면 빈 문자열 } -} +}; export const confirmToast = (content: string, saveFunc: () => void) => { - toast((t) => ( - -
- {content} -
-
- - -
-
- ), + toast( + (t) => ( + +
{content}
+
+ + +
+
+ ), { id: "global-confirm-toast", // 중복 토스트 방지 duration: Infinity, // 자동 사라짐 방지 - }); -} \ No newline at end of file + } + ); +}; + +export const showConfirmToast = (message: string) => { + toast.custom( + (t) => ( +
+
+ {message} +
{" "} +
+ +
+
+ ), + { duration: Infinity } + ); // 수동 닫기 +}; diff --git a/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx b/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx index b35f4c3a1..011c43cf1 100644 --- a/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx +++ b/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx @@ -7,6 +7,7 @@ import { excelToRswtGuideData, excelToRswtPolution, } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel"; +import { showConfirmToast } from "@/components/lib/func/commom"; import { filePicked, filePickedArray, @@ -18,22 +19,39 @@ import { } from "@common/business_components/ui"; import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; import type { z } from "zod"; -export default function ChkgdExcel({ type,setIsRegExcel ,setYearChange }: { type: string, setIsRegExcel:(open:boolean) =>void ,setYearChange:(year:string)=> void }) { - const { rainChkExcelDataUPload, rswtChkExceDataUpload } = - useWrStore((state) => { - return state; - }); +export default function ChkgdExcel({ + type, + setIsRegExcel, + setYearChange, +}: { + type: string; + setIsRegExcel: (open: boolean) => void; + setYearChange: (year: string) => void; +}) { + const { + rainChkgdn, + rainChkExcelDataUPload, + rswtChkExceDataUpload, + setRainChkLoading, + } = useWrStore((state) => { + return state; + }); const [excelData, setExcelData] = useState< rswtChkZodType[] | rainChkZodType[] >(); const [file, setFile] = useState(null); + const clearFile = () => { + if (fileInputRef.current) { + fileInputRef.current.value = ""; // ✅ 파일 선택 비우기 + } + }; const handleFileChange = (e: React.ChangeEvent) => { const selectedFile = e.target.files?.[0]; if (selectedFile) { @@ -41,6 +59,7 @@ export default function ChkgdExcel({ type,setIsRegExcel ,setYearChange }: { type } }; const [excelRswtData, setExcelRswtData] = useState(); + const fileInputRef = useRef(null); const currentYear = new Date().getFullYear(); const startYear = 1970; @@ -70,26 +89,25 @@ export default function ChkgdExcel({ type,setIsRegExcel ,setYearChange }: { type } if (file) if (type === "RNB") { - filePicked(file, 0).then((res) => { - // if (res !== undefined){ - - const excelData = excelToRainChkData(res, form.getValues("chckYr")) - if(res !== undefined) - setExcelData(excelData) - // if( excelData&&excelData.errors.length>0){ - - // excelData.errors.forEach((err) =>{ - // toast.error(err) - // }) - // }else{ - - // } - - // } - - - - }) + setRainChkLoading(true); + filePicked(file, 0) + .then(async (res) => { + const excelData = excelToRainChkData( + res, + form.getValues("chckYr") + ); + if (excelData.issues && excelData.issues.length > 0) { + excelData.issues.forEach((err) => { + showConfirmToast(err); + }); + clearFile(); + } else { + setExcelData(excelData.rainChkgdData); + } + }) + .finally(() => { + setRainChkLoading(false); + }); } else if (type === "RST") { filePickedArray(file, 0).then((res) => { if (res && res?.length > 1) { @@ -165,9 +183,12 @@ export default function ChkgdExcel({ type,setIsRegExcel ,setYearChange }: { type return; } else { - rainChkExcelDataUPload(excelData as rainChkZodType[] , form.getValues("chckYr")) ; - setIsRegExcel(false) - setYearChange(form.getValues("chckYr")) + rainChkExcelDataUPload( + excelData as rainChkZodType[], + form.getValues("chckYr") + ); + setIsRegExcel(false); + setYearChange(form.getValues("chckYr")); } } else if (type === "RST") { if (excelRswtData === undefined || excelRswtData?.length === 0) { @@ -176,13 +197,12 @@ export default function ChkgdExcel({ type,setIsRegExcel ,setYearChange }: { type return; } else { rswtChkExceDataUpload(excelRswtData as rswtChkZodType[]); - setIsRegExcel(false) - setYearChange(form.getValues("chckYr")) - } - + setIsRegExcel(false); + setYearChange(form.getValues("chckYr")); } } - + }; + return ( <>
@@ -231,14 +251,15 @@ export default function ChkgdExcel({ type,setIsRegExcel ,setYearChange }: { type
{ - handleFileChange( file) - ////TODO : 폼 을 써야하는 지 확인 필요 - console.log(field.value) - } } + onChange={(file) => { + handleFileChange(file); + ////TODO : 폼 을 써야하는 지 확인 필요 + console.log(field.value); + }} accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" - /> + />
)}