diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx index ee86a9df5..9c2f6201e 100644 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx +++ b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx @@ -123,33 +123,33 @@ export default function SdmApplyList({}: SdmApplyProps) { 총대장균군수(개/100ml) - {} + { sdmApplyDetail?.grssClfg!=-1?:''} {sdmApplyDetail?.grssClfg?sdmApplyDetail.grssClfg!=-1? '이하':'불검출':'-'} 결합잔류염소(mg/L)​ - {} + {} {sdmApplyDetail?.combResdch?'이하':'-'} 탁도(NTU) - {} + {} {sdmApplyDetail?.trbt?'이하':'-'} 생물학적 산소요구량(BOD)(mg/L) - {} + {} {sdmApplyDetail?.blgcOxygDmndQnt?'이하':'-'} 냄새 - {} + {} {sdmApplyDetail?.smll?'이하':'-'} 색도(도) - {} + {} {sdmApplyDetail?.crmty?'이하':'-'} 총질소(T-N)(mg/L) - {} + {} {sdmApplyDetail?.tn?'이하':'-'} 총인(T-P)(mg/L)​ - {} + {} {sdmApplyDetail?.tph?'이하':'-'} 수소이온농도(pH)​ {} 염화물(mgCL/L)​ - {} + {} {sdmApplyDetail?.clIonQnt?'이하':'-'} 전기전도도(㎲/cm)​ - {} + {} {sdmApplyDetail?.cdtnv?'이하':'-'} 비고 {sdmApplyDetail?.rmrk} 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..8e5194bcd 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"; @@ -9,49 +9,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; 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 toast, { Toaster } from "react-hot-toast"; 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 @@ -291,6 +252,7 @@ function SdmInfoInsertContent() { open={!isLogin} onOpenChange={(open) => { if (!open) { + toast.dismiss(); router.push("/"); } }} diff --git a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx index 04dfa58df..da3324ffc 100644 --- a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx +++ b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx @@ -131,7 +131,7 @@ export default function WrAboutPage() { 물재이용 필요성
-

물 재이용, 왜 필요한가요?

+

물 재이용, 왜 필요한가요?

빗물, 중수, 하수처리수는 지속적으로 이용 가능한 순환자원입니다.
물 재이용을 통한 효율적인 수자원 활용은 물 스트레스국가인 우리나라에서
가뭄대응능력 향상 및 하천수질보전을 위해 반드시 필요한 일입니다. @@ -237,7 +237,7 @@ export default function WrAboutPage() { 하수 재이용

-

아직 할 일이 많은 하수처리수,
풍족하게 재이용할 수 있어요!

+

아직 할 일이 많은 하수처리수,
풍족하게 재이용할 수 있어요!

사용하고 버려져 공공하수처리장으로 모여든 물은
용도에 맞는 처리 후 재이용 할 수 있습니다.

) { return ( + + 물재이용 대민 + {children} diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index d68a34e2f..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, @@ -298,7 +401,6 @@ const useTypesData : SelectDataType[] = [ { text: "농업_간접용수", val: "농업_간접용수" }, { text: "농업_직접용수", val: "농업_직접용수" }, { text: "하천", val: "하천" }, - { text: "지하수", val: "지하수" }, { text: "공업", val: "공업" }, ]; diff --git a/apps/dm/components/ui/sdmInfoInsertForm/component.tsx b/apps/dm/components/ui/sdmInfoInsertForm/component.tsx index 478c61ac3..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({ @@ -159,11 +159,9 @@ export function SdmInfoRegForm({ // 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.setValue(field as keyof SdmInfoFormData, value, { shouldDirty: true }); }); } }, [form.getValues("usePurpose")]); @@ -178,7 +176,7 @@ export function SdmInfoRegForm({ ); const formFields: Array<{ - name: keyof FormData; + name: keyof SdmInfoFormData; label: string; component: FieldComponent | undefined; formIndex: number; @@ -348,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 ); @@ -365,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 dd764844b..7617b4b31 100644 --- a/apps/dm/components/ui/sdmLoginForm/component.tsx +++ b/apps/dm/components/ui/sdmLoginForm/component.tsx @@ -4,8 +4,9 @@ import type { DemandCompType } from "@common/business_components/ui/sdmInsertFor import { Button, Input } from "@common/components"; import { zodResolver } from "@hookform/resolvers/zod"; import { sha512 } from 'js-sha512'; -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { FormProvider, useForm, type ControllerRenderProps } from "react-hook-form"; +import toast from "react-hot-toast"; import * as z from "zod"; export function SdmLoginForm() { @@ -15,10 +16,10 @@ export function SdmLoginForm() { const formatBusinessNumber = (value: string) => { // 숫자만 추출 const digitsOnly = value.replace(/\D/g, ""); - + // 10자리까지만 처리 const limitedDigits = digitsOnly.slice(0, 10); - + // 3-2-5 형식으로 포맷팅 if (limitedDigits.length <= 3) { return limitedDigits; @@ -28,33 +29,33 @@ export function SdmLoginForm() { return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`; } }; - + // 스키마를 useMemo로 최적화 const sdmLoginFormSchema = useMemo(() => z.object({ dmdInfoId: z.string() .min(1, "사업자등록번호를 입력해주세요") .regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다"), - bznmTotNo: isBznmTotNoDisabled + bznmTotNo: isBznmTotNoDisabled ? z.string().optional() : z.string().length(4, "종사업자등록번호는 4자리 숫자만 입력 가능합니다"), password: z.string() .min(1, "비밀번호를 입력해주세요") - .min(8, "비밀번호는 8자 이상 입력해주세요") + .min(8, "비밀번호는 8자 이상 입력해주세요") .max(16, "비밀번호는 16자 이내로 입력해주세요"), rgtr: z.string().optional(), 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; useEffect(() => { initializeCommonData(); }, [initializeCommonData]); - const defaultValues: FormData = { + const defaultValues: SdmLoginForm = { dmdInfoId: '', bznmTotNo: '', password: '', @@ -62,7 +63,7 @@ export function SdmLoginForm() { dmdAplyPswd: '', }; - const form = useForm({ + const form = useForm({ resolver: zodResolver(formSchema), defaultValues, mode: "onChange", @@ -72,6 +73,26 @@ export function SdmLoginForm() { setIsBznmTotNoDisabled(checked); }; + const initPassword = () => { + toast( + (t) => ( +
+
+ 관리자(031-0000-0000)에게 비밀번호 초기화 요청을 유선으로 하시기 바랍니다. +
+ +
+ ), + { + id: "pwd-reset", + duration: Infinity, + position: "top-center", + } + ); + } + const handleLogin = () => { const data = form.getValues(); const passwordStr = sha512.hex(data.password) @@ -82,61 +103,61 @@ export function SdmLoginForm() { } actionLogin(param as unknown as DemandCompType); }; - + const formFields: Array<{ - name: keyof FormData; + name: keyof SdmLoginForm; label: string; component: FieldComponent | undefined; }> = [ - { - name: "dmdInfoId", - label: "사업자등록번호", - component: (field) => ( - { - const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10); - field.onChange(dmdId); - }} - /> - ) - }, - { - name: "bznmTotNo", - label: BZNM_TOT_NO || "종사업자등록번호", - component: (field) => ( - - ( + { + const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10); + field.onChange(dmdId); + }} + /> + ) + }, + { + name: "bznmTotNo", + label: BZNM_TOT_NO || "종사업자등록번호", + component: (field) => ( + + + + ) + }, + { + name: "password", + label: "비밀번호", + component: (field) => ( + - - ) - }, - { - name: "password", - label: "비밀번호", - component: (field) => ( - - ) - } - ]; + ) + } + ]; return ( <> @@ -148,11 +169,11 @@ export function SdmLoginForm() { control={form.control} name={name} render={({ field }) => ( - label==='종사업자등록번호' ? - <> + label === '종사업자등록번호' ? + <> {component && component(field)} - : + : {component && component(field)} )} @@ -160,12 +181,15 @@ export function SdmLoginForm() { ))}
- +
+ +
비밀번호 초기화 요청
+
diff --git a/apps/dm/public/img/wrAbout/img_02_1.png b/apps/dm/public/img/wrAbout/img_02_1.png index 3d61c6e5d..aa1c8ee5a 100644 Binary files a/apps/dm/public/img/wrAbout/img_02_1.png and b/apps/dm/public/img/wrAbout/img_02_1.png differ