diff options
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.tsx | 226 |
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 |
