615 lines
34 KiB
TypeScript
615 lines
34 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, memo, useRef, useState } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
|
|
import {
|
|
Button,
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
DialogClose,
|
|
Input,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableRow,
|
|
RadioGroup,
|
|
RadioGroupItem,
|
|
Label,
|
|
Select,
|
|
SelectTrigger,
|
|
SelectContent,
|
|
SelectValue,
|
|
SelectItem,
|
|
Skeleton,
|
|
} from "@common/components";
|
|
import { Form, FormField, FormItem, FormControl } from "@/shared/ui/form";
|
|
import { ENDPOINT } from "@/shared/config";
|
|
import { CodeSelect } from "@/entities/code";
|
|
|
|
import { type GeneralVerificationMasterSubmitFormData, generalVerificationMasterSubmitSchema } from '@/entities/statistics/model/general-verification-master-submit-schema';
|
|
import type { UpdateVrfcMasterRequest, RegistVrfcMasterRequest } from "@/entities/statistics/model/general-verification-interface";
|
|
import { generalverificationApiService } from '@/entities/statistics/api/general-verification-service';
|
|
import { UserApi } from "@/entities/user/api/user-service";
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
import { useAlertStore } from "@/shared/lib";
|
|
import { getUserInfo } from "../../../../../../../packages/common/components/src/api";
|
|
|
|
interface RegistDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
children?: React.ReactNode;
|
|
vrfcSn?: number;
|
|
/** 수정/등록 성공 시 부모에서 목록 재조회 등 실행 */
|
|
onSuccess?: () => void;
|
|
}
|
|
|
|
const RegistDialog = memo(
|
|
({
|
|
children,
|
|
open,
|
|
onOpenChange,
|
|
vrfcSn,
|
|
onSuccess,
|
|
...props
|
|
}: RegistDialogProps) => {
|
|
|
|
const isVrfcSn = vrfcSn != null;
|
|
const [checkVrfcNm, setCheckVrfcNm] = useState<boolean>(false);
|
|
const { confirm } = useConfirm();
|
|
const alert = useAlertStore((state) => state.setMessage);
|
|
|
|
const { data: detailData } = useQuery({
|
|
queryKey: [`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/masters`, vrfcSn],
|
|
queryFn: () => generalverificationApiService.getGeneralverificationInfo(vrfcSn!),
|
|
enabled: open && vrfcSn != null,
|
|
});
|
|
|
|
const form = useForm<GeneralVerificationMasterSubmitFormData>({
|
|
resolver: zodResolver(generalVerificationMasterSubmitSchema),
|
|
values: {
|
|
vrfcSn: detailData?.data.vrfcSn ?? undefined,
|
|
vrfcNm: detailData?.data.vrfcNm ?? '',
|
|
vrfcClsfCd: detailData?.data.vrfcClsfCd ?? '',
|
|
trgtStatsCd: detailData?.data.trgtStatsCd ?? '',
|
|
trgtStatsNm: detailData?.data.trgtStatsNm ?? '',
|
|
dcptDigtCnt: detailData?.data.dcptDigtCnt ?? 0,
|
|
dcptPrcsMthCd: detailData?.data.dcptPrcsMthCd ?? '',
|
|
useYn: detailData?.data.useYn ?? 'Y',
|
|
rgtrId: detailData?.data.rgtrId ?? '',
|
|
regDt: detailData?.data.regDt ?? '',
|
|
mdfrId: detailData?.data.mdfrId ?? '',
|
|
mdfcnDt: detailData?.data.mdfcnDt ?? '',
|
|
},
|
|
});
|
|
|
|
const dcptDigtCnt = form.watch('dcptDigtCnt');
|
|
const rgtrId = form.watch('rgtrId');
|
|
const mdfrId = form.watch('mdfrId');
|
|
const trgtStatsCd = form.watch('trgtStatsCd');
|
|
const vrfcNm = form.watch('vrfcNm');
|
|
|
|
const { data: registrarUser } = useQuery({
|
|
queryKey: ['user', 'rgtr', rgtrId],
|
|
queryFn: () => UserApi.findByUserUnqId(rgtrId!),
|
|
enabled: open && !!rgtrId,
|
|
});
|
|
|
|
const { data: modifierUser } = useQuery({
|
|
queryKey: ['user', 'mdfr', mdfrId],
|
|
queryFn: () => UserApi.findByUserUnqId(mdfrId!),
|
|
enabled: open && !!mdfrId,
|
|
});
|
|
|
|
const originalVrfcNmRef = useRef<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (detailData?.data.vrfcNm && originalVrfcNmRef.current == null) {
|
|
originalVrfcNmRef.current = detailData.data.vrfcNm;
|
|
}
|
|
}, [detailData]);
|
|
|
|
/** 중복 여부 반환: true = 중복, false = 사용 가능 */
|
|
const runVerificationNameCheck = async (trgtStatsCdVal: string, vrfcNmVal: string, vrfcClsfCd: string): Promise<boolean> => {
|
|
try {
|
|
const response = await generalverificationApiService.checkVerificationName({
|
|
trgtStatsCd: trgtStatsCdVal,
|
|
vrfcNm: vrfcNmVal,
|
|
vrfcClsfCd: vrfcClsfCd,
|
|
});
|
|
if (response.status === 'success') {
|
|
const isDuplicate = response.data;
|
|
setCheckVrfcNm(isDuplicate);
|
|
return isDuplicate;
|
|
}
|
|
alert('중복검사 중 오류가 발생했습니다.');
|
|
setCheckVrfcNm(true);
|
|
return true;
|
|
} catch {
|
|
alert('중복검사 중 오류가 발생했습니다.');
|
|
setCheckVrfcNm(true);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
const handleUpdateVrfcMaster = async (data: GeneralVerificationMasterSubmitFormData) => {
|
|
try {
|
|
const response = await generalverificationApiService.UpdateVrfcMaster(data as UpdateVrfcMasterRequest);
|
|
|
|
if (response.status === 'success') {
|
|
alert('수정되었습니다.');
|
|
onOpenChange(false);
|
|
onSuccess?.();
|
|
} else {
|
|
alert(response.message ?? '수정 중 오류가 발생했습니다.');
|
|
}
|
|
} catch {
|
|
alert('수정 중 오류가 발생했습니다.');
|
|
}
|
|
};
|
|
|
|
const handleRegistVrfcMaster = async (data: GeneralVerificationMasterSubmitFormData) => {
|
|
try {
|
|
const response = await generalverificationApiService.RegistVrfcMaster(data as RegistVrfcMasterRequest);
|
|
|
|
if (response.status === 'success') {
|
|
alert('등록되었습니다.');
|
|
onOpenChange(false);
|
|
onSuccess?.();
|
|
} else {
|
|
alert(response.message ?? '등록 중 오류가 발생했습니다.');
|
|
}
|
|
} catch {
|
|
alert('등록 중 오류가 발생했습니다.');
|
|
}
|
|
};
|
|
|
|
const handleDeleteVrfcMaster = async () => {
|
|
const deleteMsg = `${detailData?.data.vrfcNm} 검증룰을
|
|
삭제하시겠습니까?`;
|
|
|
|
if (await confirm(deleteMsg)) {
|
|
try {
|
|
const response = await generalverificationApiService.DeleteVrfcMaster(vrfcSn!);
|
|
|
|
if (response.status === 'success') {
|
|
alert('삭제되었습니다.');
|
|
onOpenChange(false);
|
|
onSuccess?.();
|
|
} else {
|
|
alert(response.message ?? '삭제 중 오류가 발생했습니다.');
|
|
}
|
|
} catch {
|
|
alert('삭제 중 오류가 발생했습니다.');
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
if (!vrfcNm) return;
|
|
|
|
const original = originalVrfcNmRef.current;
|
|
// 수정 모드: 기준값이 아직 안 세팅됐으면 검사 안 함(다이얼로그 막 열었을 때)
|
|
if (isVrfcSn && original === null) return;
|
|
// 수정 모드: 사용자가 검증명을 바꾸지 않았으면 검사 안 함
|
|
if (original !== null && original === vrfcNm) {
|
|
setCheckVrfcNm(false);
|
|
return;
|
|
}
|
|
|
|
// const timer = setTimeout(() => {
|
|
// runVerificationNameCheck(trgtStatsCd ?? '', vrfcNm, vrfcClsfCd);
|
|
// }, 500);
|
|
|
|
// return () => clearTimeout(timer);
|
|
}, [vrfcNm, open, isVrfcSn]);
|
|
|
|
const onSubmit = async (data: GeneralVerificationMasterSubmitFormData) => {
|
|
const currentVrfcNm = (data.vrfcNm ?? '').trim();
|
|
const originalVrfcNm = originalVrfcNmRef.current;
|
|
|
|
// 수정 모드인데 기존값과 동일하면 중복검사 완전 생략
|
|
const skipNameCheck =
|
|
isVrfcSn &&
|
|
(originalVrfcNm === null || currentVrfcNm === originalVrfcNm);
|
|
|
|
if (!skipNameCheck && currentVrfcNm) {
|
|
// 제출 시점에 중복검사 실행
|
|
const isDuplicate = await runVerificationNameCheck(
|
|
data.trgtStatsCd ?? '',
|
|
currentVrfcNm,
|
|
data.vrfcClsfCd,
|
|
);
|
|
if (isDuplicate) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const confirmMessage = isVrfcSn
|
|
? "수정 하시겠습니까?"
|
|
: "등록 하시겠습니까?";
|
|
if (await confirm(confirmMessage)) {
|
|
const userInfo = await getUserInfo();
|
|
if (isVrfcSn) {
|
|
const updateData = {
|
|
vrfcSn: data.vrfcSn,
|
|
vrfcNm: data.vrfcNm,
|
|
vrfcClsfCd: data.vrfcClsfCd,
|
|
dcptDigtCnt: data.dcptDigtCnt,
|
|
useYn: data.useYn,
|
|
...(data.dcptDigtCnt === 0
|
|
? {}
|
|
: { dcptPrcsMthCd: data.dcptPrcsMthCd }),
|
|
trgtStatsCd: data.trgtStatsCd,
|
|
mdfrId: userInfo.userUnqId,
|
|
};
|
|
await handleUpdateVrfcMaster(updateData);
|
|
} else {
|
|
const registData = {
|
|
vrfcNm: data.vrfcNm,
|
|
vrfcClsfCd: data.vrfcClsfCd,
|
|
dcptDigtCnt: data.dcptDigtCnt,
|
|
dcptPrcsMthCd: data.dcptPrcsMthCd,
|
|
trgtStatsCd: data.trgtStatsCd,
|
|
rgtrId: userInfo.userUnqId
|
|
};
|
|
await handleRegistVrfcMaster(registData);
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
form.reset();
|
|
setCheckVrfcNm(false);
|
|
originalVrfcNmRef.current = null;
|
|
}
|
|
}, [open, form]);
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange} {...props}>
|
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
|
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{isVrfcSn ?
|
|
detailData ? detailData.data.vrfcNm
|
|
:
|
|
<Skeleton className="h-[14px] rounded-xl w-2/3" />
|
|
: "일반 검증 등록"}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<Form {...form}>
|
|
<form
|
|
className="space-y-4"
|
|
onSubmit={form.handleSubmit((data) => {
|
|
void onSubmit(data);
|
|
})}
|
|
>
|
|
<Table variant="secondary">
|
|
<colgroup>
|
|
<col width="25%" />
|
|
<col width="75%" />
|
|
</colgroup>
|
|
<TableBody>
|
|
<TableRow>
|
|
<TableHead>* 통계항목</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="trgtStatsCd"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<CodeSelect
|
|
upCd="STITEM"
|
|
value={field.value ?? ''}
|
|
onValueChange={field.onChange}
|
|
placeholder="전체"
|
|
className="w-[16rem]"
|
|
disabled={isVrfcSn}
|
|
/>
|
|
</FormControl>
|
|
{form.formState.errors.trgtStatsCd && (
|
|
<p className="text-red-500 text-base pt-2">
|
|
{form.formState.errors.trgtStatsCd.message}
|
|
</p>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>* 검증명</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="vrfcNm"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Input
|
|
{...field}
|
|
value={field.value ?? ""}
|
|
className="!text-[1.3rem]"
|
|
placeholder="검증명을 입력해주세요"
|
|
disabled={(trgtStatsCd ?? '') === ''}
|
|
autoComplete="off"
|
|
/>
|
|
</FormControl>
|
|
{checkVrfcNm &&
|
|
<p className="text-red-500 text-base pt-2">
|
|
이미 등록된 검증명입니다.
|
|
</p>
|
|
}
|
|
{form.formState.errors.vrfcNm && (
|
|
<p className="text-red-500 text-base pt-2">
|
|
{form.formState.errors.vrfcNm.message}
|
|
</p>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
{/* <Button color="white" className="mt-2">중복검사</Button> */}
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>* 검증분류</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="vrfcClsfCd"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Select
|
|
value={field.value ?? ""}
|
|
onValueChange={field.onChange}
|
|
>
|
|
<SelectTrigger className="w-[10rem]">
|
|
<SelectValue placeholder="선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="ALL">공통 검증</SelectItem>
|
|
<SelectItem value="DO_SI">도단위 검증</SelectItem>
|
|
<SelectItem value="SI_GU">시단위 검증</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
{form.formState.errors.vrfcClsfCd && (
|
|
<p className="text-red-500 text-base pt-2">
|
|
{form.formState.errors.vrfcClsfCd.message}
|
|
</p>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>* 소수점자리수</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="dcptDigtCnt"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Input
|
|
{...field}
|
|
value={field.value ?? 0}
|
|
className="!text-[1.3rem] w-[10rem]"
|
|
placeholder="소수점자리수를 입력해주세요"
|
|
type="number"
|
|
min={0}
|
|
/>
|
|
</FormControl>
|
|
<p className="text-gray-500 text-base !mt-0">
|
|
※ 0일시 소수점자리 생략
|
|
</p>
|
|
{form.formState.errors.dcptDigtCnt && (
|
|
<p className="text-red-500 text-base pt-2">
|
|
{form.formState.errors.dcptDigtCnt.message}
|
|
</p>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>소수점처리방식</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="dcptPrcsMthCd"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Select
|
|
value={field.value ?? ""}
|
|
onValueChange={field.onChange}
|
|
disabled={Number(dcptDigtCnt ?? 0) === 0}
|
|
>
|
|
<SelectTrigger className="w-[10rem]">
|
|
<SelectValue placeholder="선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="CEIL">올림</SelectItem>
|
|
<SelectItem value="ROUND">반올림</SelectItem>
|
|
<SelectItem value="FLOOR">내림</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
<p className="text-gray-500 text-base !mt-0">
|
|
※ 소수점자리가 1일시 두 번째 자리에서 올림/반올림/내림 처리<br />
|
|
예시 - 14.16 → 14.2
|
|
</p>
|
|
{form.formState.errors.dcptPrcsMthCd && (
|
|
<p className="text-red-500 text-base pt-2">
|
|
{form.formState.errors.dcptPrcsMthCd.message}
|
|
</p>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
{isVrfcSn &&
|
|
<>
|
|
<TableRow>
|
|
<TableHead>사용여부</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="useYn"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<RadioGroup
|
|
{...field}
|
|
name="useYn"
|
|
onValueChange={field.onChange}
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="Y" id="useY" />
|
|
<Label htmlFor="useY">사용</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="N" id="useN" />
|
|
<Label htmlFor="useN">사용안함</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</FormControl>
|
|
{form.formState.errors.useYn && (
|
|
<p className="text-red-500 text-base pt-2">
|
|
{form.formState.errors.useYn.message}
|
|
</p>
|
|
)}
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>등록자</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="rgtrId"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
{registrarUser?.userName != null ? (
|
|
<span className="!text-[1.3rem]">{registrarUser.userName}</span>
|
|
) : field.value ? (
|
|
<Skeleton className="h-9 w-24 rounded-xl" />
|
|
) : (
|
|
<span className="!text-[1.3rem]">-</span>
|
|
)}
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>등록일</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="regDt"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
{field.value != null && field.value != '' ?
|
|
<span className="!text-[1.3rem]">{field.value}</span>
|
|
:
|
|
<span className="!text-[1.3rem]">-</span>
|
|
}
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>수정자</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="mdfrId"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
{modifierUser?.userName != null ? (
|
|
<span className="!text-[1.3rem]">{modifierUser.userName}</span>
|
|
) : field.value ? (
|
|
<Skeleton className="h-9 w-24 rounded-xl" />
|
|
) : (
|
|
<span className="!text-[1.3rem]">-</span>
|
|
)}
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>수정일</TableHead>
|
|
<TableCell className="border">
|
|
<FormField
|
|
control={form.control}
|
|
name="mdfcnDt"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
{field.value != null && field.value != '' ?
|
|
<span className="!text-[1.3rem]">{field.value}</span>
|
|
:
|
|
<span className="!text-[1.3rem]">-</span>
|
|
}
|
|
</FormControl>
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
</>
|
|
}
|
|
</TableBody>
|
|
</Table>
|
|
<DialogFooter>
|
|
{vrfcSn != null &&
|
|
<Button size="lg" type="button" color="red" onClick={handleDeleteVrfcMaster}>
|
|
삭제
|
|
</Button>
|
|
}
|
|
<Button size="lg" type="submit">
|
|
{vrfcSn != null ? '수정' : '등록'}
|
|
</Button>
|
|
<DialogClose asChild>
|
|
<Button size="lg" color="white">
|
|
취소
|
|
</Button>
|
|
</DialogClose>
|
|
</DialogFooter>
|
|
</form>
|
|
</Form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
})
|
|
|
|
RegistDialog.displayName = 'RegistDialog';
|
|
|
|
export default RegistDialog; |