summaryrefslogtreecommitdiff
path: root/lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx')
-rw-r--r--lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx226
1 files changed, 226 insertions, 0 deletions
diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx
new file mode 100644
index 00000000..60ca173b
--- /dev/null
+++ b/lib/evaluation-criteria/table/reg-eval-criteria-details-sheet.tsx
@@ -0,0 +1,226 @@
+'use client';
+
+/* IMPORT */
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { Separator } from '@/components/ui/separator';
+import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
+import { useEffect, useState } from 'react';
+import { Loader2, X } from 'lucide-react';
+import {
+ REG_EVAL_CRITERIA_CATEGORY,
+ REG_EVAL_CRITERIA_CATEGORY2,
+ REG_EVAL_CRITERIA_ITEM,
+ type RegEvalCriteriaView,
+ type RegEvalCriteriaDetails,
+} from '@/db/schema';
+import { getRegEvalCriteriaDetails } from '../service'; // 서버 액션 import
+
+// ----------------------------------------------------------------------------------------------------
+
+/* TYPES */
+interface RegEvalCriteriaDetailsSheetProps {
+ criteriaViewData: RegEvalCriteriaView;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* CRITERIA DETAILS SHEET COMPONENT */
+export function RegEvalCriteriaDetailsSheet({
+ criteriaViewData,
+ open,
+ onOpenChange
+}: RegEvalCriteriaDetailsSheetProps) {
+ const [details, setDetails] = useState<RegEvalCriteriaDetails[]>([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+
+ // 상세 항목들 가져오기
+ useEffect(() => {
+ if (criteriaViewData?.id && open) {
+ setLoading(true);
+ setError(null);
+
+ getRegEvalCriteriaDetails(criteriaViewData.id)
+ .then((fetchedDetails) => {
+ setDetails(fetchedDetails || []);
+ })
+ .catch((err) => {
+ console.error('Failed to fetch criteria details:', err);
+ setError('상세 정보를 불러오는데 실패했습니다.');
+ setDetails([]);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+ }, [criteriaViewData?.id, open]);
+
+ // 라벨 변환 함수들
+ const getCategoryLabel = (value: string) =>
+ REG_EVAL_CRITERIA_CATEGORY.find(item => item.value === value)?.label ?? value;
+
+ const getCategory2Label = (value: string) =>
+ REG_EVAL_CRITERIA_CATEGORY2.find(item => item.value === value)?.label ?? value;
+
+ const getItemLabel = (value: string) =>
+ REG_EVAL_CRITERIA_ITEM.find(item => item.value === value)?.label ?? value;
+
+ // 점수 표시 함수
+ const formatScore = (score: string | null | undefined) => {
+ if (!score) return '-';
+ const numericScore = typeof score === 'string' ? parseFloat(score) : score;
+ return isNaN(numericScore) ? '-' : parseFloat(numericScore.toFixed(2)).toString();
+ };
+
+ return (
+ <Sheet open={open} onOpenChange={onOpenChange}>
+ <SheetContent className="w-[800px] sm:w-[900px] sm:max-w-[90vw]" style={{width:900}}>
+ <SheetHeader>
+ <SheetTitle className="flex items-center justify-between">
+ <span>평가 기준 상세보기</span>
+ </SheetTitle>
+ </SheetHeader>
+
+ <ScrollArea className="h-[calc(100vh-120px)] pr-4">
+ <div className="space-y-6 py-4">
+ {/* 기본 정보 카드 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">기본 정보</CardTitle>
+ </CardHeader>
+ <CardContent className="space-y-4">
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">평가부문</p>
+ <Badge variant="default" className="mt-1">
+ {getCategoryLabel(criteriaViewData.category)}
+ </Badge>
+ </div>
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">점수구분</p>
+ <Badge variant="secondary" className="mt-1">
+ {getCategory2Label(criteriaViewData.category2)}
+ </Badge>
+ </div>
+ </div>
+
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">항목</p>
+ <Badge variant="outline" className="mt-1">
+ {getItemLabel(criteriaViewData.item)}
+ </Badge>
+ </div>
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">구분</p>
+ <p className="text-sm mt-1">{criteriaViewData.classification}</p>
+ </div>
+ </div>
+
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">평가명 (범위)</p>
+ <p className="text-sm mt-1 font-medium">{criteriaViewData.range || '-'}</p>
+ </div>
+
+ {criteriaViewData.remarks && (
+ <div>
+ <p className="text-sm font-medium text-muted-foreground">비고</p>
+ <p className="text-sm mt-1">{criteriaViewData.remarks}</p>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+
+ <Separator />
+
+ {/* 평가 옵션 및 점수 카드 */}
+ <Card>
+ <CardHeader>
+ <CardTitle className="text-lg">평가 옵션 및 점수</CardTitle>
+ <p className="text-sm text-muted-foreground">
+ 각 평가 옵션에 따른 점수를 확인할 수 있습니다.
+ </p>
+ </CardHeader>
+ <CardContent>
+ {loading ? (
+ <div className="flex justify-center items-center py-8">
+ <Loader2 className="h-4 w-4 animate-spin mr-2" />
+ <span className="text-sm text-muted-foreground">로딩 중...</span>
+ </div>
+ ) : error ? (
+ <div className="flex justify-center items-center py-8">
+ <div className="text-sm text-destructive">{error}</div>
+ </div>
+ ) : details.length === 0 ? (
+ <div className="flex justify-center items-center py-8">
+ <div className="text-sm text-muted-foreground">등록된 평가 옵션이 없습니다.</div>
+ </div>
+ ) : (
+ <div className="border rounded-lg">
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead className="w-12">#</TableHead>
+ <TableHead className="min-w-[200px]">평가 옵션</TableHead>
+ <TableHead className="text-center w-24">기자재-조선</TableHead>
+ <TableHead className="text-center w-24">기자재-해양</TableHead>
+ <TableHead className="text-center w-24">벌크-조선</TableHead>
+ <TableHead className="text-center w-24">벌크-해양</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {details.map((detail, index) => (
+ <TableRow key={detail.id}>
+ <TableCell className="font-medium">
+ {(detail.orderIndex ?? index) + 1}
+ </TableCell>
+ <TableCell className="font-medium">
+ {detail.detail}
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreEquipShip)}
+ </Badge>
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreEquipMarine)}
+ </Badge>
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreBulkShip)}
+ </Badge>
+ </TableCell>
+ <TableCell className="text-center">
+ <Badge variant="outline" className="font-mono">
+ {formatScore(detail.scoreBulkMarine)}
+ </Badge>
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </div>
+ )}
+ </CardContent>
+ </Card>
+
+ </div>
+ </ScrollArea>
+ </SheetContent>
+ </Sheet>
+ );
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+/* EXPORT */
+export default RegEvalCriteriaDetailsSheet; \ No newline at end of file