diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx index 4efc58cb5..56ec3bc00 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useDmStore } from "@/app/store"; -import type { DemandCompBizType } from "@/app/store/sdmMng"; +import { sdmInfoFormSchema, type DemandCompBizType, type SdmInfoFormData } from "@/app/store/sdmMng"; import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component"; import { UiModal } from "@common/business_components/ui/dialog_ui"; import { Button } from "@common/components/ui"; @@ -10,48 +10,9 @@ import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; import { FormProvider, useForm, type SubmitHandler } from "react-hook-form"; import { Toaster } from "react-hot-toast"; -import * as z from "zod"; import SdmLogin from "../sdmLogin/page"; -const sdmInfoFormSchema = z.object({ - sdmNm: z.string().min(1, "수요처를 선택해주세요"), - demandYmd: z.date().optional(), - demandQnty: z - .string() - .min(1, "요구수량을 입력해주세요") - .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), - usePurpose: z.string().optional(), - money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), - /**요구수질 */ - reqWaterQlty: z.string().optional(), - grssClfg: z.number().nullish(), - tn: z.number().nullish(), - combResdch: z.number().nullish(), - tph: z.number().nullish(), - trbt: z.number().nullish(), - ph: z.string().nullish(), - bod: z.number().nullish(), - /**컬럼확인필요 */ - clIonQnt: z.number().nullish(), - smll: z.string(), - cdtnv: z.number().nullish(), - crmty: z.number().nullish(), - rmrk: z.string().optional(), - /* telno: z.string() - .min(8, "전화번호는 8자 이상 입력해주세요") - .max(12, "전화번호는 12자 이내로 입력해주세요") - .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ - candidateTitle: z - .array( - z.object({ - ruseUtztnFcltNm: z.string(), - ruseUtztnFcltCd: z.string(), - }) - ) - .optional(), -}); -export type FormData = z.infer; function SdmInfoInsertContent() { const searchParams = useSearchParams(); @@ -62,7 +23,7 @@ function SdmInfoInsertContent() { const [isLogin, setIsLogin] = useState(false); const [facilityName, setFacilityName] = useState(""); - const [defaultValues, setDefaultValues] = useState({ + const [defaultValues, setDefaultValues] = useState({ sdmNm: "", demandYmd: new Date(), demandQnty: "", @@ -109,10 +70,10 @@ function SdmInfoInsertContent() { setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만) }, [setSubsInfo]); - const form = useForm({ + const form = useForm({ resolver: zodResolver(formSchema), defaultValues, - mode: "onChange", + mode: "onSubmit", }); const subsInfo = useDmStore((state) => state.map.subsInfo); @@ -167,7 +128,7 @@ function SdmInfoInsertContent() { } }, [ismoneyDisabled, form]); const router = useRouter(); - const onSubmit: SubmitHandler = async (data) => { + const onSubmit: SubmitHandler = async (data) => { try { const dmSubsInfo = data.candidateTitle?.filter( (item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 6a6628339..1b63c399d 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -3,10 +3,105 @@ import type { SelectDataType } from "@common/business_components/ui"; import type { TableUpperProps } from "../common"; import toast from "react-hot-toast"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +import { z } from "zod"; /**==================================== * Store - 타입 =====================================*/ + +export const sdmInfoFormSchema = z.object({ + sdmNm: z.string().min(1, "수요처를 선택해주세요"), + demandYmd: z.date().optional(), + demandQnty: z + .string() + .min(1, "요구수량을 입력해주세요") + .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), + usePurpose: z.string().optional(), + money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), + /**요구수질 */ + reqWaterQlty: z.string().optional(), + grssClfg: z.number().nullish(), + tn: z.number().nullish(), + combResdch: z.number().nullish(), + tph: z.number().nullish(), + trbt: z.number().nullish(), + ph: z.string().nullish(), + bod: z.number().nullish(), + /**컬럼확인필요 */ + clIonQnt: z.number().nullish(), + smll: z.string(), + cdtnv: z.number().nullish(), + crmty: z.number().nullish(), + rmrk: z.string().optional(), + /* telno: z.string() + .min(8, "전화번호는 8자 이상 입력해주세요") + .max(12, "전화번호는 12자 이내로 입력해주세요") + .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ + candidateTitle: z + .array( + z.object({ + ruseUtztnFcltNm: z.string(), + ruseUtztnFcltCd: z.string(), + }) + ) + .optional(), +}).superRefine((data, ctx) => { + const disabledFields = disableRules[data.usePurpose ?? ""] || []; + // 모든 수질 관련 필드 + // 총대장균군수는 null 값을 불검출로 표시하기때문에 해당 검사에서 제외함. + const allFields: (keyof typeof data)[] = [ + "tn", "combResdch", "tph", + "trbt", "ph", "bod", "clIonQnt", + "smll", "cdtnv", "crmty", + ]; + + const allFieldsName: Record = { + tn: "총질소(T-N)(mg/L)", + combResdch: "결합잔류염소(mg/L)", + tph: "총인(T-P)(mg/L)", + trbt: "탁도(NTU)", + ph: "수소이온농도(pH)", + bod: "생물학적 산소요구량(BOD)(mg/L)", + clIonQnt: "염화물(mgCL/L)", + smll: "냄새", + cdtnv: "전기전도도(㎲/cm)", + crmty: "색도(도)", + }; + allFields.forEach((field) => { + const isDisabled = disabledFields.includes(field); + if (!isDisabled) { + // disabled가 아닌 필드는 반드시 값이 있어야 함 + const value = data[field]; + if (typeof value === "string") { + if (value.trim().length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (typeof value === "number") { + if (value <= 0 || Number.isNaN(value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } else if (value == null) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: [field], + message: `${allFieldsName[field]} 을 입력해주세요.`, + }); + } + } + }); +}); + +export type SdmInfoFormData = z.infer; + + // 수요처 정보 export type DemandCompBizType = { index : number | ''; @@ -167,8 +262,8 @@ 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','tn',"clIonQnt",'cdtnv'], + "살수": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'], "조경": ["combResdch", "crmty",'tph','cdtnv'], "농업_간접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], "농업_직접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'], @@ -184,6 +279,7 @@ const disableRules: DisableRulesType = { bod: 5, crmty: 20, ph: "5.8~8.5", + smll:'불쾌하지않을것', tn:null, tph: null, clIonQnt: null, @@ -196,6 +292,7 @@ const disableRules: DisableRulesType = { trbt: 2, bod: 5, crmty: 20, + smll:'불쾌하지않을것', ph: "5.8~8.5", tn:null, tph: null, @@ -208,6 +305,7 @@ const disableRules: DisableRulesType = { bod: 5, ph: "5.8~8.5", combResdch:null, + smll:'불쾌하지않을것', crmty:null, tn:null, tph: null, @@ -219,6 +317,7 @@ const disableRules: DisableRulesType = { trbt: 2, bod: 5, ph: "5.8~8.5", + smll:'불쾌하지않을것', combResdch:null, crmty:null, tn:null, @@ -231,6 +330,7 @@ const disableRules: DisableRulesType = { trbt: 2, bod: 5, ph: "5.8~8.5", + smll:'불쾌하지않을것', clIonQnt:250, combResdch:null, crmty:null, @@ -254,6 +354,7 @@ const disableRules: DisableRulesType = { grssClfg: null, trbt: 2, bod: 8, + smll:'불쾌하지않을것', ph: "5.8~8.5", cdtnv:700, combResdch:null, @@ -267,6 +368,7 @@ const disableRules: DisableRulesType = { bod: 5, tn: 10, tph: 0.5, + smll:'불쾌하지않을것', ph: "5.8~8.5", combResdch:null, trbt:null, @@ -274,13 +376,14 @@ const disableRules: DisableRulesType = { clIonQnt: null, cdtnv:null }, - 공업:{ + '공업':{ grssClfg: null, bod: null, tn: null, tph: null, ph: "~", combResdch:null, + smll:'불쾌하지않을것', trbt:null, crmty:null, clIonQnt: null, diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 7d6c41f24..c007645bb 100644 --- a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx +++ b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx @@ -1,5 +1,4 @@ import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page"; -import type { FormData } from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page"; import { useDmStore } from "@/app/store"; import { CANDIDATE, @@ -25,15 +24,16 @@ import SearchInputText from "../common/searhDemandInput/component"; import React from "react"; import { sanitizeInput } from "../common/utils/utils"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; +import type { SdmInfoFormData } from "@/app/store/sdmMng"; export interface SdmInfoRegFormProps { - form: UseFormReturn; + form: UseFormReturn; onCheckboxChange?: (checked: boolean) => void; facilityName?: string; } type FieldComponent = ( - field: ControllerRenderProps + field: ControllerRenderProps ) => JSX.Element; export function SdmInfoRegForm({ @@ -161,7 +161,7 @@ export function SdmInfoRegForm({ if (defaults) { Object.entries(defaults).forEach(([field, value]) => { - form.setValue(field as keyof FormData, value, { shouldDirty: true }); + form.setValue(field as keyof SdmInfoFormData, value, { shouldDirty: true }); }); } }, [form.getValues("usePurpose")]); @@ -176,7 +176,7 @@ export function SdmInfoRegForm({ ); const formFields: Array<{ - name: keyof FormData; + name: keyof SdmInfoFormData; label: string; component: FieldComponent | undefined; formIndex: number; @@ -346,9 +346,7 @@ export function SdmInfoRegForm({ const normalFields = formFields.filter( (f) => f.formIndex === 0 && f.component ); - // const groupFields = formFields.filter( - // (f) => f.formIndex === 1 && f.component - // ); + const candidateFields = formFields.filter( (f) => f.formIndex === 2 && f.component ); @@ -363,8 +361,13 @@ export function SdmInfoRegForm({ name={name} render={({ field }) => name === "money" ? ( - // component(field) - {component(field)} +
+ + { component(field)} +
+ // {component(field)} ) : ( { field.onChange(val.toString()); }} - value={field.value || "불쾌하지않을것"} + value={field.value } readOnly={isDisabled} onBlur={field.onBlur} ref={field.ref} diff --git a/apps/dm/components/ui/sdmLoginForm/component.tsx b/apps/dm/components/ui/sdmLoginForm/component.tsx index 75852a4da..9664c52a2 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -46,8 +46,8 @@ export function SdmLoginForm() { dmdAplyPswd: z.string().optional(), }), [isBznmTotNoDisabled]); - type FormData = z.infer; - type FieldComponent = (field: ControllerRenderProps) => JSX.Element; + type SdmLoginForm = z.infer; + type FieldComponent = (field: ControllerRenderProps) => JSX.Element; const formSchema = sdmLoginFormSchema; @@ -55,7 +55,7 @@ export function SdmLoginForm() { initializeCommonData(); }, [initializeCommonData]); - const defaultValues: FormData = { + const defaultValues: SdmLoginForm = { dmdInfoId: '', bznmTotNo: '', password: '', @@ -63,7 +63,7 @@ export function SdmLoginForm() { dmdAplyPswd: '', }; - const form = useForm({ + const form = useForm({ resolver: zodResolver(formSchema), defaultValues, mode: "onChange", @@ -102,7 +102,7 @@ export function SdmLoginForm() { }; const formFields: Array<{ - name: keyof FormData; + name: keyof SdmLoginForm; label: string; component: FieldComponent | undefined; }> = [