diff --git a/apps/dm_new/app/(with-layout)/apply/register/page.tsx b/apps/dm_new/app/(with-layout)/apply/register/page.tsx index c37912b97..3c084bf73 100644 --- a/apps/dm_new/app/(with-layout)/apply/register/page.tsx +++ b/apps/dm_new/app/(with-layout)/apply/register/page.tsx @@ -8,75 +8,14 @@ */ "use client"; -import { Suspense, useEffect, useMemo, useRef, useState } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; -import { useDmStore } from "@/app/store"; -import type { DemandCompBizType } from "@/app/store/sdmMng"; -import { putFetch } from "@/components/ui/api"; +import { useMemo, useState, useRef } from "react"; import SubPageHeader from "@/components/SubPageHeader"; import WaterQualityRefModal from "../modal/WaterQualityRefModal"; +import CandidateSiteModal, { type CandidateSite } from "@/components/CandidateSiteModal"; type PHRange = { min: string; max: string }; -type SessionUser = { - dmdInfoId?: string; - dmdCoNm?: string; - ctpvCd?: string; - sggCd?: string; - zip?: string; - daddr?: string; - telno?: string; - rgtr?: string; -}; - -const toNumberOrNull = (value: string, disabled = false) => { - if (disabled) return null; - const trimmed = value.trim(); - if (!trimmed) return null; - const parsed = Number(trimmed); - return Number.isNaN(parsed) ? null : parsed; -}; - -const toNumberOrEmpty = (value: string) => { - const trimmed = value.trim(); - if (!trimmed) return "" as const; - const parsed = Number(trimmed); - return Number.isNaN(parsed) ? ("" as const) : parsed; -}; - -const isNumeric = (value: string) => /^\d+(\.\d+)?$/.test(value.trim()); - -const formatDateForInput = (raw?: string | null) => { - if (!raw) return ""; - const parsed = new Date(raw); - if (Number.isNaN(parsed.getTime())) return ""; - const yyyy = parsed.getFullYear(); - const mm = String(parsed.getMonth() + 1).padStart(2, "0"); - const dd = String(parsed.getDate()).padStart(2, "0"); - return `${yyyy}-${mm}-${dd}`; -}; - -const parsePhRange = (raw?: string | null): PHRange => { - if (!raw) return { min: "", max: "" }; - const [min = "", max = ""] = raw.split("~").map((v) => v.trim()); - return { min, max }; -}; - -const getSessionUser = (): SessionUser | null => { - if (typeof window === "undefined") return null; - const raw = sessionStorage.getItem("user"); - if (!raw) return null; - try { - return JSON.parse(raw) as SessionUser; - } catch { - return null; - } -}; - -function DemandRegisterPageContent() { - const router = useRouter(); - const searchParams = useSearchParams(); - const { insertSdmInfoData, Sdm } = useDmStore((state) => state); +export default function DemandRegisterPage() { const today = useMemo(() => { const d = new Date(); const yyyy = d.getFullYear(); @@ -86,486 +25,299 @@ function DemandRegisterPageContent() { }, []); const [openRef, setOpenRef] = useState(false); - const [editMode, setEditMode] = useState(false); - const [updateAllowed, setUpdateAllowed] = useState(true); - const [dmdInfoSn, setDmdInfoSn] = useState(0); - const [submitting, setSubmitting] = useState(false); + + const [openCand, setOpenCand] = useState(false); + const [selectedCands, setSelectedCands] = useState([]); // 기본정보 - const [reqDate, setReqDate] = useState(today); // 요구시기 - const [reqQty, setReqQty] = useState(""); // 요구수량(m²/d) - const [usePurpose, setUsePurpose] = useState(""); // 사용용도 - const [hopePrice, setHopePrice] = useState(""); // 희망단가 - + const [reqDate, setReqDate] = useState(today); + const [reqQty, setReqQty] = useState(""); + const [usePurpose, setUsePurpose] = useState(""); + const [hopePrice, setHopePrice] = useState(""); // 요구수질 - const [coliform, setColiform] = useState(""); // 총대장균군수(개/mg) - const [noDetect, setNoDetect] = useState(false); // 총대장균군수 불검출 - - const [tn, setTn] = useState(""); // 총질소(T-N)(mg/L) - const [residualCl, setResidualCl] = useState(""); // 결합잔류염소(mg/L) - const [tp, setTp] = useState(""); // 총인(T-P)(mg/L) - const [turbidity, setTurbidity] = useState(""); // - const [ph, setPh] = useState({ min: "", max: "" }); // 수소이온농도(pH) - const [bod, setBod] = useState(""); // 생물화학적산소요구량(BOD)(mg/L) - const [chloride, setChloride] = useState(""); // 염화물(mgCL/L) - const [odor, setOdor] = useState("불쾌하지 않을 것"); // 냄새 - const [ec, setEc] = useState(""); // 전기전도도(µs/cm) - const [color, setColor] = useState(""); // 색도(도) - const [remark, setRemark] = useState(""); // 비고 + const [coliform, setColiform] = useState(""); + const [noDetect, setNoDetect] = useState(false); + const [tn, setTn] = useState(""); + const [residualCl, setResidualCl] = useState(""); + const [tp, setTp] = useState(""); + const [turbidity, setTurbidity] = useState(""); + const [ph, setPh] = useState({ min: "", max: "" }); + const [bod, setBod] = useState(""); + const [chloride, setChloride] = useState(""); + const [odor, setOdor] = useState("불쾌하지 않을 것"); + const [ec, setEc] = useState(""); + const [color, setColor] = useState(""); + const [remark, setRemark] = useState(""); // 공급처 후보지 선택(필수아님) const [cand1, setCand1] = useState(""); const [cand2, setCand2] = useState(""); const [cand3, setCand3] = useState(""); - useEffect(() => { - const isEditParam = - searchParams.get("editMode") === "true" || - Boolean(searchParams.get("dmdInfoSn")); - setEditMode(isEditParam); - - const status = (searchParams.get("status") || "").trim(); - setUpdateAllowed(!isEditParam || status === "신청"); - - if (!isEditParam) return; - - const parsedSn = Number(searchParams.get("dmdInfoSn") || "0"); - setDmdInfoSn(Number.isNaN(parsedSn) ? 0 : parsedSn); - - setReqDate(formatDateForInput(searchParams.get("dmndTmng")) || today); - setReqQty(searchParams.get("dmndQty") || ""); - setUsePurpose(searchParams.get("useUsgCd") || ""); - setHopePrice(searchParams.get("wntUntprc") || ""); - - const rawColiform = searchParams.get("grssClfg") || ""; - const isNoDetect = rawColiform === "-1"; - setNoDetect(isNoDetect); - setColiform(isNoDetect ? "" : rawColiform); - - setTn(searchParams.get("tn") || ""); - setResidualCl(searchParams.get("combResdch") || ""); - setTp(searchParams.get("tph") || ""); - setTurbidity(searchParams.get("trbt") || ""); - setBod(searchParams.get("blgcOxygDmndQnt") || ""); - setChloride(searchParams.get("clIonQnt") || ""); - setEc(searchParams.get("cdtnv") || ""); - setColor(searchParams.get("crmty") || ""); - setRemark(searchParams.get("rmrk") || ""); - - const phRange = parsePhRange(searchParams.get("ph")); - setPh(phRange); - setOdor(searchParams.get("smll") || "불쾌하지 않을 것"); - }, [searchParams, today]); - - const validate = () => { - const disabledFields = Sdm.disableRules[usePurpose] || []; - const isDisabled = (field: string) => disabledFields.includes(field); - - if (!reqDate) { - alert("요구시기를 입력해주세요."); - return false; - } - if (!reqQty.trim()) { - alert("요구수량을 입력해주세요."); - return false; - } - if (!/^\d+$/.test(reqQty.trim())) { - alert("요구수량은 숫자만 입력 가능합니다."); - return false; - } - if (!usePurpose.trim()) { - alert("사용용도를 입력해주세요."); - return false; - } - if (!hopePrice.trim()) { - alert("희망단가를 입력해주세요."); - return false; - } - if (!/^\d+$/.test(hopePrice.trim())) { - alert("희망단가는 숫자만 입력 가능합니다."); - return false; - } - if (!noDetect && coliform.trim() && !isNumeric(coliform)) { - alert("총대장균군수는 숫자만 입력 가능합니다."); - return false; - } - - const requiredFields: Array<{ key: string; label: string; value: string }> = [ - { key: "tn", label: "총질소(T-N)(mg/L)", value: tn }, - { key: "combResdch", label: "결합잔류염소(mg/L)", value: residualCl }, - { key: "tph", label: "총인(T-P)(mg/L)", value: tp }, - { key: "trbt", label: "탁도(NTU)", value: turbidity }, - { key: "bod", label: "생물학적 산소요구량(BOD)(mg/L)", value: bod }, - { key: "clIonQnt", label: "염화물(mgCL/L)", value: chloride }, - { key: "smll", label: "냄새", value: odor }, - { key: "cdtnv", label: "전기전도도(㎲/cm)", value: ec }, - { key: "crmty", label: "색도(도)", value: color }, - ]; - - for (const field of requiredFields) { - if (isDisabled(field.key)) continue; - if (!field.value.trim()) { - alert(`${field.label} 을 입력해주세요.`); - return false; - } - if (field.key !== "smll" && !isNumeric(field.value)) { - alert(`${field.label} 은 숫자만 입력 가능합니다.`); - return false; - } - } - - if (!isDisabled("ph") && (!ph.min.trim() || !ph.max.trim())) { - alert("수소이온농도(pH) 을 입력해주세요."); - return false; - } - - return true; - }; - - const onSubmit = async () => { - if (submitting) return; - if (!validate()) return; - - if (editMode && !updateAllowed) { - alert("진행상태가 신청인 경우에만 수정할 수 있습니다."); - return; - } - - const sessionUser = getSessionUser(); - const idFromQuery = - (searchParams.get("dmdInfoId") || "") + (searchParams.get("bznmTotNo") || ""); - const dmdInfoId = String(sessionUser?.dmdInfoId || idFromQuery || "").trim(); - - if (!dmdInfoId) { - alert("수요처 정보가 없어 저장할 수 없습니다."); - return; - } - - const disabledFields = Sdm.disableRules[usePurpose] || []; - const isDisabled = (field: string) => disabledFields.includes(field); - const parsedPh = isDisabled("ph") ? "" : `${ph.min.trim()}~${ph.max.trim()}`; - const cands = [cand1, cand2, cand3] - .map((name) => name.trim()) - .filter(Boolean) - .map((name) => ({ - ruseUtztnFcltNm: name, - ruseUtztnFcltCd: "", - })) - .filter((item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd); - - const payload: DemandCompBizType = { - index: 0, - dmdInfoId, - dmdInfoFk: dmdInfoId, - bzmnNo: "", - dmdCoNm: String(sessionUser?.dmdCoNm || searchParams.get("dmdCoNm") || ""), - zip: String(sessionUser?.zip || searchParams.get("zip") || ""), - daddr: String(sessionUser?.daddr || searchParams.get("daddr") || ""), - telno: String(sessionUser?.telno || searchParams.get("telno") || ""), - rgtr: String(sessionUser?.rgtr || searchParams.get("rgtr") || ""), - useUsgCd: usePurpose.trim(), - ctpvCd: String(sessionUser?.ctpvCd || searchParams.get("ctpvCd") || ""), - sggCd: String(sessionUser?.sggCd || searchParams.get("sggCd") || ""), - dmdAplyPswd: "", - wntUntprc: toNumberOrEmpty(hopePrice), - dmndTmng: reqDate ? new Date(reqDate) : "", - dmndQty: toNumberOrEmpty(reqQty), - grssClfg: noDetect ? -1 : toNumberOrNull(coliform), - tn: toNumberOrNull(tn, isDisabled("tn")), - combResdch: toNumberOrNull(residualCl, isDisabled("combResdch")), - tph: toNumberOrNull(tp, isDisabled("tph")), - trbt: toNumberOrNull(turbidity, isDisabled("trbt")), - ph: parsedPh, - blgcOxygDmndQnt: toNumberOrNull(bod, isDisabled("bod")), - clIonQnt: toNumberOrNull(chloride, isDisabled("clIonQnt")), - smll: isDisabled("smll") ? "" : odor.trim(), - cdtnv: toNumberOrNull(ec, isDisabled("cdtnv")), - crmty: toNumberOrNull(color, isDisabled("crmty")), - rmrk: remark.trim(), - reqDt: "", - dmdInfoSn: editMode ? dmdInfoSn : 0, - status: String(searchParams.get("status") || ""), - splyCndtList: cands, - }; - - try { - setSubmitting(true); - if (editMode) { - const updatePayload = { - ...payload, - mdfr: String(sessionUser?.rgtr || payload.rgtr || ""), - }; - await putFetch("/reuseFc/request/demandInfo", updatePayload); - alert("수요정보 수정 완료!"); - } else { - await insertSdmInfoData(payload); - } - router.push("/apply/status"); - } catch (error) { - console.error("Failed to submit demand info", error); - alert(editMode ? "수요정보 수정에 실패하였습니다." : "수요정보 등록에 실패하였습니다."); - } finally { - setSubmitting(false); - } - }; - return ( <> -
+
- {/* 서브 타이틀 + 브레드크럼 */} - + {/* 서브 타이틀 + 브레드크럼 */} + - {/* 컨텐츠 영역 */} -
+ {/* 컨텐츠 영역 */} +
- {/* 기본정보 테이블 */} -
- - - + {/* 기본정보 테이블 */} +
+ + + - + - - - + + + - + - - - + + + - + - - - -
- - {/* 요구수질 섹션 타이틀 바 */} -
-
- 요구수질 - - {/* 수질기준참고 모달 */} - setOpenRef(false)} - imageSrc="/images/landing/water_quality_ref_1.png" - /> + + +
-
- {/* 요구수질 테이블 */} -
- {/* 1행 */} - - - -
- } - rightLabel="총질소(T-N)(mg/L)" - rightControl={ - - } - /> - - - {/* 2행 */} - } - rightLabel="총인(T-P)(mg/L)" - rightControl={ - - } - /> - - - {/* 3행 */} - - } - rightLabel="수소이온농도(pH)" - rightControl={ -
- setPh((p) => ({ ...p, min: v }))} - className="w-32" - /> - ~ - setPh((p) => ({ ...p, max: v }))} - className="w-32" - /> -
- } - /> - - - {/* 4행 */} - - 생물화학적산소요구량 -
- (BOD)(mg/L) - - } - leftRequired - leftControl={} - rightLabel="염화물(mgCL/L)" - rightControl={} - /> - - - {/* 5행 */} - setOdor(e.target.value)} - className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" - > - - - - - } - rightLabel="전기전도도(µs/cm)" - rightControl={} - /> - - - {/* 6행 */} - } - rightLabel="비고" - rightControl={ -