[st] feat: 통계 검증 승인 기능 작업

This commit is contained in:
2026-04-22 13:28:32 +09:00
parent 91263b77dd
commit 2e858dc901
3 changed files with 199 additions and 8 deletions
@@ -0,0 +1,177 @@
"use client";
import { useEffect, useState } from "react";
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Input,
Label,
Textarea,
} from "@common/components";
export interface ApprovalFormData {
rjctOpnnCn: string;
lastCfmtnPicNm: string;
lastCfmtnPicTelNo: string;
lastCfmtnPicDeptNm: string;
lastCfmtnPicJbgdNm: string;
}
export interface ApprovalInputDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm?: (data: ApprovalFormData) => void | Promise<void>;
}
export function ApprovalInputDialog({
open,
onOpenChange,
onConfirm,
}: ApprovalInputDialogProps) {
const [rjctOpnnCn, setRjctOpnnCn] = useState("");
const [lastCfmtnPicNm, setLastCfmtnPicNm] = useState("");
const [lastCfmtnPicTelNo, setLastCfmtnPicTelNo] = useState("");
const [lastCfmtnPicDeptNm, setLastCfmtnPicDeptNm] = useState("");
const [lastCfmtnPicJbgdNm, setLastCfmtnPicJbgdNm] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
setRjctOpnnCn("");
setLastCfmtnPicNm("");
setLastCfmtnPicTelNo("");
setLastCfmtnPicDeptNm("");
setLastCfmtnPicJbgdNm("");
};
const handleDialogOpenChange = (nextOpen: boolean) => {
if (!nextOpen) resetForm();
onOpenChange(nextOpen);
};
useEffect(() => {
if (!open) {
resetForm();
}
}, [open]);
const handleConfirm = async () => {
try {
setIsSubmitting(true);
const formData: ApprovalFormData = {
rjctOpnnCn: rjctOpnnCn.trim(),
lastCfmtnPicNm: lastCfmtnPicNm.trim(),
lastCfmtnPicTelNo: lastCfmtnPicTelNo.trim(),
lastCfmtnPicDeptNm: lastCfmtnPicDeptNm.trim(),
lastCfmtnPicJbgdNm: lastCfmtnPicJbgdNm.trim(),
};
await onConfirm?.(formData);
} finally {
setIsSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={handleDialogOpenChange}>
<DialogContent
className="max-w-[56rem]"
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={lastCfmtnPicNm}
onChange={(e) => {
setLastCfmtnPicNm(e.target.value);
}}
className="!text-[1.3rem]"
placeholder="담당자명을 입력하세요."
disabled={isSubmitting}
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">
최종 확인 담당자 전화번호
</Label>
<Input
value={lastCfmtnPicTelNo}
onChange={(e) => {
setLastCfmtnPicTelNo(e.target.value);
}}
className="!text-[1.3rem]"
placeholder="전화번호를 입력하세요."
disabled={isSubmitting}
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">
최종 확인 담당자 부서명
</Label>
<Input
value={lastCfmtnPicDeptNm}
onChange={(e) => {
setLastCfmtnPicDeptNm(e.target.value);
}}
className="!text-[1.3rem]"
placeholder="부서명을 입력하세요."
disabled={isSubmitting}
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">
최종 확인 담당자 직급명
</Label>
<Input
value={lastCfmtnPicJbgdNm}
onChange={(e) => {
setLastCfmtnPicJbgdNm(e.target.value);
}}
className="!text-[1.3rem]"
placeholder="직급명을 입력하세요."
disabled={isSubmitting}
/>
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">반려 의견 내용</Label>
<Textarea
value={rjctOpnnCn}
onChange={(e) => setRjctOpnnCn(e.target.value)}
className="min-h-[10rem] resize-y !text-[1.3rem] !leading-6"
placeholder="반려 의견 내용을 입력하세요. (선택사항)"
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>
);
}
@@ -46,6 +46,10 @@ import {
} from "@common/components";
import DataGrid from "@/components/module/DataGrid";
import {
type ApprovalFormData,
ApprovalInputDialog,
} from "@/components/statistics/verification/ApprovalInputDialog";
import type { CellEditedCallbackParams } from "@/components/type/DataGrid.type";
import { toGridRows } from "@/components/util/grid/gridFunction";
@@ -167,6 +171,7 @@ function StatisticsVerificationReport() {
const [selectedResultRows, setSelectedResultRows] = useState<
VerifyResultItem[]
>([]);
const [approvalDialogOpen, setApprovalDialogOpen] = useState(false);
const editedResultRowMapRef = useRef<Map<number, VerifyResultItem>>(
new Map(),
);
@@ -405,24 +410,27 @@ function StatisticsVerificationReport() {
handleSearch(DEFAULT_FILTERS, DEFAULT_SUB_FILTERS);
}
async function handleApprove() {
function handleApprove() {
if (selectedReportRows.length === 0) return;
setApprovalDialogOpen(true);
}
async function handleApprovalConfirm(formData: ApprovalFormData) {
const commonParam = {
statsYr: filters.statsYr,
sewerBzmnCd: filters.sewerBzmnCd ?? "",
statsCd: filters.statsCd ?? "",
mdfrId: userInfo?.userUnqId ?? "",
};
const approvalRequests = selectedReportRows.map((row) => ({
...commonParam,
rjctOpnnCn: row.rjctOpnnCn ?? "",
lastCfmtnPicNm: row.laePicNm ?? "",
lastCfmtnPicTelNo: row.laePicTelNo ?? "",
lastCfmtnPicDeptNm: row.laeKbrdrDeptNm ?? "",
lastCfmtnPicJbgdNm: row.laeKbrdrJbgdNm ?? "",
...formData,
statsCd: row.statsCd ?? "",
sewerBzmnCd: row.sewerBzmnCd ?? "",
}));
try {
if (await confirm("승인하시겠습니까?")) {
await onApproveStats(approvalRequests);
setApprovalDialogOpen(false);
}
} catch {
alert("승인 중 오류가 발생했습니다.");
@@ -474,6 +482,11 @@ function StatisticsVerificationReport() {
return (
<div className="relative">
<ApprovalInputDialog
open={approvalDialogOpen}
onOpenChange={setApprovalDialogOpen}
onConfirm={handleApprovalConfirm}
/>
<div className="flex justify-between p-2">
<div className="flex items-center gap-6">
<FilterSelect
@@ -12,6 +12,7 @@ export interface StatsInputStatusList {
ctpvNm?: string;
sggNm?: string;
statsCd?: string;
sewerBzmnCd?: string;
menuTabName?: string;
locgStatusNm?: string;
inputRate?: number;