summaryrefslogtreecommitdiff
path: root/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/evaluation/table/periodic-evaluation-action-dialogs.tsx')
-rw-r--r--lib/evaluation/table/periodic-evaluation-action-dialogs.tsx373
1 files changed, 373 insertions, 0 deletions
diff --git a/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx b/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx
new file mode 100644
index 00000000..30ff9535
--- /dev/null
+++ b/lib/evaluation/table/periodic-evaluation-action-dialogs.tsx
@@ -0,0 +1,373 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { FileText, Users, Calendar, Send } from "lucide-react"
+import { toast } from "sonner"
+import { PeriodicEvaluationView } from "@/db/schema"
+import { checkExistingSubmissions, requestDocumentsFromVendors } from "../service"
+
+
+// ================================================================
+// 2. 협력업체 자료 요청 다이얼로그
+// ================================================================
+interface RequestDocumentsDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ evaluations: PeriodicEvaluationView[]
+ onSuccess: () => void
+}
+
+interface EvaluationWithSubmissionStatus extends PeriodicEvaluationView {
+ hasExistingSubmission?: boolean
+ submissionDate?: Date | null
+}
+
+export function RequestDocumentsDialog({
+ open,
+ onOpenChange,
+ evaluations,
+ onSuccess,
+}: RequestDocumentsDialogProps) {
+
+ console.log(evaluations)
+
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [isCheckingStatus, setIsCheckingStatus] = React.useState(false)
+ const [message, setMessage] = React.useState("")
+ const [evaluationsWithStatus, setEvaluationsWithStatus] = React.useState<EvaluationWithSubmissionStatus[]>([])
+
+ // 제출대기 상태인 평가들만 필터링
+ const pendingEvaluations = React.useMemo(() =>
+ evaluations.filter(e => e.status === "PENDING_SUBMISSION"),
+ [evaluations]
+ )
+
+ React.useEffect(() => {
+ if (!open) return;
+
+ // 대기 중 평가가 없으면 초기화
+ if (pendingEvaluations.length === 0) {
+ setEvaluationsWithStatus([]);
+ setIsCheckingStatus(false);
+ return;
+ }
+
+ // 상태 확인
+ (async () => {
+ setIsCheckingStatus(true);
+ try {
+ const ids = pendingEvaluations.map(e => e.id);
+ const existing = await checkExistingSubmissions(ids);
+
+ setEvaluationsWithStatus(
+ pendingEvaluations.map(e => ({
+ ...e,
+ hasExistingSubmission: existing.some(s => s.periodicEvaluationId === e.id),
+ submissionDate: existing.find(s => s.periodicEvaluationId === e.id)?.createdAt ?? null,
+ })),
+ );
+ } catch (err) {
+ console.error(err);
+ setEvaluationsWithStatus(
+ pendingEvaluations.map(e => ({ ...e, hasExistingSubmission: false })),
+ );
+ } finally {
+ setIsCheckingStatus(false);
+ }
+ })();
+ }, [open, pendingEvaluations]); // 함수 대신 값에만 의존
+
+ // 새 요청과 재요청 분리
+ const newRequests = evaluationsWithStatus.filter(e => !e.hasExistingSubmission)
+ const reRequests = evaluationsWithStatus.filter(e => e.hasExistingSubmission)
+
+ const handleSubmit = async () => {
+ if (!message.trim()) {
+ toast.error("요청 메시지를 입력해주세요.")
+ return
+ }
+
+ setIsLoading(true)
+ try {
+ // 서버 액션 데이터 준비
+ const requestData = evaluationsWithStatus.map(evaluation => ({
+ periodicEvaluationId: evaluation.id,
+ companyId: evaluation.vendorId,
+ evaluationYear: evaluation.evaluationYear,
+ evaluationRound: evaluation.evaluationPeriod,
+ message: message.trim()
+ }))
+
+ // 서버 액션 호출
+ const result = await requestDocumentsFromVendors(requestData)
+
+ if (result.success) {
+ toast.success(result.message)
+ onSuccess()
+ onOpenChange(false)
+ setMessage("")
+ } else {
+ toast.error(result.message)
+ }
+ } catch (error) {
+ console.error('Error requesting documents:', error)
+ toast.error("자료 요청 발송 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="sm:max-w-2xl">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ <FileText className="size-4" />
+ 협력업체 자료 요청
+ </DialogTitle>
+ <DialogDescription>
+ 선택된 평가의 협력업체들에게 평가 자료 제출을 요청합니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-4">
+ {isCheckingStatus ? (
+ <div className="flex items-center justify-center py-8">
+ <div className="text-sm text-muted-foreground">요청 상태를 확인하고 있습니다...</div>
+ </div>
+ ) : (
+ <>
+ {/* 신규 요청 대상 업체 */}
+ {newRequests.length > 0 && (
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-sm text-blue-600">
+ 신규 요청 대상 ({newRequests.length}개 업체)
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-2 max-h-32 overflow-y-auto">
+ {newRequests.map((evaluation) => (
+ <div
+ key={evaluation.id}
+ className="flex items-center justify-between text-sm p-2 bg-blue-50 rounded"
+ >
+ <span className="font-medium">{evaluation.vendorName}</span>
+ <div className="flex gap-2">
+ <Badge variant="outline">{evaluation.vendorCode}</Badge>
+ <Badge variant="default" className="bg-blue-600">신규</Badge>
+ </div>
+ </div>
+ ))}
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 재요청 대상 업체 */}
+ {reRequests.length > 0 && (
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-sm text-orange-600">
+ 재요청 대상 ({reRequests.length}개 업체)
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-2 max-h-32 overflow-y-auto">
+ {reRequests.map((evaluation) => (
+ <div
+ key={evaluation.id}
+ className="flex items-center justify-between text-sm p-2 bg-orange-50 rounded"
+ >
+ <span className="font-medium">{evaluation.vendorName}</span>
+ <div className="flex gap-2">
+ <Badge variant="outline">{evaluation.vendorCode}</Badge>
+ <Badge variant="secondary" className="bg-orange-100">
+ 재요청
+ </Badge>
+ {evaluation.submissionDate && (
+ <span className="text-xs text-muted-foreground">
+ {new Intl.DateTimeFormat("ko-KR", {
+ month: "2-digit",
+ day: "2-digit",
+ }).format(new Date(evaluation.submissionDate))}
+ </span>
+ )}
+ </div>
+ </div>
+ ))}
+ </CardContent>
+ </Card>
+ )}
+
+ {/* 요청 대상이 없는 경우 */}
+ {!isCheckingStatus && evaluationsWithStatus.length === 0 && (
+ <Card>
+ <CardContent className="pt-6">
+ <div className="text-center text-sm text-muted-foreground">
+ 요청할 수 있는 평가가 없습니다.
+ </div>
+ </CardContent>
+ </Card>
+ )}
+ </>
+ )}
+
+ {/* 요청 메시지 */}
+ <div className="space-y-2">
+ <Label htmlFor="message">요청 메시지</Label>
+ <Textarea
+ id="message"
+ placeholder="협력업체에게 전달할 메시지를 입력하세요..."
+ value={message}
+ onChange={(e) => setMessage(e.target.value)}
+ rows={4}
+ disabled={isCheckingStatus}
+ />
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={() => onOpenChange(false)}
+ disabled={isLoading || isCheckingStatus}
+ >
+ 취소
+ </Button>
+ <Button
+ onClick={handleSubmit}
+ disabled={isLoading || isCheckingStatus || evaluationsWithStatus.length === 0}
+ >
+ <Send className="size-4 mr-2" />
+ {isLoading ? "발송 중..." :
+ `요청 발송 (신규 ${newRequests.length}개${reRequests.length > 0 ? `, 재요청 ${reRequests.length}개` : ''})`}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}
+
+
+
+// ================================================================
+// 3. 평가자 평가 요청 다이얼로그
+// ================================================================
+interface RequestEvaluationDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ evaluations: PeriodicEvaluationView[]
+ onSuccess: () => void
+}
+
+export function RequestEvaluationDialog({
+ open,
+ onOpenChange,
+ evaluations,
+ onSuccess,
+}: RequestEvaluationDialogProps) {
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [message, setMessage] = React.useState("")
+
+ // 제출완료 상태인 평가들만 필터링
+ const submittedEvaluations = evaluations.filter(e => e.status === "SUBMITTED")
+
+ const handleSubmit = async () => {
+ if (!message.trim()) {
+ toast.error("요청 메시지를 입력해주세요.")
+ return
+ }
+
+ setIsLoading(true)
+ try {
+ // TODO: 평가자들에게 평가 요청 API 호출
+ toast.success(`${submittedEvaluations.length}개 평가에 대한 평가 요청이 발송되었습니다.`)
+ onSuccess()
+ onOpenChange(false)
+ setMessage("")
+ } catch (error) {
+ console.error('Error requesting evaluation:', error)
+ toast.error("평가 요청 발송 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="sm:max-w-2xl">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ <Users className="size-4" />
+ 평가자 평가 요청
+ </DialogTitle>
+ <DialogDescription>
+ 선택된 평가들에 대해 평가자들에게 평가를 요청합니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-4">
+ {/* 대상 평가 목록 */}
+ <Card>
+ <CardHeader className="pb-3">
+ <CardTitle className="text-sm">
+ 평가 대상 ({submittedEvaluations.length}개 평가)
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-2 max-h-32 overflow-y-auto">
+ {submittedEvaluations.map((evaluation) => (
+ <div
+ key={evaluation.id}
+ className="flex items-center justify-between text-sm"
+ >
+ <span className="font-medium">{evaluation.vendorName}</span>
+ <div className="flex gap-2">
+ <Badge variant="outline">{evaluation.evaluationPeriod}</Badge>
+ <Badge variant="secondary">제출완료</Badge>
+ </div>
+ </div>
+ ))}
+ </CardContent>
+ </Card>
+
+ {/* 요청 메시지 */}
+ <div className="space-y-2">
+ <Label htmlFor="evaluation-message">요청 메시지</Label>
+ <Textarea
+ id="evaluation-message"
+ placeholder="평가자들에게 전달할 메시지를 입력하세요..."
+ value={message}
+ onChange={(e) => setMessage(e.target.value)}
+ rows={4}
+ />
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={() => onOpenChange(false)}
+ disabled={isLoading}
+ >
+ 취소
+ </Button>
+ <Button onClick={handleSubmit} disabled={isLoading}>
+ <Send className="size-4 mr-2" />
+ {isLoading ? "발송 중..." : `${submittedEvaluations.length}개 평가 요청`}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+} \ No newline at end of file