수요처 등록시 주소 검색 서치인풋으로 변경

This commit is contained in:
leedj312
2025-08-26 17:46:51 +09:00
parent f2447cb1f5
commit dae440a4b4
2 changed files with 337 additions and 252 deletions
@@ -1,47 +1,48 @@
import { Input } from "@common/components";
import { AsYouType } from "libphonenumber-js";
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from "react";
import type { ControllerRenderProps, UseFormReturn } from "react-hook-form";
import { FormField } from "../form";
import { FormUi } from "../form_ui";
import { SelectBox } from "../select";
import { SdmSearchForm } from "./sdmNameBox";
import SearchInputAddress from "../search_input_address";
import type { addressType } from "../search_input_address/findAddress";
// 수요처 정보
export type DemandCompType = {
index : number | '';
dmdInfoId : string | '';
bzmnNo : string | '';
dmdCoNm : string | '';
daddr : string | '';
telno : string | '';
rgtr : string | '';
useUsgCd : string | '';
ctpvCd : string | '';
sggCd : string | '';
dmdAplyPswd : string | '';
wntUntprc : number | '';
dmndTmng : Date | '';
dmndQty : number | '';
grssClfg : number | '';
tn : number | '';
combResdch : number | '';
tph : number | '';
trbt : number | '';
ph : number | '';
blgcOxygDmndQnt : number | '';
clIonQnt : number | '';
smll : number | '';
cdtnv : number | '';
crmty : number | '';
rmrk : string | '';
reqDt : string | '';
}
index: number | "";
dmdInfoId: string | "";
bzmnNo: string | "";
dmdCoNm: string | "";
daddr: string | "";
telno: string | "";
rgtr: string | "";
useUsgCd: string | "";
ctpvCd: string | "";
sggCd: string | "";
dmdAplyPswd: string | "";
wntUntprc: number | "";
dmndTmng: Date | "";
dmndQty: number | "";
grssClfg: number | "";
tn: number | "";
combResdch: number | "";
tph: number | "";
trbt: number | "";
ph: number | "";
blgcOxygDmndQnt: number | "";
clIonQnt: number | "";
smll: number | "";
cdtnv: number | "";
crmty: number | "";
rmrk: string | "";
reqDt: string | "";
};
type SelectDataType = {
text: string;
val: string;
text: string;
val: string;
};
interface FormSchema {
@@ -52,7 +53,7 @@ interface FormSchema {
daddr: string;
telno: string;
bznmTotNo?: string;
rgtr?: string | '';
rgtr?: string | "";
dmdAplyPswd?: string;
dmdAplyPswdConfirm?: string;
}
@@ -65,38 +66,50 @@ interface SdmRegFormProps {
dataList: DemandCompType[];
rtnValue: (searchText: string, pageCnt: number) => void;
setIsDuplicate: (value: boolean) => void;
updateList?:DemandCompType
readOnly?:boolean
updateList?: DemandCompType;
readOnly?: boolean;
}
type FieldComponent = (field: ControllerRenderProps<FormSchema, keyof FormSchema>) => JSX.Element;
type FieldComponent = (
field: ControllerRenderProps<FormSchema, keyof FormSchema>,
) => JSX.Element;
export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigunData, dataList, rtnValue, setIsDuplicate ,updateList,readOnly}: SdmRegFormProps) {
const [sidoValue, setSidoValue] = useState<string>('');
const [sigunValue, setSigunValue] = useState<string>('');
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
export function SdmRegForm({
form,
onCheckboxChange,
selectSidoData,
selectSigunData,
dataList,
rtnValue,
setIsDuplicate,
updateList,
readOnly,
}: SdmRegFormProps) {
const [sidoValue, setSidoValue] = useState<string>("");
const [sigunValue, setSigunValue] = useState<string>("");
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
{ text: "전체", val: "0" },
]);
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(true);
const [orgBznmNo, setOrgBznmNo] = useState<string>('');
const [updateCoNm, setUpdateNm] = useState<string >('')
const [orgBznmNo, setOrgBznmNo] = useState<string>("");
const [updateCoNm, setUpdateNm] = useState<string>("");
const formatPhoneNumber = (value:string) => {
const formatPhoneNumber = (value: string) => {
const input = value;
const formatter = new AsYouType("KR");
const formatted = formatter.input(input);
return formatted
return formatted;
};
// 사업자등록번호 포맷팅 함수 (3-2-5 형식)
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;
@@ -107,136 +120,166 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
}
};
const initializeSigunData = useCallback((sidoCd: string) => {
if (sidoCd === '0') {
setSigunValue('0');
setFilteredSigunData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectSigunData.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd)
];
setSigunValue('0');
setFilteredSigunData(newFilteredData);
}
}, [selectSigunData]);
const onSelectValue = useCallback((
val: string,
type: string,
field: ControllerRenderProps<FormSchema, keyof FormSchema> | undefined
) => {
if(!updateList?.dmdInfoId)
switch (type) {
case "ctpvCd":
initializeSigunData(val);
setSidoValue(val);
field?.onChange(val);
form.setValue('dmdInfoId', '');
form.setValue('bznmTotNo', '');
break;
case "sggCd":
setSigunValue(val);
field?.onChange(val);
form.setValue('dmdInfoId', '');
form.setValue('bznmTotNo', '');
break;
}
}, [initializeSigunData, form,updateList]);
const handleCheckboxChange = useCallback((checked: boolean) => {
setIsBznmTotNoDisabled(checked);
onCheckboxChange?.(checked);
}, [onCheckboxChange]);
const setRtnValue = useCallback((searchText: string, pageCnt: number) => {
rtnValue(searchText, pageCnt);
}, [rtnValue]);
const setRtnData = useCallback((item: DemandCompType) => {
const rtnCompData = item;
form.setValue('dmdCoNm', rtnCompData?.dmdCoNm || '');
form.setValue('dmdInfoId', rtnCompData?.dmdInfoId.substring(0,10) || '');
form.setValue('bznmTotNo', rtnCompData?.dmdInfoId.substring(10,14)==='0000' ? '' : rtnCompData?.dmdInfoId.substring(10,14));
// 수요처 불러온 후 사업자번호를 바꿀경우를 대비함
setOrgBznmNo(rtnCompData?.dmdInfoId);
const sidoCd = rtnCompData?.ctpvCd.trim() || '';
setSidoValue(sidoCd);
form.setValue('ctpvCd', sidoCd);
initializeSigunData(sidoCd);
setTimeout(() => {
const sigunCd = rtnCompData?.sggCd.trim() || '';
setSigunValue(sigunCd);
form.setValue('sggCd', sigunCd);
}, 100);
if(rtnCompData?.dmdInfoId.substring(10,14) === '0000') {
handleCheckboxChange(true);
} else {
handleCheckboxChange(false);
}
form.setValue('daddr', rtnCompData?.daddr || '');
form.setValue('telno', rtnCompData?.telno || '');
// 선택을 했을 때 중복 상태를 true로 설정
setIsDuplicate(true);
}, [form, handleCheckboxChange, initializeSigunData, setIsDuplicate]);
const setRtnText = useCallback((text: string) => {
form.setValue('dmdCoNm', text);
}, [form]);
const bznNoChange = useCallback((e: React.ChangeEvent<HTMLInputElement>, noType: string, field?: ControllerRenderProps<FormSchema, keyof FormSchema>) => {
const value = e.target.value;
if(noType === 'dmdInfoId') {
// 숫자만 추출하여 저장 (포맷팅 제거)
const digitsOnly = value.replace(/\D/g, "");
if(orgBznmNo.substring(0,10) === digitsOnly) {
setIsDuplicate(true);
const initializeSigunData = useCallback(
(sidoCd: string) => {
if (sidoCd === "0") {
setSigunValue("0");
setFilteredSigunData([{ text: "전체", val: "0" }]);
} else {
setIsDuplicate(false);
const newFilteredData = [
{ text: "전체", val: "0" },
...selectSigunData.filter(
(item: SelectDataType) => item.val.substring(0, 2) === sidoCd,
),
];
setSigunValue("0");
setFilteredSigunData(newFilteredData);
}
field?.onChange(digitsOnly);
} else {
if(orgBznmNo.substring(10,14) === value) {
setIsDuplicate(true);
} else {
setIsDuplicate(false);
}
field?.onChange(value);
}
}, [orgBznmNo, setIsDuplicate]);
},
[selectSigunData],
);
useEffect(() => {
form.setValue('rgtr', '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e');
if(updateList?.dmdInfoId){
form.reset(updateList)
if(updateList?.dmdInfoId.slice(10,14) !== '0000') {
handleCheckboxChange( false);
form.setValue('bznmTotNo', updateList?.dmdInfoId.slice(10,14));
} else {
handleCheckboxChange( true);
const onSelectValue = useCallback(
(
val: string,
type: string,
field: ControllerRenderProps<FormSchema, keyof FormSchema> | undefined,
) => {
if (!updateList?.dmdInfoId)
switch (type) {
case "ctpvCd":
initializeSigunData(val);
setSidoValue(val);
field?.onChange(val);
form.setValue("dmdInfoId", "");
form.setValue("bznmTotNo", "");
break;
case "sggCd":
setSigunValue(val);
field?.onChange(val);
form.setValue("dmdInfoId", "");
form.setValue("bznmTotNo", "");
break;
}
form.setValue('dmdInfoId', updateList?.dmdInfoId.slice(0,10));
setUpdateNm(updateList?.dmdCoNm)
// setSigunValue(updateList?.sggCd)
}else{
handleCheckboxChange( true);
}
}, [updateList,form]);
},
[initializeSigunData, form, updateList],
);
const handleCheckboxChange = useCallback(
(checked: boolean) => {
setIsBznmTotNoDisabled(checked);
onCheckboxChange?.(checked);
},
[onCheckboxChange],
);
const setRtnValue = useCallback(
(searchText: string, pageCnt: number) => {
rtnValue(searchText, pageCnt);
},
[rtnValue],
);
const setRtnData = useCallback(
(item: DemandCompType) => {
const rtnCompData = item;
form.setValue("dmdCoNm", rtnCompData?.dmdCoNm || "");
form.setValue("dmdInfoId", rtnCompData?.dmdInfoId.substring(0, 10) || "");
form.setValue(
"bznmTotNo",
rtnCompData?.dmdInfoId.substring(10, 14) === "0000"
? ""
: rtnCompData?.dmdInfoId.substring(10, 14),
);
// 수요처 불러온 후 사업자번호를 바꿀경우를 대비함
setOrgBznmNo(rtnCompData?.dmdInfoId);
const sidoCd = rtnCompData?.ctpvCd.trim() || "";
setSidoValue(sidoCd);
form.setValue("ctpvCd", sidoCd);
initializeSigunData(sidoCd);
setTimeout(() => {
const sigunCd = rtnCompData?.sggCd.trim() || "";
setSigunValue(sigunCd);
form.setValue("sggCd", sigunCd);
}, 100);
if (rtnCompData?.dmdInfoId.substring(10, 14) === "0000") {
handleCheckboxChange(true);
} else {
handleCheckboxChange(false);
}
form.setValue("daddr", rtnCompData?.daddr || "");
form.setValue("telno", rtnCompData?.telno || "");
// 선택을 했을 때 중복 상태를 true로 설정
setIsDuplicate(true);
},
[form, handleCheckboxChange, initializeSigunData, setIsDuplicate],
);
const setRtnText = useCallback(
(text: string) => {
form.setValue("dmdCoNm", text);
},
[form],
);
const bznNoChange = useCallback(
(
e: React.ChangeEvent<HTMLInputElement>,
noType: string,
field?: ControllerRenderProps<FormSchema, keyof FormSchema>,
) => {
const value = e.target.value;
if (noType === "dmdInfoId") {
// 숫자만 추출하여 저장 (포맷팅 제거)
const digitsOnly = value.replace(/\D/g, "");
if (orgBznmNo.substring(0, 10) === digitsOnly) {
setIsDuplicate(true);
} else {
setIsDuplicate(false);
}
field?.onChange(digitsOnly);
} else {
if (orgBznmNo.substring(10, 14) === value) {
setIsDuplicate(true);
} else {
setIsDuplicate(false);
}
field?.onChange(value);
}
},
[orgBznmNo, setIsDuplicate],
);
useEffect(() => {
console.log( 'form.formState.errors ', form.formState.errors)
},[form.formState.errors])
form.setValue("rgtr", "2d4b151d-1828-41e0-91d9-4cd28ea1cc7e");
if (updateList?.dmdInfoId) {
form.reset(updateList);
if (updateList?.dmdInfoId.slice(10, 14) !== "0000") {
handleCheckboxChange(false);
form.setValue("bznmTotNo", updateList?.dmdInfoId.slice(10, 14));
} else {
handleCheckboxChange(true);
}
form.setValue("dmdInfoId", updateList?.dmdInfoId.slice(0, 10));
setUpdateNm(updateList?.dmdCoNm);
// setSigunValue(updateList?.sggCd)
} else {
handleCheckboxChange(true);
}
}, [updateList, form]);
useEffect(() => {
console.log("form.formState.errors ", form.formState.errors);
}, [form.formState.errors]);
const formFields: Array<{
name: keyof FormSchema;
@@ -246,53 +289,64 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
{
name: "ctpvCd",
label: "시도",
component: (field) => (
component: (field) =>
updateList?.dmdInfoId ? (
<Input
placeholder="시도"
type="text"
className="!text-xl text-gray-600"
onChange={(e) =>{field.onChange(e.target.value)} }
value={ selectSidoData.find(item => item.val === field.value?.trim() )?.text??''}
readOnly={!!updateList?.dmdInfoId}
onChange={(e) => {
field.onChange(e.target.value);
}}
value={
selectSidoData.find((item) => item.val === field.value?.trim())
?.text ?? ""
}
readOnly={!!updateList?.dmdInfoId}
/>
) : (
<SelectBox
selectClass={undefined}
label=""
selectArray={selectSidoData}
onSelectValue={(val) => onSelectValue(val.toString(), 'ctpvCd', field??'')}
className=""
value={sidoValue}
/>
)
)
<SelectBox
selectClass={undefined}
label=""
selectArray={selectSidoData}
onSelectValue={(val) =>
onSelectValue(val.toString(), "ctpvCd", field ?? "")
}
className=""
value={sidoValue}
/>
),
},
{
name: "sggCd",
label: "시군구",
component: (field) => (
component: (field) =>
updateList?.dmdInfoId ? (
<Input
placeholder="시도"
type="text"
className="!text-xl text-gray-600"
onChange={(e) =>{field.onChange(e.target.value)} }
value={selectSigunData.find(item => item.val === field.value?.trim() )?.text??''}
readOnly={!!updateList?.dmdInfoId}
onChange={(e) => {
field.onChange(e.target.value);
}}
value={
selectSigunData.find((item) => item.val === field.value?.trim())
?.text ?? ""
}
readOnly={!!updateList?.dmdInfoId}
/>
) : (
<SelectBox
selectClass={"!text-xl text-gray-600"}
label=""
selectArray={filteredSigunData}
onSelectValue={(val) => onSelectValue(val.toString(), 'sggCd', field)}
className=""
value={sigunValue}
/>
)
)
<SelectBox
selectClass={"!text-xl text-gray-600"}
label=""
selectArray={filteredSigunData}
onSelectValue={(val) =>
onSelectValue(val.toString(), "sggCd", field)
}
className=""
value={sigunValue}
/>
),
},
{
name: "dmdCoNm",
@@ -308,117 +362,138 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
readOnly={readOnly}
{...field}
/>
)
),
},
{
name: "dmdInfoId",
label: "사업자등록번호",
component: (field) => (
<Input
<Input
placeholder="사업자등록번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
onChange={(e) => bznNoChange(e, 'dmdInfoId', field)}
value={formatBusinessNumber(field.value || '')}
onChange={(e) => bznNoChange(e, "dmdInfoId", field)}
value={formatBusinessNumber(field.value || "")}
readOnly={!!updateList?.dmdInfoId}
/>
)
),
},
{
name: "bznmTotNo",
label: "",
component: (field) => (
<FormUi
<FormUi
label="종사업자등록번호"
onCheckboxChange={handleCheckboxChange}
isDisabled={isBznmTotNoDisabled}
>
<Input
placeholder="여러사업장이 있을 경우 입력해주세요"
<Input
placeholder="여러사업장이 있을 경우 입력해주세요"
type="text"
className="!mt-5 !text-xl text-gray-600"
disabled={isBznmTotNoDisabled}
onChange={(e) => bznNoChange(e, 'bznmTotNo', field)}
// {...field}
value={ field.value}
onChange={(e) => bznNoChange(e, "bznmTotNo", field)}
// {...field}
value={field.value}
readOnly={!!updateList?.dmdInfoId}
/>
</FormUi>
)
),
},
{
name: "daddr",
label: "사업자주소",
component: (field) => (
<Input
placeholder="주소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
// <Input
// placeholder="주소를 입력하세요"
// type="text"
// className="!text-xl text-gray-600"
// readOnly={readOnly}
// {...field}
// />
<SearchInputAddress
initValue={undefined}
setRtnData={(rtnData: addressType)=> {
field.onChange(rtnData.roadAddrPart1);
console.log("rtnData ", rtnData);
}}
/>
)
),
},
{
name: "telno",
label: "전화번호",
component: (field) =>
{
const formattedValue = formatPhoneNumber(field.value??''); // 초기 렌더링 시에도 포맷 적용
component: (field) => {
const formattedValue = formatPhoneNumber(field.value ?? ""); // 초기 렌더링 시에도 포맷 적용
return (
<Input
placeholder="전화번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={formattedValue}
readOnly={readOnly}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, ''); // 숫자만 저장
field.onChange(value);
}}
/>
)}
<Input
placeholder="전화번호를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={formattedValue}
readOnly={readOnly}
onChange={(e) => {
const value = e.target.value.replace(/\D/g, ""); // 숫자만 저장
field.onChange(value);
}}
/>
);
},
},
{
name: "dmdAplyPswd",
label: "패스워드",
component: (field) => (
<Input
placeholder="패스워드를 입력하세요"
<Input
placeholder="패스워드를 입력하세요"
type="password"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
/>
)
),
},
{
name: "dmdAplyPswdConfirm",
label: "패스워드확인",
component: (field) => (
<Input
placeholder="패스워드를 입력하세요"
<Input
placeholder="패스워드를 입력하세요"
type="password"
className="!text-xl text-gray-600"
readOnly={readOnly}
{...field}
/>
)
}
]
),
},
];
return (
<>
{formFields
.filter(({ name }) => !(updateList?.dmdInfoId && (name === "dmdAplyPswd" || name === "dmdAplyPswdConfirm")))
.filter(
({ name }) =>
!(
updateList?.dmdInfoId &&
(name === "dmdAplyPswd" || name === "dmdAplyPswdConfirm")
),
)
.map(({ name, label, component }) => {
const error = form.formState.errors[name];
const inputId = `${name}-input`;
const errorId = `${name}-error`;
const isRequired = ["ctpvCd", "sggCd", "dmdCoNm", "dmdInfoId", "daddr", "telno", "dmdAplyPswd", "dmdAplyPswdConfirm"].includes(name);
const isRequired = [
"ctpvCd",
"sggCd",
"dmdCoNm",
"dmdInfoId",
"daddr",
"telno",
"dmdAplyPswd",
"dmdAplyPswdConfirm",
].includes(name);
return (
<FormField
@@ -427,20 +502,27 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
name={name}
render={({ field }) => (
<div className="mb-4">
<label htmlFor={inputId} className="block text-gray-800 font-medium text-xl">
<label
htmlFor={inputId}
className="block text-xl font-medium text-gray-800"
>
{label}
{isRequired && <span className="text-red-500 ml-1">*</span>}
{isRequired && <span className="ml-1 text-red-500">*</span>}
</label>
{React.cloneElement(component(field), {
id: inputId,
"aria-required": isRequired,
"aria-invalid": !!error,
"aria-describedby": error ? errorId : undefined
"aria-describedby": error ? errorId : undefined,
})}
{error?.message && (
<p id={errorId} role="alert" className="text-red-500 text-lg mt-1">
<p
id={errorId}
role="alert"
className="mt-1 text-lg text-red-500"
>
{error.message}
</p>
)}
@@ -451,4 +533,4 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
})}
</>
);
}
}
@@ -5,6 +5,7 @@ import { findAddress, type addressType } from "./findAddress";
type thisProps = {
initValue: string | undefined;
setRtnData: (rtnData: addressType) => void;
readonly?: boolean;
};
/**
@@ -15,6 +16,7 @@ type thisProps = {
export default function SearchInputAddress({
initValue = undefined,
setRtnData = () => {},
readonly=false
}: thisProps) {
const [isOpen, setIsOpen] = useState(false);
@@ -73,6 +75,7 @@ export default function SearchInputAddress({
/* onFocus={() => setIsOpen(true)} */
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder={`주소를 입력하세요`}
readOnly={readonly}
/>
{isOpen && (
<div className="absolute left-0 right-0 mt-2 bg-white border border-gray-300 rounded-md shadow-lg max-h-72 overflow-auto z-50">