dm_new: 우편번호 추가

This commit is contained in:
2026-02-25 18:15:25 +09:00
parent 5e1a5c68c8
commit b6ca669c2e
8 changed files with 538 additions and 249 deletions
@@ -2,7 +2,7 @@
import type { LnbItem } from "../_components/Lnb";
export const LNB_DEMAND: LnbItem[] = [
{ href: "/reuse/apply/place", label: "수요처등록" },
{ href: "/reuse/apply/place", label: "수요처" },
{ href: "/reuse/apply/register", label: "수요정보등록" },
{ href: "/reuse/apply/status", label: "수요신청현황" },
];
@@ -13,6 +13,7 @@ type Option = { value: string; label: string };
type SessionUser = {
dmdInfoId?: string;
dmdCoNm?: string;
zip?: string;
daddr?: string;
telno?: string;
ctpvCd?: string;
@@ -211,6 +212,7 @@ export default function DemandPlaceRegisterPage() {
}
setCompany(String(user.dmdCoNm ?? ""));
setZip(String(user.zip ?? ""));
setAddr1(String(user.daddr ?? ""));
setAddr2("");
setTel(formatKoreanPhoneNumber(String(user.telno ?? "")));
@@ -334,6 +336,7 @@ export default function DemandPlaceRegisterPage() {
dmdCoNm: company.trim(),
dmdInfoId: fullDmdInfoId,
bzmnNo: bizDigits,
zip: zip.trim(),
daddr: mergedAddress,
telno: telDigits,
rgtr: (sessionUser?.rgtr as string) || myUUID,
@@ -1,13 +1,82 @@
// apps/dm/app/(landing)/reuse/apply/register/page.tsx
// app/(with-layout)/apply/register/page.tsx
/**
* 수요정보등록(수정) 화면
*
* 🔗 사용 API
* - 수요정보등록: POST /reuseFc/request/demandInfo
* - 수요정보조회: GET /reuseFc/request/' + dmdInfoId - getSdmInfoStatusList
*/
"use client";
import { useMemo, useState, useRef } from "react";
import { 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 SubPageHeader from "@/components/SubPageHeader";
import WaterQualityRefModal from "../modal/WaterQualityRefModal";
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;
}
};
export default function DemandRegisterPage() {
const router = useRouter();
const searchParams = useSearchParams();
const { insertSdmInfoData, Sdm } = useDmStore((state) => state);
const today = useMemo(() => {
const d = new Date();
const yyyy = d.getFullYear();
@@ -17,279 +86,474 @@ export default function DemandRegisterPage() {
}, []);
const [openRef, setOpenRef] = useState(false);
const [editMode, setEditMode] = useState(false);
const [updateAllowed, setUpdateAllowed] = useState(true);
const [dmdInfoSn, setDmdInfoSn] = useState<number>(0);
const [submitting, setSubmitting] = useState(false);
// 기본정보
const [reqDate, setReqDate] = useState<string>(today);
const [reqQty, setReqQty] = useState<string>("");
const [usePurpose, setUsePurpose] = useState<string>("");
const [hopePrice, setHopePrice] = useState<string>("");
const [reqDate, setReqDate] = useState<string>(today); // 요구시기
const [reqQty, setReqQty] = useState<string>(""); // 요구수량(m²/d)
const [usePurpose, setUsePurpose] = useState<string>(""); // 사용용도
const [hopePrice, setHopePrice] = useState<string>(""); // 희망단가
// 요구수질
const [coliform, setColiform] = useState<string>("");
const [noDetect, setNoDetect] = useState<boolean>(false);
const [coliform, setColiform] = useState<string>(""); // 총대장균군수(개/mg)
const [noDetect, setNoDetect] = useState<boolean>(false); // 총대장균군수 불검출
const [tn, setTn] = useState<string>(""); // 총질소(T-N)(mg/L)
const [residualCl, setResidualCl] = useState<string>(""); // 결합잔류염소(mg/L)
const [tp, setTp] = useState<string>(""); // 총인(T-P)(mg/L)
const [turbidity, setTurbidity] = useState<string>(""); //
const [ph, setPh] = useState<PHRange>({ min: "", max: "" }); // 수소이온농도(pH)
const [bod, setBod] = useState<string>(""); // 생물화학적산소요구량(BOD)(mg/L)
const [chloride, setChloride] = useState<string>(""); // 염화물(mgCL/L)
const [odor, setOdor] = useState<string>("불쾌하지 않을 것"); // 냄새
const [ec, setEc] = useState<string>(""); // 전기전도도(µs/cm)
const [color, setColor] = useState<string>(""); // 색도(도)
const [remark, setRemark] = useState<string>(""); // 비고
const [tn, setTn] = useState<string>("");
const [residualCl, setResidualCl] = useState<string>("");
const [tp, setTp] = useState<string>("");
const [turbidity, setTurbidity] = useState<string>("");
const [ph, setPh] = useState<PHRange>({ min: "", max: "" });
const [bod, setBod] = useState<string>("");
const [chloride, setChloride] = useState<string>("");
const [odor, setOdor] = useState<string>("불쾌하지 않을 것");
const [ec, setEc] = useState<string>("");
const [color, setColor] = useState<string>("");
const [remark, setRemark] = useState<string>("");
// 공급처 후보지 선택(필수아님)
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 (
<>
<main className="bg-white">
<main className="bg-white">
{/* 서브 타이틀 + 브레드크럼 */}
<SubPageHeader
title="수요정보등록"
crumbs={[
{ label: "Home", href: "/reuse" },
{ label: "수요신청", href: "/reuse/apply" },
{ label: "수요정보등록" },
]}
/>
{/* 서브 타이틀 + 브레드크럼 */}
<SubPageHeader
title="수요정보등록"
crumbs={[
{ label: "Home", href: "/main" },
{ label: "수요신청", href: "/apply/status" },
{ label: "수요정보등록" },
]}
/>
{/* 컨텐츠 영역 */}
<div>
{/* 컨텐츠 영역 */}
<div>
{/* 기본정보 테이블 */}
<div className="border border-neutral-200 bg-white">
<FormRow label="요구시기" required>
<DateInput value={reqDate} onChange={setReqDate} />
</FormRow>
{/* 기본정보 테이블 */}
<div className="border border-neutral-200 bg-white">
<FormRow label="요구시기" required>
<DateInput value={reqDate} onChange={setReqDate} />
</FormRow>
<Divider />
<Divider />
<FormRow label="요구수량(m²/d)" required>
<TextInput value={reqQty} onChange={setReqQty} placeholder="" />
</FormRow>
<FormRow label="요구수량(m²/d)" required>
<TextInput value={reqQty} onChange={setReqQty} placeholder="" />
</FormRow>
<Divider />
<Divider />
<FormRow label="사용용도" required>
<TextInput value={usePurpose} onChange={setUsePurpose} />
</FormRow>
<FormRow label="사용용도" required>
<TextInput value={usePurpose} onChange={setUsePurpose} />
</FormRow>
<Divider />
<Divider />
<FormRow label="희망단가(원)" required>
<TextInput value={hopePrice} onChange={setHopePrice} />
</FormRow>
<FormRow label="희망단가(원)" required>
<TextInput value={hopePrice} onChange={setHopePrice} />
</FormRow>
</div>
{/* 요구수질 섹션 타이틀 바 */}
<div className="grid grid-cols-12">
<div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-6 py-6 text-center">
<span className="text-lg font-semibold text-neutral-800">요구수질</span>
<button
type="button"
onClick={() => setOpenRef(true)}
className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50"
>
수질기준 참고
</button>
{/* 수질기준참고 모달 */}
<WaterQualityRefModal
open={openRef}
onClose={() => setOpenRef(false)}
imageSrc="/images/landing/water_quality_ref_1.png"
/>
</div>
</div>
{/* 요구수질 섹션 타이틀 바 */}
<div className="grid grid-cols-12">
<div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-6 py-6 text-center">
<span className="text-lg font-semibold text-neutral-800">요구수질</span>
<button
type="button"
onClick={() => setOpenRef(true)}
className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50"
>
수질기준 참고
</button>
{/* 수질기준참고 모달 */}
<WaterQualityRefModal
open={openRef}
onClose={() => setOpenRef(false)}
imageSrc="/images/landing/water_quality_ref_1.png"
{/* 요구수질 테이블 */}
<div className="border border-neutral-200 bg-white">
{/* 1행 */}
<QualityRow
leftLabel="총대장균군수(개/mg)"
leftRequired
leftControl={
<div className="flex items-center gap-4">
<TextInput
value={coliform}
onChange={setColiform}
disabled={noDetect}
className={noDetect ? "bg-neutral-100 text-neutral-400" : ""}
/>
<label className="flex items-center gap-2 text-sm text-neutral-700">
<input
type="checkbox"
checked={noDetect}
onChange={(e) => {
setNoDetect(e.target.checked);
if (e.target.checked) setColiform("");
}}
className="h-4 w-4 rounded border-neutral-300"
/>
불검출
</label>
</div>
}
rightLabel="총질소(T-N)(mg/L)"
rightControl={
<InlineSuffixInput value={tn} onChange={setTn} suffix="이하" />
}
/>
<Divider />
{/* 2행 */}
<QualityRow
leftLabel="결합잔류염소(mg/L)"
leftRequired
leftControl={<TextInput value={residualCl} onChange={setResidualCl} />}
rightLabel="총인(T-P)(mg/L)"
rightControl={
<InlineSuffixInput value={tp} onChange={setTp} suffix="이하" />
}
/>
<Divider />
{/* 3행 */}
<QualityRow
leftLabel="탁도(NTU)"
leftRequired
leftControl={
<InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" />
}
rightLabel="수소이온농도(pH)"
rightControl={
<div className="flex items-center gap-2">
<TextInput
value={ph.min}
onChange={(v) => setPh((p) => ({ ...p, min: v }))}
className="w-32"
/>
<span className="text-neutral-500">~</span>
<TextInput
value={ph.max}
onChange={(v) => setPh((p) => ({ ...p, max: v }))}
className="w-32"
/>
</div>
</div>
}
/>
<Divider />
{/* 요구수질 테이블 */}
<div className="border border-neutral-200 bg-white">
{/* 1행 */}
<QualityRow
leftLabel="총대장균군수(개/mg)"
leftRequired
leftControl={
<div className="flex items-center gap-4">
<TextInput
value={coliform}
onChange={setColiform}
disabled={noDetect}
className={noDetect ? "bg-neutral-100 text-neutral-400" : ""}
/>
<label className="flex items-center gap-2 text-sm text-neutral-700">
<input
type="checkbox"
checked={noDetect}
onChange={(e) => {
setNoDetect(e.target.checked);
if (e.target.checked) setColiform("");
}}
className="h-4 w-4 rounded border-neutral-300"
/>
불검출
</label>
</div>
}
rightLabel="총질소(T-N)(mg/L)"
rightControl={
<InlineSuffixInput value={tn} onChange={setTn} suffix="이하" />
}
/>
<Divider />
{/* 2행 */}
<QualityRow
leftLabel="결합잔류염소(mg/L)"
leftRequired
leftControl={<TextInput value={residualCl} onChange={setResidualCl} />}
rightLabel="총인(T-P)(mg/L)"
rightControl={
<InlineSuffixInput value={tp} onChange={setTp} suffix="이하" />
}
/>
<Divider />
{/* 3행 */}
<QualityRow
leftLabel="탁도(NTU)"
leftRequired
leftControl={
<InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" />
}
rightLabel="수소이온농도(pH)"
rightControl={
<div className="flex items-center gap-2">
<TextInput
value={ph.min}
onChange={(v) => setPh((p) => ({ ...p, min: v }))}
className="w-32"
/>
<span className="text-neutral-500">~</span>
<TextInput
value={ph.max}
onChange={(v) => setPh((p) => ({ ...p, max: v }))}
className="w-32"
/>
</div>
}
/>
<Divider />
{/* 4행 */}
<QualityRow
leftLabel={
<>
생물화학적산소요구량
<br />
(BOD)(mg/L)
</>
}
leftRequired
leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />}
rightLabel="염화물(mgCL/L)"
rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />}
/>
<Divider />
{/* 5행 */}
<QualityRow
leftLabel="냄새"
leftRequired
leftControl={
<select
value={odor}
onChange={(e) => 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"
>
<option>불쾌하지 않을 것</option>
<option>약간 있음</option>
<option>있음</option>
</select>
}
rightLabel="전기전도도(µs/cm)"
rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix="이하" />}
/>
<Divider />
{/* 6행 */}
<QualityRow
leftLabel="색도(도)"
leftRequired
leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />}
rightLabel="비고"
rightControl={
<textarea
value={remark}
onChange={(e) => setRemark(e.target.value)}
className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500"
/>
}
/>
<Divider />
{/* 후보지 선택 */}
<div className="grid grid-cols-12">
<LabelCell>
공급처 후보지 선택
{/* 4행 */}
<QualityRow
leftLabel={
<>
생물화학적산소요구량
<br />
(필수아님)
</LabelCell>
(BOD)(mg/L)
</>
}
leftRequired
leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />}
rightLabel="염화물(mgCL/L)"
rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />}
/>
<Divider />
<div className="col-span-12 md:col-span-10 p-6">
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
<TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" />
<TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" />
<TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" />
{/* 5행 */}
<QualityRow
leftLabel="냄새"
leftRequired
leftControl={
<select
value={odor}
onChange={(e) => 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"
>
<option>불쾌하지 않을 것</option>
<option>약간 있음</option>
<option>있음</option>
</select>
}
rightLabel="전기전도도(µs/cm)"
rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix="이하" />}
/>
<Divider />
<div className="flex items-center gap-3 xl:ml-2">
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700"
onClick={() => {
// TODO: 후보지 조회 모달/검색 연결
console.log("후보지 조회", { cand1, cand2, cand3 });
}}
>
후보지 조회
</button>
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600"
onClick={() => {
// TODO: 유사사례 연결
console.log("유사사례");
}}
>
유사사례
</button>
</div>
{/* 6행 */}
<QualityRow
leftLabel="색도(도)"
leftRequired
leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />}
rightLabel="비고"
rightControl={
<textarea
value={remark}
onChange={(e) => setRemark(e.target.value)}
className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500"
/>
}
/>
<Divider />
{/* 후보지 선택 */}
<div className="grid grid-cols-12">
<LabelCell>
공급처 후보지 선택
<br />
(필수아님)
</LabelCell>
<div className="col-span-12 md:col-span-10 p-6">
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
<TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" />
<TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" />
<TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" />
<div className="flex items-center gap-3 xl:ml-2">
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700"
onClick={() => {
// TODO: 후보지 조회 모달/검색 연결
console.log("후보지 조회", { cand1, cand2, cand3 });
}}
>
후보지 조회
</button>
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600"
onClick={() => {
// TODO: 유사사례 연결
console.log("유사사례");
}}
>
유사사례
</button>
</div>
</div>
</div>
</div>
{/* 하단 버튼 */}
<div className="flex justify-center py-12">
<button
type="button"
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90"
onClick={() => {
// TODO: 저장 처리
console.log("수요정보 등록");
}}
>
수요정보 등록
</button>
</div>
</div>
</main>
{/* 하단 버튼 */}
<div className="flex justify-center py-12">
<button
type="button"
disabled={submitting}
className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-70"
onClick={onSubmit}
>
{editMode ? "수요정보 수정" : "수요정보 등록"}
</button>
</div>
</div>
</main>
</>
);
}
@@ -65,12 +65,32 @@ export default function RainwaterReusePage() {
<Hr className="my-11" />
{/* 2) 빗물이용시설 설치 관리 법적 의무 대상 */}
<section className="pb-[200px]">
<section className="mt-6">
<SectionTitle>빗물이용시설 설치 관리 법적 의무 대상</SectionTitle>
<div className="mt-8">
<ImgCard src="/images/landing/intro/rainreuse_mandatory.png" alt="법적 의무 대상 설명" />
</div>
</section>
<Hr className="my-11" />
{/* 3) 빗물이용시설 설계 방안 */}
<section className="mt-6">
<SectionTitle>빗물이용시설 설계 방안</SectionTitle>
<div className="mt-8">
<ImgCard src="/images/landing/intro/rainwater_design.png" alt="빗물이용시설 설계 방안 설명" />
</div>
</section>
<Hr className="my-11" />
{/* 4) 빗물이용시설 시설기준 및 관리기준 */}
<section className="pb-[200px]">
<SectionTitle>빗물이용시설 시설기준 및 관리기준</SectionTitle>
<div className="mt-8">
<ImgCard src="/images/landing/intro/rainwater_standards.png" alt="빗물이용시설 시설기준 및 관리기준 설명" />
</div>
</section>
</div>
);
}
+2
View File
@@ -109,6 +109,7 @@ export type DemandCompBizType = {
dmdInfoFk: string | '';
bzmnNo: string | '';
dmdCoNm: string | '';
zip: string | '';
daddr: string | '';
telno: string | '';
rgtr: string | '';
@@ -719,6 +720,7 @@ export const SdmReducer = (set: any): SdmActions => {
bzmnNo: res.bzmnNo,
dmdCoNm: res.dmdCoNm,
dmdInfoId: res.dmdInfoId,
zip: res.zip || '',
daddr: res.daddr,
telno: res.telno.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3'),
rgtr: res.rgtr,
+3 -3
View File
@@ -64,8 +64,8 @@ export default function LoginModal({ open, onClose, onSuccess }: LoginModalProps
}, [open, onClose]);
useEffect(() => {
if (noWorker) {
setWorkerNo("");
if (noWorker) { // 종사업자번호 해당없음
setWorkerNo(""); // 종사업자번호
setErrors((p) => {
const nextErrors = { ...p };
delete nextErrors.workerNo;
@@ -127,7 +127,7 @@ export default function LoginModal({ open, onClose, onSuccess }: LoginModalProps
markAllTouched();
const v = validate();
if (Object.keys(v).length) return;
if (Object.keys(v).length) return; //검증 에러가 하나라도 있으면 로그인 요청을 중단
try {
setSubmitting(true);
@@ -1,4 +1,4 @@
import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page";
//import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page";
import { useDmStore } from "@/app/store";
import {
CANDIDATE,
@@ -946,10 +946,10 @@ export function SdmInfoRegForm({
)}
{isSimilarCase && (
<UiModal
body={
<ModalSimilarPage sply={sply} usageCd={usageCd} money={money} />
}
<UiModal body={}
// body={
// <ModalSimilarPage sply={sply} usageCd={usageCd} money={money} />
// }
bodyClass="sm:max-w-[100vw] w-[1400px] h-[100dvh] max-h-[900px] overflow-y-auto overflow-x-hidden"
open={isSimilarCase}
onOpenChange={(open) => !open && setIsSimilarCase(false)}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB