수요처 등록 공통 컴포넌트 만드는 중
This commit is contained in:
@@ -19,4 +19,4 @@ export * from "./tab";
|
||||
export * from "./table";
|
||||
export * from "./table_div";
|
||||
export * from "./title";
|
||||
export * from "./sdmSearchForm";
|
||||
export * from "./sdmInsertForm/sdmNameBox";
|
||||
@@ -0,0 +1,246 @@
|
||||
import { FormField } from "../form";
|
||||
import { SelectBox } from "../select";
|
||||
import { Input } from "@common/components";
|
||||
import { FormUi } from "../form_ui";
|
||||
import { useCallback, useState } from "react";
|
||||
import type { ControllerRenderProps, UseFormReturn } from "react-hook-form";
|
||||
import { SdmSearchForm } from "./sdmNameBox";
|
||||
import type { DemandCompType } from "../../../../../../apps/dm/app/store/sdmMng";
|
||||
|
||||
type SelectDataType = {
|
||||
text: string;
|
||||
val: string;
|
||||
};
|
||||
|
||||
interface FormSchema {
|
||||
ctpvCd: string;
|
||||
sggCd: string;
|
||||
dmdCoNm: string;
|
||||
dmdInfoPk: string;
|
||||
daddr: string;
|
||||
telno: string;
|
||||
bznmTotNo?: string;
|
||||
rgtr?: string;
|
||||
}
|
||||
|
||||
interface SdmRegFormProps {
|
||||
form: UseFormReturn<FormSchema>;
|
||||
onCheckboxChange?: (checked: boolean) => void;
|
||||
selectSidoData: SelectDataType[];
|
||||
selectSigunData: SelectDataType[];
|
||||
dataList: DemandCompType[];
|
||||
rtnValue: (searchText: string, pageCnt: number) => void;
|
||||
setIsDuplicate: (value: boolean) => void;
|
||||
}
|
||||
|
||||
type FieldComponent = (field: ControllerRenderProps<FormSchema, keyof FormSchema>) => JSX.Element;
|
||||
|
||||
export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigunData, dataList, rtnValue, setIsDuplicate }: 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 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
|
||||
) => {
|
||||
switch (type) {
|
||||
case "ctpvCd":
|
||||
initializeSigunData(val);
|
||||
setSidoValue(val);
|
||||
field?.onChange(val);
|
||||
form.setValue('dmdInfoPk', '');
|
||||
form.setValue('bznmTotNo', '');
|
||||
break;
|
||||
case "sggCd":
|
||||
setSigunValue(val);
|
||||
field?.onChange(val);
|
||||
form.setValue('dmdInfoPk', '');
|
||||
form.setValue('bznmTotNo', '');
|
||||
break;
|
||||
}
|
||||
}, [initializeSigunData, form]);
|
||||
|
||||
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('dmdInfoPk', rtnCompData?.dmdInfoPk.substring(0,10) || '');
|
||||
form.setValue('bznmTotNo', rtnCompData?.dmdInfoPk.substring(10,14)==='0000' ? '' : rtnCompData?.dmdInfoPk.substring(10,14));
|
||||
|
||||
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?.dmdInfoPk.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 formFields: Array<{
|
||||
name: keyof FormSchema;
|
||||
label: string;
|
||||
component: FieldComponent;
|
||||
}> = [
|
||||
{
|
||||
name: "ctpvCd",
|
||||
label: "시도",
|
||||
component: (field) => (
|
||||
<SelectBox
|
||||
selectClass={undefined}
|
||||
label=""
|
||||
selectArray={selectSidoData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), 'ctpvCd', field)}
|
||||
className=""
|
||||
value={sidoValue}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "sggCd",
|
||||
label: "시군구",
|
||||
component: (field) => (
|
||||
<SelectBox
|
||||
selectClass={undefined}
|
||||
label=""
|
||||
selectArray={filteredSigunData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), 'sggCd', field)}
|
||||
className=""
|
||||
value={sigunValue}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "dmdCoNm",
|
||||
label: "회사명",
|
||||
component: (field) => (
|
||||
<SdmSearchForm
|
||||
rtnData={setRtnData}
|
||||
label="수요처명"
|
||||
initValue={''}
|
||||
dataList={dataList}
|
||||
rtnValue={setRtnValue}
|
||||
rtnText={setRtnText}
|
||||
{...field}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "dmdInfoPk",
|
||||
label: "사업자등록번호",
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="사업자등록번호를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
{...field}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "bznmTotNo",
|
||||
label: "종사업자등록번호",
|
||||
component: (field) => (
|
||||
<FormUi
|
||||
label="종사업자등록번호"
|
||||
onCheckboxChange={handleCheckboxChange}
|
||||
isDisabled={isBznmTotNoDisabled}
|
||||
>
|
||||
<Input
|
||||
placeholder="여러사업장이 있을 경우 입력해주세요"
|
||||
type="text"
|
||||
className="!mt-5 !text-xl text-gray-600"
|
||||
disabled={isBznmTotNoDisabled}
|
||||
{...field}
|
||||
/>
|
||||
</FormUi>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "daddr",
|
||||
label: "사업자주소",
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="주소를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
{...field}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "telno",
|
||||
label: "전화번호",
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="전화번호를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
{...field}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{formFields.map(({ name, label, component }) => (
|
||||
<FormField
|
||||
key={String(name)}
|
||||
control={form.control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
name === 'bznmTotNo' ? component(field) : (
|
||||
<FormUi label={label}>
|
||||
{component(field)}
|
||||
</FormUi>
|
||||
)
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
"use client";
|
||||
|
||||
/* import { useDmStore } from "@/app/store"; */
|
||||
import { SdmRegForm } from "./form";
|
||||
import type { DemandCompType } from "../../../../../../apps/dm/app/store/sdmMng";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
/* import { useRouter } from "next/navigation"; */
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FormProvider, useForm, type SubmitHandler } from "react-hook-form";
|
||||
import { toast, Toaster } from "sonner";
|
||||
/* import { Toaster, toast } from "react-hot-toast"; */
|
||||
import * as z from "zod";
|
||||
import type { SelectDataType } from "../search_input_select";
|
||||
|
||||
type thisProps = {
|
||||
selectSidoData: SelectDataType[];
|
||||
selectSigunData: SelectDataType[];
|
||||
dataList: DemandCompType[];
|
||||
rtnValue: (searchText: string, pageCnt: number) => void;
|
||||
rtnInputData: (data: DemandCompType) => void;
|
||||
}
|
||||
|
||||
const createFormSchema = (isBznmTotNoDisabled: boolean, isDuplicate: boolean) => z.object({
|
||||
ctpvCd: z.string().min(1, "시/도를 선택해주세요"),
|
||||
sggCd: z.string().min(1, "시/군/구를 선택해주세요"),
|
||||
dmdCoNm: z.string()
|
||||
.min(1, "회사명을 입력해주세요")
|
||||
.max(20, "회사명은 20자 이내로 입력해주세요"),
|
||||
dmdInfoPk: z.string()
|
||||
.min(1, "사업자등록번호를 입력해주세요")
|
||||
.regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다")
|
||||
.refine(() => !isDuplicate, "이미 등록된 사업자등록번호입니다"),
|
||||
bznmTotNo: isBznmTotNoDisabled
|
||||
? z.string().optional()
|
||||
: z.string()
|
||||
.length(4, "종사업자등록번호는 4자리 숫자만 입력 가능합니다"),
|
||||
daddr: z.string()
|
||||
.min(1, "주소를 입력해주세요")
|
||||
.min(6, "주소는 6자 이상 입력해주세요")
|
||||
.max(40, "주소는 40자 이내로 입력해주세요"),
|
||||
telno: z.string()
|
||||
.min(8, "전화번호는 8자 이상 입력해주세요")
|
||||
.max(12, "전화번호는 12자 이내로 입력해주세요")
|
||||
.regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"),
|
||||
rgtr: z.string().optional(),
|
||||
});
|
||||
|
||||
type FormSchema = ReturnType<typeof createFormSchema>;
|
||||
export type FormData = z.infer<FormSchema>;
|
||||
|
||||
const defaultValues: FormData = {
|
||||
ctpvCd: '',
|
||||
sggCd: '',
|
||||
dmdCoNm: '',
|
||||
dmdInfoPk: '',
|
||||
bznmTotNo: '',
|
||||
daddr: '',
|
||||
telno: '',
|
||||
rgtr: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
};
|
||||
|
||||
export default function SdmInsertForm({selectSidoData, selectSigunData, dataList, rtnValue, rtnInputData}: thisProps) {
|
||||
/* const { insertSdmData } = useDmStore((state) => state);
|
||||
const router = useRouter(); */
|
||||
|
||||
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(true);
|
||||
const [isDuplicate, setIsDuplicate] = useState(false);
|
||||
const formSchema = createFormSchema(isBznmTotNoDisabled, isDuplicate);
|
||||
|
||||
const form = useForm<z.infer<FormSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues,
|
||||
mode: "onChange",
|
||||
criteriaMode: "all",
|
||||
shouldFocusError: false,
|
||||
shouldUnregister: false
|
||||
});
|
||||
|
||||
// 체크박스 상태가 변경될 때마다 bznmTotNo 필드의 에러를 초기화
|
||||
useEffect(() => {
|
||||
if (isBznmTotNoDisabled) {
|
||||
form.clearErrors('bznmTotNo');
|
||||
form.setValue('bznmTotNo', '');
|
||||
}
|
||||
}, [isBznmTotNoDisabled, form]);
|
||||
|
||||
const setRtnValue = useCallback((searchText: string, pageCnt: number) => {
|
||||
rtnValue(searchText, pageCnt);
|
||||
}, [rtnValue]);
|
||||
|
||||
const onSubmit: SubmitHandler<z.infer<FormSchema>> = async (data) => {
|
||||
try {
|
||||
// form 에러 체크
|
||||
const formErrors = form.formState.errors;
|
||||
if (Object.keys(formErrors).length > 0) {
|
||||
const errorMessage = Object.values(formErrors)[0]?.message || '입력값을 확인해주세요.';
|
||||
toast.error(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const dmdInfoPk = data.dmdInfoPk + (data.bznmTotNo || '0000');
|
||||
const formData = {
|
||||
...data,
|
||||
dmdInfoPk,
|
||||
};
|
||||
|
||||
console.log(formData);
|
||||
/* await insertSdmData(formData); */
|
||||
rtnInputData(formData as DemandCompType);
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
dmdCoNm: data.dmdCoNm,
|
||||
ctpvCd: data.ctpvCd,
|
||||
sggCd: data.sggCd,
|
||||
dmdInfoPk,
|
||||
bznmTotNo: data.bznmTotNo || '',
|
||||
daddr: data.daddr,
|
||||
telno: data.telno,
|
||||
});
|
||||
|
||||
//router.push(`/pages/sdmMng/sdmInfoInsert?${queryParams.toString()}`);
|
||||
toast.success('수요처가 성공적으로 등록되었습니다.');
|
||||
} catch (error) {
|
||||
console.error('Error submitting form:', error);
|
||||
toast.error('수요처 등록에 실패했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-gray-900 underline">
|
||||
수요처등록
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto bg-white rounded-lg shadow-md p-6">
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<SdmRegForm
|
||||
form={form}
|
||||
onCheckboxChange={setIsBznmTotNoDisabled}
|
||||
selectSidoData={selectSidoData}
|
||||
selectSigunData={selectSigunData}
|
||||
dataList={dataList}
|
||||
rtnValue={setRtnValue}
|
||||
setIsDuplicate={setIsDuplicate}
|
||||
/>
|
||||
<div className="flex justify-center pt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-32 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200"
|
||||
>
|
||||
수요처등록
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
<Toaster position="top-center" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
+28
-18
@@ -1,18 +1,24 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { DemandCompType } from "../../../../../../apps/dm/app/store/sdmMng";
|
||||
|
||||
export type thisProps = {
|
||||
label: string | undefined;
|
||||
initValue: string | undefined;
|
||||
setRtnData : (rtnData: any[]) => void;
|
||||
dataList : DemandCompType[];
|
||||
rtnValue : (searchText: string, pageCnt: number) => void;
|
||||
rtnData : (item: DemandCompType) => void;
|
||||
rtnText : (text: string) => void;
|
||||
}
|
||||
|
||||
export const SdmSearchForm = ({label=undefined, initValue=undefined, setRtnData=()=>{}} :thisProps) => {
|
||||
|
||||
/* const { getDemandCompList, Sdm } = useDmStore((state) => state);
|
||||
|
||||
const {
|
||||
demandCompList
|
||||
} = Sdm; */
|
||||
/**
|
||||
* 수요처 검색 컴포넌트
|
||||
* @param label 라벨
|
||||
* @param initValue 초기값
|
||||
* @param rtnValue 검색 결과 반환 함수
|
||||
* @param dataList 검색할 때 넘기는 데이터 리스트
|
||||
* @param rtnData 검색 결과 반환 함수
|
||||
*/
|
||||
export const SdmSearchForm = ({label=undefined, initValue=undefined, rtnValue=()=>{}, dataList=[], rtnData=()=>{}, rtnText=()=>{} } :thisProps) => {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [textValue, setTextValue] = useState(initValue || '');
|
||||
@@ -20,16 +26,20 @@ export const SdmSearchForm = ({label=undefined, initValue=undefined, setRtnData=
|
||||
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setTextValue(value);
|
||||
rtnValue(value, 10);
|
||||
rtnText(value);
|
||||
if (value) {
|
||||
setIsOpen(true);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}, []);
|
||||
}, [rtnValue, rtnText]);
|
||||
|
||||
/* const handleSuggestionClick = useCallback((dmdCoNm: string) => {
|
||||
setTextValue(dmdCoNm);
|
||||
}, []); */
|
||||
const handleSuggestionClick = useCallback((item: DemandCompType) => {
|
||||
setTextValue(item.dmdCoNm);
|
||||
rtnData(item);
|
||||
setIsOpen(false);
|
||||
}, [rtnData]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@@ -43,7 +53,7 @@ export const SdmSearchForm = ({label=undefined, initValue=undefined, setRtnData=
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef}>
|
||||
<input
|
||||
@@ -57,26 +67,26 @@ export const SdmSearchForm = ({label=undefined, initValue=undefined, setRtnData=
|
||||
{isOpen && (
|
||||
<div className=" z-1 w-full mt-2 bg-white border border-gray-300 rounded-md shadow-lg h-90 overflow-auto">
|
||||
<div className="max-h-72">
|
||||
{/* {demandCompList && demandCompList.length > 0 ? demandCompList.map((item : DemandCompType, index : number) => (
|
||||
{dataList && dataList.length > 0 ? dataList.map((item : DemandCompType, index : number) => (
|
||||
<div
|
||||
key={index}
|
||||
className="!text-xl w-full text-left px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto"
|
||||
onClick={() => handleSuggestionClick(item.dmdCoNm)}
|
||||
onClick={() => handleSuggestionClick(item)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
handleSuggestionClick(item.dmdCoNm);
|
||||
handleSuggestionClick(item);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.dmdInfoPk.substring(0,10)} <span className="float-right">{item.dmdCoNm}</span>
|
||||
</div>
|
||||
)) : ( */}
|
||||
)) : (
|
||||
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
|
||||
검색 결과가 없습니다.
|
||||
</div>
|
||||
{/* )) : ( */}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
Reference in New Issue
Block a user