대민 수요신청 정보 변경

This commit is contained in:
leedj312
2025-07-30 17:57:03 +09:00
parent ebde6d433a
commit 886561535e
9 changed files with 948 additions and 444 deletions
@@ -7,7 +7,7 @@ import ListDiv from '@common/business_components/ui/list_div';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Toaster } from 'react-hot-toast'; import { Toaster } from 'react-hot-toast';
import SdmLogin from '../sdmLogin/page'; import SdmLogin from '../sdmLogin/page';
import type { DemandCompType } from '@/app/store/sdmMng'; import type { DemandCompBizType } from '@/app/store/sdmMng';
import TransValueFormat from '@/app/common/transValueFormat'; import TransValueFormat from '@/app/common/transValueFormat';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
@@ -31,8 +31,8 @@ export default function SdmApplyList({}: SdmApplyProps) {
const {selectSidoData, selectSigunData} = Common; const {selectSidoData, selectSigunData} = Common;
const [isLogin, setIsLogin] = useState(false); const [isLogin, setIsLogin] = useState(false);
const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false); const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false);
const [sdmApplyDetail, setSdmApplyDetail] = useState<DemandCompType>({} as DemandCompType); const [sdmApplyDetail, setSdmApplyDetail] = useState<DemandCompBizType>({} as DemandCompBizType);
const [transformedList, setTransformedList] = useState<DemandCompType[]>([]); const [transformedList, setTransformedList] = useState<DemandCompBizType[]>([]);
useEffect(() => { useEffect(() => {
console.log('sessionStorage : ',sessionStorage.getItem('user')); console.log('sessionStorage : ',sessionStorage.getItem('user'));
@@ -60,7 +60,7 @@ export default function SdmApplyList({}: SdmApplyProps) {
// 데이터 변환을 위한 별도의 useEffect // 데이터 변환을 위한 별도의 useEffect
useEffect(() => { useEffect(() => {
if (sdmInfoStatusList.length > 0) { if (sdmInfoStatusList.length > 0) {
const newTransformedList = sdmInfoStatusList.map((item: DemandCompType) => ({ const newTransformedList = sdmInfoStatusList.map((item: DemandCompBizType) => ({
...item, ...item,
ctpvCd: selectSidoData.find(sido => sido.val === item.ctpvCd?.trim())?.text || '', ctpvCd: selectSidoData.find(sido => sido.val === item.ctpvCd?.trim())?.text || '',
sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '' sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || ''
@@ -121,33 +121,33 @@ export default function SdmApplyList({}: SdmApplyProps) {
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>총대장균군수(개/100ml)</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>총대장균군수(개/100ml)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.grssClfg} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.grssClfg ?? ''} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>결합잔류염소(mg/L)​</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>결합잔류염소(mg/L)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch ?? ''} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>탁도(NTU)</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>탁도(NTU)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt ?? ''} format="number"/>}</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>생물학적 산소요구량(BOD)(mg/L)</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>생물학적 산소요구량(BOD)(mg/L)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt ?? ''} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>냄새</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>냄새</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>색도(도)</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>색도(도)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty ?? ''} format="number"/>}</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>총질소(T-N)(mg/L)</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>총질소(T-N)(mg/L)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn ?? ''} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>총인(T-P)(mg/L)​</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>총인(T-P)(mg/L)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph ?? ''} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>수소이온농도(pH)​</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>수소이온농도(pH)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.ph} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.ph} format="number"/>}</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>염화물(mgCL/L)​</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>염화물(mgCL/L)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt ?? ''} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>전기전도도(㎲/cm)​</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>전기전도도(㎲/cm)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv} format="number"/>}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv ?? ''} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>비고</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.header}>비고</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.rmrk}</TableCell> <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.rmrk}</TableCell>
</TableRow> </TableRow>
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useDmStore } from "@/app/store"; import { useDmStore } from "@/app/store";
import type { DemandCompType } from "@/app/store/sdmMng"; import type { DemandCompBizType } from "@/app/store/sdmMng";
import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component"; import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component";
import { UiModal } from "@common/business_components/ui/dialog_ui"; import { UiModal } from "@common/business_components/ui/dialog_ui";
import { Button } from "@common/components/ui"; import { Button } from "@common/components/ui";
@@ -14,121 +14,99 @@ import * as z from "zod";
import SdmLogin from "../sdmLogin/page"; import SdmLogin from "../sdmLogin/page";
const sdmInfoFormSchema = z.object({ const sdmInfoFormSchema = z.object({
sdmNm: z.string() sdmNm: z.string().min(1, "수요처를 선택해주세요"),
.min(1, "수요처를 선택해주세요"),
demandYmd: z.date().optional(), demandYmd: z.date().optional(),
demandQnty: z.string() demandQnty: z
.string()
.min(1, "요구수량을 입력해주세요") .min(1, "요구수량을 입력해주세요")
.regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"),
usePurpose: z.string().optional(), usePurpose: z.string().optional(),
money: z.string() money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"),
.regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"),
/**요구수질 */ /**요구수질 */
reqWaterQlty: z.string().optional(), reqWaterQlty: z.string().optional(),
grssClfg: z.string() grssClfg: z.number().nullish(),
.min(1, "총대장균군수를 입력해주세요") tn: z.number().nullish(),
.regex(/^[0-9]+$/, "총대장균군수는 숫자만 입력 가능합니다"), combResdch: z.number().nullish(),
tn: z.string() tph: z.number().nullish(),
.min(1, "총질소를 입력해주세요") trbt: z.number().nullish(),
.regex(/^[0-9]+$/, "총질소는 숫자만 입력 가능합니다"), ph: z.string().nullish(),
combResdch: z.string() bod: z.number().nullish(),
.min(1, "결합잔류염소를 입력해주세요")
.regex(/^[0-9]+$/, "결합잔류염소는 숫자만 입력 가능합니다"),
tph: z.string()
.min(1, "총인산물을 입력해주세요")
.regex(/^[0-9]+$/, "총인산물은 숫자만 입력 가능합니다"),
trbt: z.string()
.min(1, "탁도를 입력해주세요")
.regex(/^[0-9]+$/, "탁도는 숫자만 입력 가능합니다"),
ph: z.string()
.min(1, "수소이온농도를 입력해주세요")
.regex(/^[0-9]+$/, "수소이온농도는 숫자만 입력 가능합니다"),
bod: z.string()
.min(1, "생물학적 산소요구량을 입력해주세요")
.regex(/^[0-9]+$/, "생물학적 산소요구량은 숫자만 입력 가능합니다"),
/**컬럼확인필요 */ /**컬럼확인필요 */
clIonQnt: z.string() clIonQnt: z.number().nullish(),
.min(1, "염화물을 입력해주세요") smll: z.string(),
.regex(/^[0-9]+$/, "염화물은 숫자만 입력 가능합니다"), cdtnv: z.number().nullish(),
smll: z.string() crmty: z.number().nullish(),
.min(1, "냄새를 입력해주세요")
.regex(/^[0-9]+$/, "냄새는 숫자만 입력 가능합니다"),
cdtnv: z.string()
.min(1, "전기전도도를 입력해주세요")
.regex(/^[0-9]+$/, "전기전도도는 숫자만 입력 가능합니다"),
crmty: z.string()
.min(1, "색도를 입력해주세요")
.regex(/^[0-9]+$/, "색도는 숫자만 입력 가능합니다"),
rmrk: z.string().optional(), rmrk: z.string().optional(),
/* telno: z.string() /* telno: z.string()
.min(8, "전화번호는 8자 이상 입력해주세요") .min(8, "전화번호는 8자 이상 입력해주세요")
.max(12, "전화번호는 12자 이내로 입력해주세요") .max(12, "전화번호는 12자 이내로 입력해주세요")
.regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */
candidateTitle: z.array(z.object({ candidateTitle: z
ruseUtztnFcltNm: z.string(), .array(
ruseUtztnFcltCd: z.string(), z.object({
})).optional(), ruseUtztnFcltNm: z.string(),
ruseUtztnFcltCd: z.string(),
})
)
.optional(),
}); });
export type FormData = z.infer<typeof sdmInfoFormSchema>; export type FormData = z.infer<typeof sdmInfoFormSchema>;
function SdmInfoInsertContent() { function SdmInfoInsertContent() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const {insertSdmInfoData, Sdm ,setSubsInfo} = useDmStore((state) => state); const { insertSdmInfoData, Sdm, setSubsInfo } = useDmStore((state) => state);
const { successLogin } = Sdm; const { successLogin } = Sdm;
const [ismoneyDisabled, setIsmoneyDisabled] = useState(false); const [ismoneyDisabled, setIsmoneyDisabled] = useState(false);
const formSchema = sdmInfoFormSchema; const formSchema = sdmInfoFormSchema;
const [isLogin, setIsLogin] = useState(false); const [isLogin, setIsLogin] = useState(false);
const [facilityName, setFacilityName] = useState<string>(''); const [facilityName, setFacilityName] = useState<string>("");
const [defaultValues, setDefaultValues] = useState<FormData>({ const [defaultValues, setDefaultValues] = useState<FormData>({
sdmNm: '', sdmNm: "",
demandYmd: new Date(), demandYmd: new Date(),
demandQnty: '', demandQnty: "",
usePurpose: '', usePurpose: "",
money: '', money: "",
/**요구수질 */ /**요구수질 */
reqWaterQlty: '', reqWaterQlty: "",
grssClfg: '', grssClfg: null,
tn: '', tn: null,
combResdch: '', combResdch: null,
tph: '', tph: null,
trbt: '', trbt: null,
ph: '', ph: null,
bod: '', bod: null,
/**컬럼확인필요 */ /**컬럼확인필요 */
clIonQnt: '', clIonQnt: null,
smll: '', smll: "",
cdtnv: '', cdtnv: null,
crmty: '', crmty: null,
rmrk: '', rmrk: "",
/** 후보지 제목 */ /** 후보지 제목 */
candidateTitle: [ candidateTitle: [
{ ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" },
{ ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" },
{ ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' }, { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" },
], ],
}); });
useEffect(() => { useEffect(() => {
const user = sessionStorage.getItem('user'); const user = sessionStorage.getItem("user");
setDefaultValues(prev => ({ setDefaultValues((prev) => ({
...prev, ...prev,
sdmNm: searchParams.get('dmdCoNm') || '', sdmNm: searchParams.get("dmdCoNm") || "",
})); }));
sessionStorage.setItem('user', searchParams.get('dmdInfoId') || ''); sessionStorage.setItem("user", searchParams.get("dmdInfoId") || "");
setIsLogin(user || searchParams.get('dmdInfoId') ? true : false); setIsLogin(user || searchParams.get("dmdInfoId") ? true : false);
}, [searchParams]); }, [searchParams]);
useEffect(() => { useEffect(() => {
const local = localStorage.getItem('user'); const local = localStorage.getItem("user");
if (local) { if (local) {
setIsLogin(true); setIsLogin(true);
} }
setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만) setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만)
}, [setSubsInfo]); }, [setSubsInfo]);
const form = useForm<FormData>({ const form = useForm<FormData>({
@@ -139,99 +117,138 @@ function SdmInfoInsertContent() {
const subsInfo = useDmStore((state) => state.map.subsInfo); const subsInfo = useDmStore((state) => state.map.subsInfo);
useEffect(()=>{ useEffect(() => {
//map.subsInfo 스토어 객체 값이 변경되면 해당 값이 폼에 자동으로 들어가도록 수정 대신 값이 있어야함 //map.subsInfo 스토어 객체 값이 변경되면 해당 값이 폼에 자동으로 들어가도록 수정 대신 값이 있어야함
if(subsInfo.length>0){ if (subsInfo.length > 0) {
form.setValue('candidateTitle', subsInfo) form.setValue("candidateTitle", subsInfo);
} }
}, [subsInfo, form]);
},[subsInfo, form]) //후보지 팝업 값 셋팅
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data?.type === "SET_SUBS_INFO") {
setSubsInfo(event.data.payload); // zustand로 값 설정
}
};
//후보지 팝업 값 셋팅 window.addEventListener("message", handleMessage);
useEffect(() => { return () => window.removeEventListener("message", handleMessage);
const handleMessage = (event: MessageEvent) => { }, [setSubsInfo]);
if (event.data?.type === 'SET_SUBS_INFO') {
setSubsInfo(event.data.payload); // zustand로 값 설정
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [setSubsInfo]);
// 로그인 성공 시 모달 닫기 및 폼 업데이트 // 로그인 성공 시 모달 닫기 및 폼 업데이트
useEffect(() => { useEffect(() => {
if (successLogin) { if (successLogin) {
setIsLogin(true); setIsLogin(true);
const user = sessionStorage.getItem('user'); const user = sessionStorage.getItem("user");
if (user) { if (user) {
try { try {
const userData = JSON.parse(user); const userData = JSON.parse(user);
if (userData.dmdCoNm) { if (userData.dmdCoNm) {
setFacilityName(userData.dmdCoNm); setFacilityName(userData.dmdCoNm);
form.setValue('sdmNm', userData.dmdCoNm); form.setValue("sdmNm", userData.dmdCoNm);
} }
} catch (error) { } catch (error) {
console.error('Error parsing user data:', error); console.error("Error parsing user data:", error);
} }
} }
} }
}, [successLogin, form]); }, [successLogin, form]);
useEffect(() => {
console.log("Form state changed:", form.formState.errors);
}, [form.formState.errors]);
// 체크박스 상태가 변경될 때마다 money 필드의 에러를 초기화 // 체크박스 상태가 변경될 때마다 money 필드의 에러를 초기화
useEffect(() => { useEffect(() => {
if (ismoneyDisabled) { if (ismoneyDisabled) {
form.clearErrors('money'); form.clearErrors("money");
form.setValue('money', ''); form.setValue("money", "");
} }
}, [ismoneyDisabled, form]); }, [ismoneyDisabled, form]);
const router = useRouter(); const router = useRouter();
const onSubmit: SubmitHandler<FormData> = async (data) => { const onSubmit: SubmitHandler<FormData> = async (data) => {
try { try {
// Transform form data to match API schema const dmSubsInfo = data.candidateTitle?.filter(
const apiData: DemandCompType = { (item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd
dmdInfoId: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''), );
dmdInfoFk: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''), // Transform form data to madata.candidateTitletch API schema
dmdCoNm: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdCoNm?.toString() || data.sdmNm, const apiData: DemandCompBizType = {
ctpvCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.ctpvCd?.toString() || searchParams.get('ctpvCd') || '', dmdInfoId:
sggCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.sggCd?.toString() || searchParams.get('sggCd') || '', JSON.parse(
daddr: JSON.parse(sessionStorage.getItem('user') || '{}')?.daddr?.toString() || searchParams.get('daddr') || '', sessionStorage.getItem("user") || "{}"
telno: JSON.parse(sessionStorage.getItem('user') || '{}')?.telno?.toString() || searchParams.get('telno') || '', )?.dmdInfoId?.toString() ||
rgtr: JSON.parse(sessionStorage.getItem('user') || '{}')?.rgtr?.toString() || searchParams.get('rgtr') || '', searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""),
useUsgCd: data.usePurpose || '', dmdInfoFk:
wntUntprc: Number(data.money) || '', JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.dmdInfoId?.toString() ||
searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""),
dmdCoNm:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.dmdCoNm?.toString() || data.sdmNm,
ctpvCd:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.ctpvCd?.toString() ||
searchParams.get("ctpvCd") ||
"",
sggCd:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.sggCd?.toString() ||
searchParams.get("sggCd") ||
"",
daddr:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.daddr?.toString() ||
searchParams.get("daddr") ||
"",
telno:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.telno?.toString() ||
searchParams.get("telno") ||
"",
rgtr:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.rgtr?.toString() ||
searchParams.get("rgtr") ||
"",
useUsgCd: data.usePurpose || "",
wntUntprc: Number(data.money) || "",
dmndTmng: data.demandYmd || new Date(), dmndTmng: data.demandYmd || new Date(),
dmndQty: Number(data.demandQnty) || '', dmndQty: Number(data.demandQnty) || "",
grssClfg: Number(data.grssClfg) || '', grssClfg: data.grssClfg === null ? -1 : Number(data.grssClfg),
tn: Number(data.tn) || '', tn: Number(data.tn) || null,
combResdch: Number(data.combResdch) || '', combResdch: Number(data.combResdch) || null,
tph: Number(data.tph) || '', tph: Number(data.tph) || null,
trbt: Number(data.trbt) || '', trbt: Number(data.trbt) || null,
ph: Number(data.ph) || '', ph: data.ph || "",
blgcOxygDmndQnt: Number(data.bod) || '', blgcOxygDmndQnt: Number(data.bod) ||null,
clIonQnt: Number(data.clIonQnt) || '', clIonQnt: Number(data.clIonQnt) || null,
smll: Number(data.smll) || '', smll: data.smll || "",
cdtnv: Number(data.cdtnv) || '', cdtnv: Number(data.cdtnv) ||null,
crmty: Number(data.crmty) || '', crmty: Number(data.crmty) || null,
rmrk: data.rmrk || '', rmrk: data.rmrk || "",
bzmnNo: "", bzmnNo: "",
dmdAplyPswd: "", dmdAplyPswd: "",
index: 0, index: 0,
reqDt: "", reqDt: "",
dmdInfoSn: 0, dmdInfoSn: 0,
status: '', status: "",
splyCndtList: data.candidateTitle || [], splyCndtList: dmSubsInfo || [],
}; };
await insertSdmInfoData(apiData); await insertSdmInfoData(apiData);
setTimeout(() => { setTimeout(() => {
router.push("/"); router.push("/");
}, 1000); }, 1000); // 0.8초 후 이동
} catch (error) { } catch (error) {
console.error('Error submitting form:', error); console.error("Error submitting form:", error);
} }
}; };
@@ -254,8 +271,10 @@ const router = useRouter();
/> />
<div className="flex justify-center pt-4"> <div className="flex justify-center pt-4">
<Button <Button
color="sky"
size={"sm"}
type="submit" type="submit"
className="w-40 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200" className="w-40 font-semibold py-2 px-4 rounded-md "
> >
수요정보 등록 수요정보 등록
</Button> </Button>
@@ -265,7 +284,7 @@ const router = useRouter();
</div> </div>
</div> </div>
{!isLogin ? {!isLogin ? (
<UiModal <UiModal
body={<SdmLogin />} body={<SdmLogin />}
bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]" bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]"
@@ -278,7 +297,7 @@ const router = useRouter();
noHeaderYn="Y" noHeaderYn="Y"
noFooterYn="Y" noFooterYn="Y"
/> />
: null} ) : null}
<Toaster position="top-center" /> <Toaster position="top-center" />
</div> </div>
); );
@@ -291,4 +310,3 @@ export default function MyForm() {
</Suspense> </Suspense>
); );
} }
@@ -1,8 +1,8 @@
"use client"; "use client";
import { useDmStore } from "@/app/store"; import { useDmStore } from "@/app/store";
import type { DemandCompType } from "@/app/store/sdmMng";
import SdmInsertForm from '@common/business_components/ui/sdmInsertForm'; import SdmInsertForm from '@common/business_components/ui/sdmInsertForm';
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
@@ -44,15 +44,9 @@ export default function MyForm() {
const handleInsertSdmData = useCallback((data: import('@common/business_components/ui/sdmInsertForm/form').DemandCompType) => { const handleInsertSdmData = useCallback((data: import('@common/business_components/ui/sdmInsertForm/form').DemandCompType) => {
const convertedData: DemandCompType = { const convertedData: DemandCompType = {
...data, ...data,
dmdInfoSn: '',
dmdInfoFk: '',
status: '',
splyCndtList: []
}; };
formDataRef.current = convertedData; formDataRef.current = convertedData;
insertSdmData(convertedData).catch(error => { insertSdmData(convertedData)
console.error('Error inserting SDM data:', error);
});
}, [insertSdmData]); }, [insertSdmData]);
return ( return (
@@ -70,4 +64,3 @@ export default function MyForm() {
</div> </div>
); );
} }
+153 -21
View File
@@ -2,12 +2,13 @@
import type { SelectDataType } from "@common/business_components/ui"; import type { SelectDataType } from "@common/business_components/ui";
import type { TableUpperProps } from "../common"; import type { TableUpperProps } from "../common";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
/**==================================== /**====================================
* Store - 타입 * Store - 타입
=====================================*/ =====================================*/
// 수요처 정보 // 수요처 정보
export type DemandCompType = { export type DemandCompBizType = {
index : number | ''; index : number | '';
dmdInfoId : string | ''; dmdInfoId : string | '';
dmdInfoFk : string | ''; dmdInfoFk : string | '';
@@ -23,17 +24,17 @@ export type DemandCompType = {
wntUntprc : number | ''; wntUntprc : number | '';
dmndTmng : Date | ''; dmndTmng : Date | '';
dmndQty : number | ''; dmndQty : number | '';
grssClfg : number | ''; grssClfg : number | null //총대장균군수
tn : number | ''; tn : number | null //총질소
combResdch : number | ''; combResdch : number | null //결합잔류염소
tph : number | ''; tph : number | null //총인
trbt : number | ''; trbt : number | null //탁도
ph : number | ''; ph : string | ''; //수소이온농
blgcOxygDmndQnt : number | ''; blgcOxygDmndQnt : number | null //생물학적산소요구량
clIonQnt : number | ''; clIonQnt : number | null //염화물
smll : number | ''; smll : string | ''; //냄새
cdtnv : number | ''; cdtnv : number | null; //전기전도도
crmty : number | ''; crmty : number | null //색도
rmrk : string | ''; rmrk : string | '';
reqDt : string | ''; reqDt : string | '';
dmdInfoSn : number | ''; dmdInfoSn : number | '';
@@ -152,7 +153,8 @@ export type MoneyDetailType = {
clclnBrdnCst : string | ''; clclnBrdnCst : string | '';
clclnEcqrRt : string | ''; clclnEcqrRt : string | '';
} }
// 규칙 타입 선언
export type DisableRulesType = Record<string, string[]>;
/**==================================== /**====================================
* Store - 데이터 * Store - 데이터
====================================*/ ====================================*/
@@ -160,6 +162,131 @@ export type MoneyDetailType = {
const demandCompList : DemandCompType[] = [ const demandCompList : DemandCompType[] = [
] ]
const disableRules: DisableRulesType = {
"청소": ["tn", 'tph',"clIonQnt" ,'cdtnv'],
"화장실용수": ["tn", 'tph',"clIonQnt" ,'cdtnv'],
"세척": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'],
"살수": ["combResdch", "crmty",'tph',"clIonQnt",'cdtnv'],
"조경": ["combResdch", "crmty",'tph','cdtnv'],
"농업_간접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'],
"농업_직접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'],
"하천": ["combResdch", "trbt", "crmty" ,'clIonQnt','cdtnv'],
'공업':[]
};
const defaultRules: Record<string, object> = {
"청소": {
grssClfg: null,
combResdch:0.2,
trbt: 2,
bod: 5,
crmty: 20,
ph: "5.8~8.5",
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"화장실용수": {
grssClfg: null,
combResdch:0.2,
trbt: 2,
bod: 5,
crmty: 20,
ph: "5.8~8.5",
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"세척": {
grssClfg: null,
trbt: 2,
bod: 5,
ph: "5.8~8.5",
combResdch:null,
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"살수": {
grssClfg: null,
trbt: 2,
bod: 5,
ph: "5.8~8.5",
combResdch:null,
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"조경": {
grssClfg: null,
trbt: 2,
bod: 5,
ph: "5.8~8.5",
clIonQnt:250,
combResdch:null,
crmty:null,
tn:null,
tph: null,
cdtnv: null,
},
"농업_간접용수": {
grssClfg: null,
trbt: 5,
bod: 8,
ph: "5.8~8.5",
cdtnv:2000,
combResdch:null,
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
},
"농업_직접용수": {
grssClfg: null,
trbt: 2,
bod: 8,
ph: "5.8~8.5",
cdtnv:700,
combResdch:null,
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
},
"하천": {
grssClfg: 1000,
bod: 5,
tn: 10,
tph: 0.5,
ph: "5.8~8.5",
combResdch:null,
trbt:null,
crmty:null,
clIonQnt: null,
cdtnv:null
},
공업:{
grssClfg: null,
bod: null,
tn: null,
tph: null,
ph: "~",
combResdch:null,
trbt:null,
crmty:null,
clIonQnt: null,
cdtnv:null
}
};
// 사용용도 // 사용용도
const useTypesData : SelectDataType[] = [ const useTypesData : SelectDataType[] = [
{ text: '청소', val: "청소" }, { text: '청소', val: "청소" },
@@ -167,7 +294,8 @@ const useTypesData : SelectDataType[] = [
{ text: "세척", val: "세척" }, { text: "세척", val: "세척" },
{ text: "살수", val: "살수" }, { text: "살수", val: "살수" },
{ text: "조경", val: "조경" }, { text: "조경", val: "조경" },
{ text: "농업", val: "농업" }, { text: "농업_간접용수", val: "농업_간접용수" },
{ text: "농업_직접용수", val: "농업_직접용수" },
{ text: "하천", val: "하천" }, { text: "하천", val: "하천" },
{ text: "지하수", val: "지하수" }, { text: "지하수", val: "지하수" },
{ text: "공업", val: "공업" }, { text: "공업", val: "공업" },
@@ -280,7 +408,7 @@ const postSdmData = async (data: DemandCompType) => {
} }
// 수요정보 등록 // 수요정보 등록
const postSdmInfoData = async (data: DemandCompType) => { const postSdmInfoData = async (data: DemandCompBizType) => {
try { try {
await postFetch("/reuseFc/request/demandInfo", data); await postFetch("/reuseFc/request/demandInfo", data);
toast.success('수요 정보 등록에 성공하였습니다.'); toast.success('수요 정보 등록에 성공하였습니다.');
@@ -360,7 +488,7 @@ const fetchGetSimilarCaseList = async (part: string, value: number|string) => {
export type SdmActions = { export type SdmActions = {
initializeSdmData: () => void; initializeSdmData: () => void;
insertSdmData: (data: DemandCompType) => Promise<void>; insertSdmData: (data: DemandCompType) => Promise<void>;
insertSdmInfoData: (data: DemandCompType) => Promise<void>; insertSdmInfoData: (data: DemandCompBizType) => Promise<void>;
getDemandCompList: (searchText: string, pageCnt: number) => Promise<void>; getDemandCompList: (searchText: string, pageCnt: number) => Promise<void>;
actionLogin: (data: DemandCompType) => Promise<void>; actionLogin: (data: DemandCompType) => Promise<void>;
getSdmInfoStatusList: (dmdInfoId: string) => Promise<void>; getSdmInfoStatusList: (dmdInfoId: string) => Promise<void>;
@@ -378,9 +506,10 @@ export type SdmType ={
moneyDetailList: MoneyDetailType[]; moneyDetailList: MoneyDetailType[];
// 수요정보 신청현황 // 수요정보 신청현황
sdmInfoStatusList: DemandCompType[]; sdmInfoStatusList: DemandCompBizType[];
upHeadSdmInfoStatus: TableUpperProps[]; upHeadSdmInfoStatus: TableUpperProps[];
defaultRules: Record<string, object>
disableRules: DisableRulesType;
// 로그인관련 // 로그인관련
successNextPage : boolean; successNextPage : boolean;
successLogin: boolean; successLogin: boolean;
@@ -398,7 +527,10 @@ export const SdmState: SdmType = {
moneyDetailList: moneyDetailList, moneyDetailList: moneyDetailList,
// 수요정보 신청현황 // 수요정보 신청현황
sdmInfoStatusList: [], sdmInfoStatusList: [],
defaultRules:defaultRules,
disableRules:disableRules,
upHeadSdmInfoStatus: upHeadSdmInfoStatus, upHeadSdmInfoStatus: upHeadSdmInfoStatus,
// 로그인관련 // 로그인관련
successNextPage : false, successNextPage : false,
successLogin: false, successLogin: false,
@@ -428,7 +560,7 @@ export const SdmReducer = (set: any): SdmActions => {
} }
}, },
insertSdmInfoData: async (data: DemandCompType) => { insertSdmInfoData: async (data: DemandCompBizType) => {
postSdmInfoData(data); postSdmInfoData(data);
}, },
@@ -465,9 +597,9 @@ export const SdmReducer = (set: any): SdmActions => {
})) }))
return; return;
} }
const sdmInfoStatusList : DemandCompType[] = []; const sdmInfoStatusList : DemandCompBizType[] = [];
res.demandInfo.map((el: DemandCompType, index: number) => { res.demandInfo.map((el: DemandCompBizType, index: number) => {
const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn); const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn);
const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청'; const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청';
@@ -1,5 +1,5 @@
import { useDmStore } from '@/app/store'; import { useDmStore } from '@/app/store';
import type { DemandCompType } from '@/app/store/sdmMng'; import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
type thisProps = { type thisProps = {
@@ -0,0 +1,18 @@
export const sanitizeInput = (val: string, allowDecimal = false) => {
if (val === "") return ""; // 빈 값 허용
// 소숫점 허용
if (allowDecimal) {
// 숫자와 점만 허용
let cleaned = val.replace(/[^0-9.]/g, "");
// 소숫점 여러 개일 경우 첫 번째만 유지
const parts = cleaned.split(".");
if (parts.length > 2) {
cleaned = parts[0] + "." + parts.slice(1).join("");
}
return cleaned;
}
// 정수만 허용
return val.replace(/[^0-9]/g, "");
};
@@ -1,27 +1,29 @@
import { FormUi } from "@common/business_components/ui"; import { FormUi } from "@common/business_components/ui";
import type { ReactNode } from "react";
import type { ControllerRenderProps, FieldError, FieldValues, Path } from "react-hook-form"; import type { ControllerRenderProps, FieldError, FieldValues, Path } from "react-hook-form";
import { cloneElement, isValidElement } from "react";
interface AccessibleFormFieldProps<T extends FieldValues> { interface AccessibleFormFieldProps<T extends FieldValues> {
field: ControllerRenderProps<T, Path<T>>; field: ControllerRenderProps<T, Path<T>>;
label: string; label: string;
required?: boolean; required?: boolean;
error?: FieldError; error?: FieldError;
inputComponent: ReactNode; isDisabled?: boolean;
inputComponent: React.ReactElement<{ disabled?: boolean }>;
} }
export function AccessibleFormField<T extends FieldValues>({ export function AccessibleFormField<T extends FieldValues>({
label, label,
required = false, required = false,
inputComponent, inputComponent,
isDisabled = false,
}: AccessibleFormFieldProps<T>) { }: AccessibleFormFieldProps<T>) {
const labelText = required ? `${label} *` : label; const labelText = required ? `${label} *` : label;
return ( return (
<FormUi label={labelText}> <FormUi label={labelText} isDisabled={isDisabled}>
<div> <div>
{inputComponent} {isValidElement(inputComponent)
? cloneElement(inputComponent, { disabled: isDisabled } as Partial<typeof inputComponent.props>)
: inputComponent}
{/* {error?.message && Array.from(new Set(error.message.split('\n'))).map((msg, idx) => ( {/* {error?.message && Array.from(new Set(error.message.split('\n'))).map((msg, idx) => (
<p key={idx} className="text-red-500 text-sm mt-1" role="alert"> <p key={idx} className="text-red-500 text-sm mt-1" role="alert">
{msg} {msg}
@@ -11,13 +11,20 @@ import {
UiModal, UiModal,
} from "@common/business_components"; } from "@common/business_components";
import { Input } from "@common/components"; import { Input } from "@common/components";
import { Button } from "@common/components/ui"; import { Button, Label } from "@common/components/ui";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { AccessibleFormField } from "./AccessibleFormField"; import { AccessibleFormField } from "./AccessibleFormField";
import type { ControllerRenderProps, UseFormReturn, FieldError } from "react-hook-form"; import {
type ControllerRenderProps,
type UseFormReturn,
type FieldError,
useWatch,
} from "react-hook-form";
import SearchInputText from "../common/searhDemandInput/component"; import SearchInputText from "../common/searhDemandInput/component";
import type { DemandCompType } from "@/app/store/sdmMng"; import React from "react";
import { sanitizeInput } from "../common/utils/utils";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
export interface SdmInfoRegFormProps { export interface SdmInfoRegFormProps {
form: UseFormReturn<FormData>; form: UseFormReturn<FormData>;
@@ -35,12 +42,15 @@ export function SdmInfoRegForm({
facilityName, facilityName,
}: SdmInfoRegFormProps) { }: SdmInfoRegFormProps) {
const { initializeCommonData, Sdm } = useDmStore((state) => state); const { initializeCommonData, Sdm } = useDmStore((state) => state);
const { useTypesData } = Sdm; const { useTypesData, defaultRules, disableRules } = Sdm;
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [sdmNmValue, setSdmNmValue] = useState<string>( const [sdmNmValue, setSdmNmValue] = useState<string>(
searchParams.get("dmdCoNm") || "" searchParams.get("dmdCoNm") || ""
); );
const [frontPHdata, setFrontPHdata] = useState("0");
const [backPHdata, setBackPHdata] = useState("0");
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false); const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false);
const [isSimilarCase, setIsSimilarCase] = useState(false); const [isSimilarCase, setIsSimilarCase] = useState(false);
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
@@ -58,6 +68,7 @@ export function SdmInfoRegForm({
useEffect(() => { useEffect(() => {
console.log("candidateTitle ", form.getValues("candidateTitle")); console.log("candidateTitle ", form.getValues("candidateTitle"));
}, [form.getValues]); }, [form.getValues]);
useEffect(() => { useEffect(() => {
if (useTypesData && useTypesData.length > 0) { if (useTypesData && useTypesData.length > 0) {
setTimeout(() => { setTimeout(() => {
@@ -109,6 +120,54 @@ export function SdmInfoRegForm({
onCheckboxChange?.(checked); onCheckboxChange?.(checked);
}; };
useEffect(() => {
const watchPH = form.getValues(`ph`);
if (watchPH && watchPH !== undefined) {
const [frontPH, backPH] = watchPH.split("~");
if (frontPH) {
setFrontPHdata(frontPH);
}
if (backPH) {
setBackPHdata(backPH);
}
} else {
setFrontPHdata("0");
setBackPHdata("0");
}
}, [form.getValues(`ph`)]);
const isSetPh = useCallback(
(typePHdata: string, index: number) => {
const PHdata = form.getValues("ph");
const isPH = PHdata?.split("~")[index] || "";
if (index == 0) {
form.setValue("ph", `${isPH}~${typePHdata}`);
setBackPHdata(typePHdata);
} else if (index == 1) {
form.setValue("ph", `${typePHdata}~${isPH}`);
setFrontPHdata(typePHdata);
}
},
[form]
);
const usePurpose = useWatch({ control: form.control, name: "usePurpose" });
useEffect(() => {
if (!usePurpose) return;
// Apply default values
const defaults = defaultRules[usePurpose];
console.log("usePurpose", usePurpose);
console.log("defaults", defaults);
if (defaults) {
Object.entries(defaults).forEach(([field, value]) => {
form.setValue(field as keyof FormData, value, { shouldDirty: true });
});
}
}, [form.getValues("usePurpose")]);
// 시설 선택시 이벤트 // 시설 선택시 이벤트
const selectComp = useCallback( const selectComp = useCallback(
(rtnData: DemandCompType[]) => { (rtnData: DemandCompType[]) => {
@@ -178,7 +237,6 @@ export function SdmInfoRegForm({
required: true, required: true,
component: (field) => ( component: (field) => (
<Input <Input
placeholder="요구수량(㎥/d)을 입력하세요"
type="text" type="text"
className="!text-xl text-gray-600" className="!text-xl text-gray-600"
value={field.value as string} value={field.value as string}
@@ -222,7 +280,6 @@ export function SdmInfoRegForm({
isDisabled={isBznmTotNoDisabled} isDisabled={isBznmTotNoDisabled}
> >
<Input <Input
placeholder="희망단가(원/㎥)을 입력하세요"
type="text" type="text"
className="!text-xl text-gray-600" className="!text-xl text-gray-600"
value={field.value as string} value={field.value as string}
@@ -244,221 +301,7 @@ export function SdmInfoRegForm({
required: true, required: true,
component: undefined, component: undefined,
}, },
{
name: "grssClfg",
label: "총대장균군수(개/100ml)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="총대장균군수를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "tn",
label: "총질소(T-N)(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="총질소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "combResdch",
label: "결합잔류염소(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="결합잔류염소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "tph",
label: "총인(T-P)(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="총인을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "trbt",
label: "탁도(NTU)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="탁도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "ph",
label: "수소이온농도(pH)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="수소이온농도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "bod",
label: "생물학적 산소요구량(BOD)(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="생물학적 산소요구량을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "clIonQnt",
label: "염화물(mgCL/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="염화물을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "smll",
label: "냄새",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="냄새를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "cdtnv",
label: "전기전도도(㎲/cm)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="전기전도도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "crmty",
label: "색도(도)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="색도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "rmrk",
label: "비고",
formIndex: 1,
component: (field) => (
<Input
placeholder="비고를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{ {
name: "candidateTitle", name: "candidateTitle",
label: CANDIDATE || "공급처 후보지 선택(필수아님)", label: CANDIDATE || "공급처 후보지 선택(필수아님)",
@@ -466,10 +309,20 @@ export function SdmInfoRegForm({
component: () => ( component: () => (
<div> <div>
<div className="-mt-14 float-right flex gap-10"> <div className="-mt-14 float-right flex gap-10">
<Button className={"bg-gray-200"} onClick={searchCandidate}> <Button
className="rounded-md"
color="sky"
size={"sm"}
onClick={searchCandidate}
>
후보지 조회 후보지 조회
</Button> </Button>
<Button className={"bg-gray-200"} onClick={searchSimilarCase}> <Button
className="rounded-md"
color="sky"
size={"sm"}
onClick={searchSimilarCase}
>
유사사례 유사사례
</Button> </Button>
</div> </div>
@@ -495,9 +348,9 @@ export function SdmInfoRegForm({
const normalFields = formFields.filter( const normalFields = formFields.filter(
(f) => f.formIndex === 0 && f.component (f) => f.formIndex === 0 && f.component
); );
const groupFields = formFields.filter( // const groupFields = formFields.filter(
(f) => f.formIndex === 1 && f.component // (f) => f.formIndex === 1 && f.component
); // );
const candidateFields = formFields.filter( const candidateFields = formFields.filter(
(f) => f.formIndex === 2 && f.component (f) => f.formIndex === 2 && f.component
); );
@@ -512,17 +365,15 @@ export function SdmInfoRegForm({
name={name} name={name}
render={({ field }) => render={({ field }) =>
name === "money" ? ( name === "money" ? (
component(field) // component(field)
<FormUi label={label + "*"}>{component(field)}</FormUi>
) : ( ) : (
// <FormUi label={label}>
// {component(field)}
// </FormUi>
<AccessibleFormField <AccessibleFormField
field={field} field={field}
label={label} label={label}
required={required} required={required}
error={ error={
typeof form.formState.errors[name]?.message === 'string' typeof form.formState.errors[name]?.message === "string"
? (form.formState.errors[name] as FieldError) ? (form.formState.errors[name] as FieldError)
: undefined : undefined
} }
@@ -534,37 +385,527 @@ export function SdmInfoRegForm({
) : null ) : null
)} )}
{groupFields.length > 0 && ( <div className="mt-10">
<div className="mt-10"> <FormLabel className="text-xl">요구수질</FormLabel>
<FormLabel className="text-xl">요구수질</FormLabel> <div className="grid grid-cols-2 gap-x-10 gap-y-6 mt-3 mb-3">
<div className="flex flex-wrap justify-start gap-10 mt-3 mb-3"> <FormField
{groupFields.map(({ name, label, component, required }) => ( control={form.control}
<div key={name} className="min-w-[180px]"> name={`grssClfg`}
<FormField render={({ field }) => {
control={form.control} const disabledFields = usePurpose ? disableRules[usePurpose] : [];
name={name} const isDisabled = disabledFields
render={({ field }) => ( ? disabledFields.includes(field.name)
// <FormUi label={label}> : true;
// {component?.(field)} return (
// </FormUi> <FormUi
<AccessibleFormField label={
field={field} !isDisabled ? `총대장균군수(개/mg)*` : "총대장균군수(개/mg)"
label={label} }
required={required} >
error={ <div className="flex items-center gap-2">
typeof form.formState.errors[name]?.message === 'string' <div className="relative flex-1 min-w-0">
? (form.formState.errors[name] as FieldError) <Input
: undefined type="text"
} className="w-full pr-16 border py-2 text-right rounded-md"
inputComponent={component?.(field)} placeholder=""
/> onChange={(e) => {
)} const inputValue = e.target.value.replace(
/> /[^0-9]/g,
</div> ""
))} ); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: "불검출"
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
{/* TODO: 총대장균군수 확인 필요*/}
{field.value !== null ? (
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
) : (
<></>
)}
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`tn`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled ? `총질소(T-N)(mg/L)*` : "총질소(T-N)(mg/L)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`combResdch`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled ? `결합잔류염소(mg/L)*` : "결합잔류염소(mg/L)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const cleanValue = sanitizeInput(
e.target.value,
true
);
field.onChange(
cleanValue === "" ? null : Number(cleanValue)
);
}}
value={field.value ?? ""}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이상
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`tph`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={!isDisabled ? `총인(T-P)(mg/L)*` : "총인(T-P)(mg/L)"}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const cleanValue = sanitizeInput(
e.target.value,
true
);
field.onChange(
cleanValue === "" ? null : Number(cleanValue)
);
}}
value={field.value ?? ""}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`trbt`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi label={!isDisabled ? `탁도(NTU)*` : "탁도(NTU)"}>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<div className="col-span-1">
<Label className="text-xl">수소이온농도(PH)</Label>
<div className="flex flex-row items-center gap-2">
<Input
className="!text-xl w-36"
placeholder=""
type="text"
onChange={(e) => {
const cleanValue = sanitizeInput(e.target.value, true);
isSetPh(cleanValue, 1);
}}
value={frontPHdata ?? ""}
maxLength={5}
/>
<span className="text-xl mx-2">~</span>
<Input
className="!text-xl w-36 "
placeholder=""
type="text"
onChange={(e) => {
const cleanValue = sanitizeInput(e.target.value, true);
isSetPh(cleanValue, 0);
}}
value={backPHdata ?? ""}
maxLength={5}
/>
</div>
</div> </div>
<FormField
control={form.control}
name={`bod`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled
? `생물학적 산소요구량(BOD)(mg/L)*`
: "생물학적 산소요구량(BOD)(mg/L)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`clIonQnt`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={!isDisabled ? `염화물(mgCL/L)*` : "염화물(mgCL/L)"}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`smll`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi label={!isDisabled ? `냄새*` : "냄새"}>
<SelectBox
className="!text-2xl rounded-md"
label={""}
selectClass={undefined}
selectArray={[
{ text: "불쾌하지않을것", val: "불쾌하지않을것" },
{ text: "해당사항없음", val: "해당사항없음" },
]}
onSelectValue={(val: string | number) => {
field.onChange(val.toString());
}}
value={field.value || "불쾌하지않을것"}
readOnly={isDisabled}
onBlur={field.onBlur}
ref={field.ref}
/>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`cdtnv`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled ? `전기전도도(㎲/cm)*` : "전기전도도(㎲/cm)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`crmty`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi label={!isDisabled ? `색도(도)*` : "색도(도)"}>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`rmrk`}
render={({ field }) => {
return (
<FormUi label={"비고"}>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
field.onChange(e.target.value);
}}
value={
field.value !== undefined && field.value !== ""
? `${field.value}`
: ""
}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
</div>
</div>
</FormUi>
);
}}
/>
</div> </div>
)} </div>
{candidateFields.map(({ name, label, component, required }, index) => {candidateFields.map(({ name, label, component, required }, index) =>
component ? ( component ? (
@@ -581,7 +922,7 @@ export function SdmInfoRegForm({
label={label} label={label}
required={required} required={required}
error={ error={
typeof form.formState.errors[name]?.message === 'string' typeof form.formState.errors[name]?.message === "string"
? (form.formState.errors[name] as FieldError) ? (form.formState.errors[name] as FieldError)
: undefined : undefined
} }
@@ -1,6 +1,6 @@
import { useDmStore } from "@/app/store"; import { useDmStore } from "@/app/store";
import type { DemandCompType } from "@/app/store/sdmMng";
import { BZNM_TOT_NO, FormField, FormUi } from "@common/business_components"; import { BZNM_TOT_NO, FormField, FormUi } from "@common/business_components";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import { Button, Input } from "@common/components"; import { Button, Input } from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { sha512 } from 'js-sha512'; import { sha512 } from 'js-sha512';