191 lines
8.2 KiB
TypeScript
191 lines
8.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { useForm, Controller } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { statisticsInputSubmitInfoSchema, type StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
|
import type { SewerageStatusInfo } from "@/entities/statistics/model/statistics-interface";
|
|
|
|
import {
|
|
Button,
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
Input,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableRow,
|
|
LoadingWithAnimation,
|
|
} from "@common/components";
|
|
|
|
/** 숫자만 추출 후 하이픈 포맷 (010-1234-5678, 02-123-4567 등) */
|
|
function formatPhoneWithHyphen(value: string): string {
|
|
const digits = value.replace(/\D/g, "");
|
|
if (digits.length <= 3) return digits;
|
|
if (digits.startsWith("010")) {
|
|
if (digits.length <= 7) return `010-${digits.slice(3)}`;
|
|
return `010-${digits.slice(3, 7)}-${digits.slice(7, 11)}`;
|
|
}
|
|
if (digits.startsWith("02")) {
|
|
if (digits.length <= 5) return `02-${digits.slice(2)}`;
|
|
if (digits.length <= 9) return `02-${digits.slice(2, 5)}-${digits.slice(5, 9)}`;
|
|
return `02-${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
|
|
}
|
|
if (digits.length <= 6) return `${digits.slice(0, 3)}-${digits.slice(3)}`;
|
|
return `${digits.slice(0, 3)}-${digits.slice(3, 6)}-${digits.slice(6, 10)}`;
|
|
}
|
|
|
|
const PHONE_FIELDS: (keyof StatisticsInputSubmitInfoFormData)[] = ["laePicTelNo", "laeIdfrTelNo"];
|
|
|
|
interface SubmitDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
statusInfo: SewerageStatusInfo | null;
|
|
/** 다이얼로그에서 저장 클릭 시 호출. 제출 정보를 넘기면 부모에서 requestData에 포함해 제출 API 호출 */
|
|
onConfirm?: (data: StatisticsInputSubmitInfoFormData) => void | Promise<void>;
|
|
/** 예외 제출로 다이얼로그를 연 경우: 일반 제출(onConfirm) 대신 호출 */
|
|
onExceptionConfirm?: (data: StatisticsInputSubmitInfoFormData) => void | Promise<void>;
|
|
/** 'normal': onConfirm / 'exception': onExceptionConfirm */
|
|
submitMode?: "normal" | "exception";
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
const defaultValues: StatisticsInputSubmitInfoFormData = {
|
|
laePicNm: "",
|
|
laeKbrdrJbgdNm: "",
|
|
laeKbrdrDeptNm: "",
|
|
laePicTelNo: "",
|
|
laeIdfrNm: "",
|
|
laeIdfrJbgdNm: "",
|
|
laeIdfrDeptNm: "",
|
|
laeIdfrTelNo: "",
|
|
};
|
|
|
|
function statusInfoToFormData(info: SewerageStatusInfo): StatisticsInputSubmitInfoFormData {
|
|
return {
|
|
laePicNm: info.laePicNm ?? "",
|
|
laeKbrdrJbgdNm: info.laeKbrdrJbgdNm ?? "",
|
|
laeKbrdrDeptNm: info.laeKbrdrDeptNm ?? "",
|
|
laePicTelNo: info.laePicTelNo ?? "",
|
|
laeIdfrNm: info.laeIdfrNm ?? "",
|
|
laeIdfrJbgdNm: info.laeIdfrJbgdNm ?? "",
|
|
laeIdfrDeptNm: info.laeIdfrDeptNm ?? "",
|
|
laeIdfrTelNo: info.laeIdfrTelNo ?? "",
|
|
};
|
|
};
|
|
|
|
const SubmitDailog = ({
|
|
open,
|
|
onOpenChange,
|
|
statusInfo,
|
|
onConfirm,
|
|
onExceptionConfirm,
|
|
submitMode = "normal",
|
|
isLoading,
|
|
}: SubmitDialogProps) => {
|
|
const { control, handleSubmit, reset, formState: { errors } } = useForm<StatisticsInputSubmitInfoFormData>({
|
|
resolver: zodResolver(statisticsInputSubmitInfoSchema),
|
|
defaultValues,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
reset(statusInfo ? statusInfoToFormData(statusInfo) : defaultValues);
|
|
}
|
|
}, [open, statusInfo, reset]);
|
|
|
|
async function onSubmit(data: StatisticsInputSubmitInfoFormData) {
|
|
if (submitMode === "exception") {
|
|
await onExceptionConfirm?.(data);
|
|
} else {
|
|
await onConfirm?.(data);
|
|
}
|
|
onOpenChange(false);
|
|
reset(defaultValues);
|
|
}
|
|
|
|
function handleCancel() {
|
|
onOpenChange(false);
|
|
reset(defaultValues);
|
|
}
|
|
|
|
const rows: { name: keyof StatisticsInputSubmitInfoFormData; label: string; placeholder: string }[] = [
|
|
{ name: "laePicNm", label: "작성자 명", placeholder: "작성자 명 입력" },
|
|
{ name: "laeKbrdrJbgdNm", label: "작성자 직제", placeholder: "작성자 직제 입력" },
|
|
{ name: "laeKbrdrDeptNm", label: "작성자 부서", placeholder: "작성자 부서 입력" },
|
|
{ name: "laePicTelNo", label: "작성자 연락처", placeholder: "작성자 연락처 입력" },
|
|
{ name: "laeIdfrNm", label: "승인자명", placeholder: "승인자명 입력" },
|
|
{ name: "laeIdfrJbgdNm", label: "승인자 직제", placeholder: "승인자 직제 입력" },
|
|
{ name: "laeIdfrDeptNm", label: "승인자 부서", placeholder: "승인자 부서 입력" },
|
|
{ name: "laeIdfrTelNo", label: "승인자 연락처", placeholder: "승인자 연락처" },
|
|
];
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-[800px]">
|
|
<DialogHeader>
|
|
<DialogTitle>제출 정보</DialogTitle>
|
|
</DialogHeader>
|
|
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
|
<div className="relative size-[5rem]">
|
|
<LoadingWithAnimation color="primary" />
|
|
</div>
|
|
</div>}
|
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
<Table variant="secondary">
|
|
<TableBody>
|
|
{rows.map(({ name, label, placeholder }) => (
|
|
<TableRow key={name}>
|
|
<TableHead className="w-[150px] bg-muted font-medium" data-required>
|
|
{label}
|
|
</TableHead>
|
|
<TableCell>
|
|
<Controller
|
|
name={name}
|
|
control={control}
|
|
render={({ field }) => {
|
|
const isPhone = PHONE_FIELDS.includes(name);
|
|
return (
|
|
<div className="space-y-1">
|
|
<Input
|
|
{...field}
|
|
value={field.value ?? ""}
|
|
placeholder={placeholder}
|
|
onChange={(e) => {
|
|
const v = e.target.value;
|
|
field.onChange(isPhone ? formatPhoneWithHyphen(v) : v);
|
|
}}
|
|
/>
|
|
{errors[name]?.message && (
|
|
<p className="text-sm text-destructive ml-1">
|
|
{errors[name]?.message}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
<DialogFooter className="mt-6">
|
|
<Button type="submit" color="primary">
|
|
{submitMode === 'normal' ? '제출' : '예외 제출'}
|
|
</Button>
|
|
<Button type="button" color="gray" onClick={handleCancel}>
|
|
취소
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
export default SubmitDailog; |