From fc03a19da14e31f36a9448efbf15f0b5a658c328 Mon Sep 17 00:00:00 2001 From: jsyang Date: Tue, 31 Mar 2026 20:18:45 +0900 Subject: [PATCH] =?UTF-8?q?=EC=88=98=EC=9A=94=EC=A0=95=EB=B3=B4=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20validation=20=EC=A0=9C=EA=B1=B0(=EC=9E=84=EC=8B=9C)?= =?UTF-8?q?,=20=EC=88=98=EC=9A=94=EC=8B=A0=EC=B2=AD=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EB=B2=84=ED=8A=BC=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(with-layout)/apply/register/page.tsx | 20 +- .../app/(with-layout)/apply/status/page.tsx | 349 ++++++++++++++++-- 2 files changed, 327 insertions(+), 42 deletions(-) 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 482c19468..a82b89726 100644 --- a/apps/dm_new/app/(with-layout)/apply/register/page.tsx +++ b/apps/dm_new/app/(with-layout)/apply/register/page.tsx @@ -189,20 +189,20 @@ export default function DemandRegisterPage() { if(!validation(reqQty, '요구수량', ['req', 'numDot'])) return false; if(!validation(usePurpose, '사용용도', ['req'])) return false; if(!validation(hopePrice, '희망단가', ['req', 'numDot'])) return false; - if(!noDetect && !validation(coliform, '총대장균군수', ['req','numDot'])) return false; + /* if(!noDetect && !validation(coliform, '총대장균군수', ['req','numDot'])) return false; if(!validation(residualCl, ['req', '결합잔류염소', 'numDot'])) return false; if(!validation(turbidity, '탁도', ['req', 'numDot'])) return false; if(!validation(bod, '생물화학적산소요구량', ['req', 'numDot'])) return false; - if(!validation(odor, '냄새', ['req'])) return false; + if(!validation(odor, '냄새', ['req'])) return false; */ // 필수는 아니지만 숫자로는 들어와야함. - if(!validation(tn, '총질소', ['numDot'])) return false; + /* if(!validation(tn, '총질소', ['numDot'])) return false; if(!validation(residualCl, '결합잔류염소', ['numDot'])) return false; if(!validation(tp, '총인', ['numDot'])) return false; if(!validation(ph.min, 'ph', ['numDot'])) return false; if(!validation(ph.max, 'ph', ['numDot'])) return false; if(!validation(chloride, '염화물', ['numDot'])) return false; - if(!validation(ec, '전기전도도', ['numDot'])) return false; + if(!validation(ec, '전기전도도', ['numDot'])) return false; */ const userStr = sessionStorage.getItem("user"); const user = userStr ? JSON.parse(userStr) : null; @@ -421,7 +421,7 @@ export default function DemandRegisterPage() { {/* 1행 */} } rightLabel="총인(T-P)(mg/L)" rightControl={ @@ -466,7 +466,7 @@ export default function DemandRegisterPage() { {/* 3행 */} } @@ -498,7 +498,7 @@ export default function DemandRegisterPage() { (BOD)(mg/L) } - leftRequired + /* leftRequired */ leftControl={} rightLabel="염화물(mgCL/L)" rightControl={} @@ -508,7 +508,7 @@ export default function DemandRegisterPage() { {/* 5행 */} } rightLabel="비고" rightControl={ diff --git a/apps/dm_new/app/(with-layout)/apply/status/page.tsx b/apps/dm_new/app/(with-layout)/apply/status/page.tsx index fc9e61134..30b222720 100644 --- a/apps/dm_new/app/(with-layout)/apply/status/page.tsx +++ b/apps/dm_new/app/(with-layout)/apply/status/page.tsx @@ -4,9 +4,11 @@ import { useEffect, useMemo, useRef, useState } from "react"; import SubPageHeader from "@/components/SubPageHeader"; import { useDmStore } from "@/app/store"; import type { DemandCompBizType } from "@/app/store/sdmMng"; +import { Textarea } from "@common/components/ui"; +import type { SelectBox } from "@common/business_components/ui"; -type TopField = { label: string; value: string; required?: boolean }; -type PairField = { label: string; value: string }; +type TopField = { label: string; key: string, value: string; required?: boolean }; +type PairField = { label: string; key: string, value: string }; type SessionUser = { dmdInfoId?: string }; const formatDate = (value: string | Date | "" | undefined) => { @@ -35,6 +37,93 @@ function StatusBadge({ v }: { v: string }) { return {v || "-"}; } +function TextInput({ + value, + onChange, + placeholder, + disabled, + className, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + disabled?: boolean; + className?: string; +}) { + return ( + onChange(e.target.value)} + placeholder={placeholder} + disabled={disabled} + 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", + disabled ? "cursor-not-allowed" : "", + className ?? "", + ].join(" ")} + /> + ); +} + +function DateInput({ + value, + onChange, +}: { + value: string; + onChange: (v: string) => void; +}) { + + const ref = useRef(null); + + const openPicker = () => { + const el = ref.current; + if (!el) return; + + // Chrome/Edge 등 지원 + if (typeof (el as any).showPicker === "function") { + (el as any).showPicker(); + return; + } + + // fallback (일부 브라우저) + el.focus(); + el.click(); + }; + return ( +
+ onChange(e.target.value)} + className={[ + "h-11 w-full rounded-md border border-neutral-300 bg-white px-3 pr-10 text-sm outline-none focus:border-neutral-500", + // ✅ 기본 달력 아이콘 숨김 (Tailwind arbitrary selector) + "[&::-webkit-calendar-picker-indicator]:opacity-0", + "[&::-webkit-calendar-picker-indicator]:absolute", + "[&::-webkit-calendar-picker-indicator]:right-0", + "[&::-webkit-calendar-picker-indicator]:w-10", + "[&::-webkit-calendar-picker-indicator]:h-full", + "[&::-webkit-calendar-picker-indicator]:cursor-pointer", + ].join(" ")} + /> + + {/* ✅ 클릭 가능한 아이콘 버튼 */} + +
+ ); +} + export default function DemandStatusPage() { const { getSdmInfoStatusList, Sdm, Common } = useDmStore((state) => state); const { successLogin, sdmInfoStatusList } = Sdm; @@ -46,6 +135,16 @@ export default function DemandStatusPage() { const [selectedDmdInfoSn, setSelectedDmdInfoSn] = useState(null); const [transformedSdmInfoStatusList, setTransformedSdmInfoStatusList] = useState([]); + // 수정 유무 + const [ isEditMode, setIsEditMode ] = useState(false); + // 수정용 (UI에서 바뀌는 값) + const [editData, setEditData] = useState(null); + // 원본 백업 (취소용) + const [originData, setOriginData] = useState(null); + // 사용용도 + const useTypesData = useDmStore((state) => state.Sdm.useTypesData); + const getUseTypesData = useDmStore((state) => state.getUseTypesData); + useEffect(() => { const user = sessionStorage.getItem("user"); if (user) { @@ -53,6 +152,10 @@ export default function DemandStatusPage() { } }, []); + useEffect(() => { + getUseTypesData(); + }, [getUseTypesData]); + useEffect(() => { if (!successLogin && !isLoginStorageInfo) return; @@ -112,10 +215,10 @@ export default function DemandStatusPage() { if (!selectedSdmInfoStatus) return []; return [ - { label: "요구시기", value: formatDate(selectedSdmInfoStatus.dmndTmng), required: true }, - { label: "요구수량(㎥/d)", value: formatValue(selectedSdmInfoStatus.dmndQty) }, - { label: "사용용도", value: formatValue(selectedSdmInfoStatus.useUsgCd) }, - { label: "희망단가(원/㎥)", value: formatValue(selectedSdmInfoStatus.wntUntprc) }, + { label: "요구시기", key: "dmndTmng", value: formatDate(selectedSdmInfoStatus.dmndTmng), required: true }, + { label: "요구수량(㎥/d)", key: "dmndQty", value: formatValue(selectedSdmInfoStatus.dmndQty) }, + { label: "사용용도", key: "useUsgCd", value: formatValue(selectedSdmInfoStatus.useUsgCd) }, + { label: "희망단가(원/㎥)", key: "wntUntprc", value: formatValue(selectedSdmInfoStatus.wntUntprc) }, ]; }, [selectedSdmInfoStatus]); @@ -123,21 +226,21 @@ export default function DemandStatusPage() { if (!selectedSdmInfoStatus) return []; return [ - { label: "총대장균군수(개/100mL)", value: formatBelow(selectedSdmInfoStatus.grssClfg) }, - { label: "결합잔류염소(mg/L)", value: formatBelow(selectedSdmInfoStatus.combResdch) }, - { label: "탁도(NTU)", value: formatBelow(selectedSdmInfoStatus.trbt) }, + { label: "총대장균군수(개/100mL)", key: "grssClfg", value: formatBelow(selectedSdmInfoStatus.grssClfg) }, + { label: "결합잔류염소(mg/L)", key:"combResdch", value: formatBelow(selectedSdmInfoStatus.combResdch) }, + { label: "탁도(NTU)", key:"trbt", value: formatBelow(selectedSdmInfoStatus.trbt) }, - { label: "생물학적산소요구량(BOD)(mg/L)", value: formatBelow(selectedSdmInfoStatus.blgcOxygDmndQnt) }, - { label: "냄새", value: formatValue(selectedSdmInfoStatus.smll) }, - { label: "색도(도)", value: formatBelow(selectedSdmInfoStatus.crmty) }, + { label: "생물학적산소요구량(BOD)(mg/L)", key:"blgcOxygDmndQnt", value: formatBelow(selectedSdmInfoStatus.blgcOxygDmndQnt) }, + { label: "냄새", key:"smll", value: formatValue(selectedSdmInfoStatus.smll) }, + { label: "색도(도)", key:"crmty", value: formatBelow(selectedSdmInfoStatus.crmty) }, - { label: "총질소(T-N)(mg/L)", value: formatBelow(selectedSdmInfoStatus.tn) }, - { label: "총인(T-P)(mg/L)", value: formatBelow(selectedSdmInfoStatus.tph) }, - { label: "수소이온농도(pH)", value: formatValue(selectedSdmInfoStatus.ph) }, + { label: "총질소(T-N)(mg/L)", key:"tn", value: formatBelow(selectedSdmInfoStatus.tn) }, + { label: "총인(T-P)(mg/L)", key:"tph", value: formatBelow(selectedSdmInfoStatus.tph) }, + { label: "수소이온농도(pH)", key:"ph", value: formatValue(selectedSdmInfoStatus.ph) }, - { label: "염화물(mgCL/L)", value: formatBelow(selectedSdmInfoStatus.clIonQnt) }, - { label: "전기전도도(μS/cm)", value: formatBelow(selectedSdmInfoStatus.cdtnv) }, - { label: "비고", value: formatValue(selectedSdmInfoStatus.rmrk) }, + { label: "염화물(mgCL/L)", key:"clIonQnt", value: formatBelow(selectedSdmInfoStatus.clIonQnt) }, + { label: "전기전도도(μS/cm)", key:"cdtnv", value: formatBelow(selectedSdmInfoStatus.cdtnv) }, + { label: "비고", key:"rmrk", value: formatValue(selectedSdmInfoStatus.rmrk) }, ]; }, [selectedSdmInfoStatus]); @@ -151,13 +254,25 @@ export default function DemandStatusPage() { }, [selectedSdmInfoStatus]); const onSelectSdmInfoStatus = (dmdInfoSn: number | "") => { + if(isEditMode){ + if(confirm('저장되지 않은 데이터는 사라집니다.\n진행하시겠습니까?')){ + setIsEditMode(false); + } + } setSelectedDmdInfoSn(dmdInfoSn); requestAnimationFrame(() => { detailRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); }); }; + // ph 전후로 나눔 + const getRange = (val: string) => { + let a = val.split('~')[0]||''; let b = val.split('~')[1]||''; + return { a, b }; + }; + return ( +
수요신청 상세현황
- + {/* { + !isEditMode ? ( + + ) : ( +
+ + +
+ ) + } */}
{!selectedSdmInfoStatus ? ( @@ -268,9 +415,47 @@ export default function DemandStatusPage() { {field.label} {field.required ? * : null} -
- {field.value} -
+ { + !isEditMode ? +
+ {field.key==='useUsgCd'? useTypesData.filter(item=>item.val===field.value)[0]?.text: field.value} +
+ :
+ {field.key ==='dmndTmng'? + + setEditData((prev) => ({ + ...prev, + [field.key]: v, + })) + } /> + : field.key ==='useUsgCd'? + + + : + setEditData((prev) => ({ + ...prev, + [field.key]: v, + })) + }> + } +
+ } ))} @@ -278,19 +463,119 @@ export default function DemandStatusPage() { 요구수질 - {waterFields.map((field) => ( + {waterFields.map((field) => { + const { a, b } = editData?.ph ? getRange(editData.ph) : { a: "", b: "" }; + return (
{field.label}
- {field.value} + { + !isEditMode ? field.value + : + field.key === "grssClfg" ? ( +
+ + setEditData((prev) => ({ + ...prev, + grssClfg: v, + })) + } + disabled={editData?.grssClfg === -1} + className={ + editData?.grssClfg === -1 + ? "bg-neutral-100 text-neutral-400" + : "" + } + /> + +
+ ) : field.key === 'smll' ? ( + + ) : field.key === 'ph' ? ( + <> + + setEditData((prev) => ({ + ...prev, + [field.key]: `${v}~${b}`, + })) + } + /> + ~ + + setEditData((prev) => ({ + ...prev, + [field.key]: `${a}~${v}`, + })) + } + /> + + ) : ( + <> + + setEditData((prev) => ({ + ...prev, + [field.key]: v, + })) + } + /> + { + field.key==='tn'||field.key === 'tph'||field.key === 'trbt'||field.key==='blgcOxygDmndQnt'||field.key==='clIonQnt'||field.key==='cdtnv'|| field.key=='crmty' + ?<>이하:<> + } + + ) + }
- ))} + )})}
공급처 후보지