225 lines
8.5 KiB
TypeScript
225 lines
8.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { useAlertStore } from "@/shared/lib";
|
|
import {
|
|
Button,
|
|
Dialog,
|
|
DialogClose,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
Input,
|
|
Label,
|
|
} from "@common/components";
|
|
|
|
export interface MainFacilityMappingSaveDialogValues {
|
|
prxyBzentyNmCn: string;
|
|
/** 위탁비용(백만원/년) */
|
|
cnsgnCstVl: number;
|
|
wholNope: number;
|
|
/** YYYYMMDD~YYYYMMDD */
|
|
prxyCtrtCn: string;
|
|
}
|
|
|
|
export interface MainFacilityMappingSaveDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
initialPrxyBzentyNmCn: string;
|
|
/** 위탁비용(백만원/년) 초기값 */
|
|
initialCnsgnCstVl: number;
|
|
initialWholNope: number;
|
|
/** 위탁계약기간 초기값 (YYYYMMDD~YYYYMMDD) */
|
|
initialPrxyCtrtCn: string;
|
|
onConfirm?: (values: MainFacilityMappingSaveDialogValues) => void | Promise<void>;
|
|
}
|
|
|
|
function isValidYmd(ymd: string): boolean {
|
|
if (!/^\d{8}$/.test(ymd)) return false;
|
|
const y = Number(ymd.slice(0, 4));
|
|
const m = Number(ymd.slice(4, 6));
|
|
const d = Number(ymd.slice(6, 8));
|
|
const dt = new Date(y, m - 1, d);
|
|
return dt.getFullYear() === y && dt.getMonth() === m - 1 && dt.getDate() === d;
|
|
}
|
|
|
|
function validatePrxyCtrtCn(value: string): string | null {
|
|
const t = value.trim();
|
|
if (!t) return "위탁계약기간을 입력해 주세요.";
|
|
if (!/^\d{8}~\d{8}$/.test(t)) {
|
|
return "위탁계약기간은 YYYYMMDD~YYYYMMDD 형식으로 입력해 주세요. (예: 20240101~20281231)";
|
|
}
|
|
const start = t.slice(0, 8);
|
|
const end = t.slice(9, 17);
|
|
if (!isValidYmd(start) || !isValidYmd(end)) {
|
|
return "시작일 또는 종료일이 유효한 날짜가 아닙니다.";
|
|
}
|
|
if (start > end) {
|
|
return "시작일이 종료일보다 늦을 수 없습니다.";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function MainFacilityMappingSaveDialog({
|
|
open,
|
|
onOpenChange,
|
|
initialPrxyBzentyNmCn,
|
|
initialCnsgnCstVl,
|
|
initialWholNope,
|
|
initialPrxyCtrtCn,
|
|
onConfirm,
|
|
}: MainFacilityMappingSaveDialogProps) {
|
|
const { setMessage: alert } = useAlertStore((state) => state);
|
|
const [prxyBzentyNmCn, setPrxyBzentyNmCn] = useState("");
|
|
const [cnsgnCstInput, setCnsgnCstInput] = useState("");
|
|
const [wholNopeInput, setWholNopeInput] = useState("");
|
|
const [prxyCtrtCn, setPrxyCtrtCn] = useState("");
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
setPrxyBzentyNmCn(initialPrxyBzentyNmCn ?? "");
|
|
const initCost = Number(initialCnsgnCstVl);
|
|
setCnsgnCstInput(
|
|
initialCnsgnCstVl === null ||
|
|
initialCnsgnCstVl === undefined ||
|
|
Number.isNaN(initCost)
|
|
? ""
|
|
: String(initCost),
|
|
);
|
|
setWholNopeInput(
|
|
initialWholNope === null || initialWholNope === undefined || Number.isNaN(Number(initialWholNope))
|
|
? ""
|
|
: String(initialWholNope),
|
|
);
|
|
setPrxyCtrtCn((initialPrxyCtrtCn ?? "").trim());
|
|
}
|
|
}, [open, initialPrxyBzentyNmCn, initialCnsgnCstVl, initialWholNope, initialPrxyCtrtCn]);
|
|
|
|
const handleDialogOpenChange = (nextOpen: boolean) => {
|
|
if (!nextOpen && !isSubmitting) {
|
|
setPrxyBzentyNmCn("");
|
|
setCnsgnCstInput("");
|
|
setWholNopeInput("");
|
|
setPrxyCtrtCn("");
|
|
}
|
|
onOpenChange(nextOpen);
|
|
};
|
|
|
|
const handleConfirm = async () => {
|
|
const trimmedCost = cnsgnCstInput.trim();
|
|
const cnsgnCstVl = trimmedCost === "" ? 0 : Number.parseFloat(trimmedCost.replace(/,/g, ""));
|
|
if (!Number.isFinite(cnsgnCstVl) || cnsgnCstVl < 0) {
|
|
alert("위탁비용은 0 이상의 숫자로 입력해 주세요.");
|
|
return;
|
|
}
|
|
const trimmedWhol = wholNopeInput.trim();
|
|
const wholNope = trimmedWhol === "" ? 0 : Number.parseInt(trimmedWhol, 10);
|
|
if (!Number.isFinite(wholNope) || wholNope < 0) {
|
|
alert("전체 인원은 0 이상의 정수로 입력해 주세요.");
|
|
return;
|
|
}
|
|
const prxyCtrtErr = validatePrxyCtrtCn(prxyCtrtCn);
|
|
if (prxyCtrtErr) {
|
|
alert(prxyCtrtErr);
|
|
return;
|
|
}
|
|
try {
|
|
setIsSubmitting(true);
|
|
await onConfirm?.({
|
|
prxyBzentyNmCn: prxyBzentyNmCn.trim(),
|
|
cnsgnCstVl,
|
|
wholNope,
|
|
prxyCtrtCn: prxyCtrtCn.trim(),
|
|
});
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={handleDialogOpenChange}>
|
|
<DialogContent className="max-w-[48rem]" aria-describedby={undefined}>
|
|
<DialogHeader>
|
|
<DialogTitle>저장 정보 입력</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="flex flex-col gap-4 py-2">
|
|
<div className="flex flex-col gap-2">
|
|
<Label className="text-label">위탁업체명</Label>
|
|
<Input
|
|
value={prxyBzentyNmCn}
|
|
onChange={(e) => setPrxyBzentyNmCn(e.target.value)}
|
|
className="!text-[1.3rem]"
|
|
placeholder="위탁사업자명을 입력하세요."
|
|
disabled={isSubmitting}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<Label className="text-label">위탁비용(백만원/년)</Label>
|
|
<Input
|
|
type="text"
|
|
inputMode="decimal"
|
|
value={cnsgnCstInput}
|
|
onChange={(e) => {
|
|
const v = e.target.value.replace(/[^\d.]/g, "");
|
|
const firstDot = v.indexOf(".");
|
|
const next =
|
|
firstDot === -1
|
|
? v
|
|
: v.slice(0, firstDot + 1) + v.slice(firstDot + 1).replace(/\./g, "");
|
|
setCnsgnCstInput(next);
|
|
}}
|
|
className="!text-[1.3rem]"
|
|
placeholder="0"
|
|
disabled={isSubmitting}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<Label className="text-label">전체 인원</Label>
|
|
<Input
|
|
type="text"
|
|
inputMode="numeric"
|
|
value={wholNopeInput}
|
|
onChange={(e) => {
|
|
const v = e.target.value.replace(/\D/g, "");
|
|
setWholNopeInput(v);
|
|
}}
|
|
className="!text-[1.3rem]"
|
|
placeholder="0"
|
|
disabled={isSubmitting}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<Label className="text-label">위탁계약기간</Label>
|
|
<Input
|
|
value={prxyCtrtCn}
|
|
onChange={(e) => setPrxyCtrtCn(e.target.value)}
|
|
className="!text-[1.3rem]"
|
|
placeholder="YYYYMMDD~YYYYMMDD (예: 20240101~20281231)"
|
|
autoComplete="off"
|
|
disabled={isSubmitting}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<DialogClose asChild>
|
|
<Button type="button" color="gray" disabled={isSubmitting}>
|
|
취소
|
|
</Button>
|
|
</DialogClose>
|
|
<Button
|
|
type="button"
|
|
color="primary"
|
|
disabled={isSubmitting}
|
|
onClick={() => void handleConfirm()}
|
|
>
|
|
저장
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|