Files
frontend/apps/st/src/components/statistics/review/ReviewSubmitDialog.tsx
T

211 lines
7.3 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useAlertStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
ReviewerInfoRequest,
ReviewerInfoResponse,
ReviewSubmitRequest,
} from "@/entities/statistics/model/statistics-interface";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Input,
Label,
LoadingWithAnimation,
Textarea,
} from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
export interface ReviewSubmitDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
context: ReviewerInfoRequest | null;
onSubmitted?: () => void | Promise<void>;
/**
* true면 검토의견·처리내역 UI를 숨기고
* submit body에 statsCd / splmntOpnnCn / splmntRsltCn을 넣지 않음
*/
hideOpinionFields?: boolean;
}
export function ReviewSubmitDialog({
open,
onOpenChange,
context,
onSubmitted,
hideOpinionFields = false,
}: ReviewSubmitDialogProps) {
const { setMessage: alert } = useAlertStore((state) => state);
const [reviewerInfo, setReviewerInfo] =
useState<ReviewerInfoResponse["data"] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
if (!open) {
setReviewerInfo(null);
setIsLoading(false);
return;
}
if (context == null) {
setReviewerInfo(null);
return;
}
let cancelled = false;
const loadReviewerInfo = async () => {
try {
setIsLoading(true);
const response = await statisticsApi.getReviewerInfo(context);
if (cancelled) return;
if (response.status !== "success" || !response.data) {
setReviewerInfo(null);
return;
}
setReviewerInfo(response.data);
} catch {
if (!cancelled) {
setReviewerInfo(null);
}
} finally {
if (!cancelled) {
setIsLoading(false);
}
}
};
void loadReviewerInfo();
return () => {
cancelled = true;
};
}, [open, context, alert]);
const handleConfirm = async () => {
if (context == null) {
alert("제출 정보를 확인한 뒤 다시 시도해주세요.");
return;
}
try {
setIsSubmitting(true);
const userInfo = await getUserInfo();
const submitParam: ReviewSubmitRequest = hideOpinionFields
? {
statsYr: context.statsYr,
sewerBzmnCd: [context.sewerBzmnCd],
mdfrUuid: userInfo.userUnqId,
}
: {
statsYr: context.statsYr,
sewerBzmnCd: context.sewerBzmnCd,
statsCd: context.statsCd,
mdfrUuid: userInfo.userUnqId,
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "",
splmntRsltCn: reviewerInfo?.splmntRsltCn ?? "",
};
const response = await statisticsApi.submitReview(submitParam);
if (response.status !== "success") {
alert(response.message || "제출에 실패했습니다.");
return;
}
alert(response.message || "제출되었습니다.");
onOpenChange(false);
await onSubmitted?.();
} catch {
alert("제출 중 오류가 발생했습니다.");
} finally {
setIsSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[64rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>제출 정보</DialogTitle>
</DialogHeader>
{(isLoading || isSubmitting) && (
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10 [data-theme='black']:bg-transparent">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<div className="grid grid-cols-2 gap-4 py-2">
<div className="flex flex-col gap-2">
<Label className="text-label">검토자명</Label>
<Input value={reviewerInfo?.userNm ?? "-"} readOnly />
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">연락처</Label>
<Input value={reviewerInfo?.telOffrm ?? "-"} readOnly />
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">부서</Label>
<Input value={reviewerInfo?.dpmNm ?? "-"} readOnly />
</div>
<div className="flex flex-col gap-2">
<Label className="text-label">직책</Label>
<Input value={reviewerInfo?.cps ?? "-"} readOnly />
</div>
{!hideOpinionFields && (
<>
<div className="col-span-2 flex flex-col gap-2">
<Label className="text-label">검토의견</Label>
<Textarea
value={reviewerInfo?.splmntOpnnCn ?? "-"}
readOnly
className="min-h-[10rem] p-2 resize-none !text-xl"
/>
</div>
<div className="col-span-2 flex flex-col gap-2">
<Label className="text-label">처리내역</Label>
<Textarea
value={reviewerInfo?.splmntRsltCn ?? "-"}
readOnly
className="min-h-[10rem] p-2 resize-none !text-xl"
/>
</div>
</>
)}
</div>
<DialogFooter>
<Button
type="button"
color="primary"
onClick={() => void handleConfirm()}
disabled={isLoading || isSubmitting}
>
제출
</Button>
<Button
type="button"
color="gray"
onClick={() => onOpenChange(false)}
disabled={isLoading || isSubmitting}
>
취소
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}