summaryrefslogtreecommitdiff
path: root/lib/vendor-evaluation-submit/table/evaluation-submissions-table-columns.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-evaluation-submit/table/evaluation-submissions-table-columns.tsx')
-rw-r--r--lib/vendor-evaluation-submit/table/evaluation-submissions-table-columns.tsx641
1 files changed, 641 insertions, 0 deletions
diff --git a/lib/vendor-evaluation-submit/table/evaluation-submissions-table-columns.tsx b/lib/vendor-evaluation-submit/table/evaluation-submissions-table-columns.tsx
new file mode 100644
index 00000000..869839cb
--- /dev/null
+++ b/lib/vendor-evaluation-submit/table/evaluation-submissions-table-columns.tsx
@@ -0,0 +1,641 @@
+"use client"
+
+import * as React from "react"
+import { type DataTableRowAction } from "@/types/table"
+import { type ColumnDef } from "@tanstack/react-table"
+import {
+ Ellipsis,
+ InfoIcon,
+ PenToolIcon,
+ FileTextIcon,
+ ClipboardListIcon,
+ DownloadIcon,
+ CheckIcon,
+ XIcon,
+ ClockIcon,
+ Send
+} from "lucide-react"
+
+import { formatDate, formatCurrency } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { Badge } from "@/components/ui/badge"
+
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { EvaluationSubmissionWithVendor } from "../service"
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<EvaluationSubmissionWithVendor> | null>>
+}
+
+/**
+ * 제출 상태에 따른 배지 스타일 및 아이콘
+ */
+const getStatusBadge = (status: string) => {
+ switch (status) {
+ case 'draft':
+ return {
+ variant: "secondary" as const,
+ icon: <ClockIcon className="h-3 w-3" />,
+ label: "임시저장"
+ }
+ case 'submitted':
+ return {
+ variant: "default" as const,
+ icon: <FileTextIcon className="h-3 w-3" />,
+ label: "제출완료"
+ }
+ case 'under_review':
+ return {
+ variant: "outline" as const,
+ icon: <ClipboardListIcon className="h-3 w-3" />,
+ label: "검토중"
+ }
+ case 'approved':
+ return {
+ variant: "default" as const,
+ icon: <CheckIcon className="h-3 w-3" />,
+ label: "승인",
+ className: "bg-green-100 text-green-800 border-green-200"
+ }
+ case 'rejected':
+ return {
+ variant: "destructive" as const,
+ icon: <XIcon className="h-3 w-3" />,
+ label: "반려"
+ }
+ default:
+ return {
+ variant: "secondary" as const,
+ icon: null,
+ label: status
+ }
+ }
+}
+
+/**
+ * 평가 제출 테이블 컬럼 정의
+ */
+export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<EvaluationSubmissionWithVendor>[] {
+
+ // ----------------------------------------------------------------
+ // 1) select 컬럼 (체크박스)
+ // ----------------------------------------------------------------
+ const selectColumn: ColumnDef<EvaluationSubmissionWithVendor> = {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ className="translate-y-0.5"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="Select row"
+ className="translate-y-0.5"
+ />
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ size: 40,
+ }
+
+ // ----------------------------------------------------------------
+ // 2) 기본 정보 컬럼들
+ // ----------------------------------------------------------------
+ const basicColumns: ColumnDef<EvaluationSubmissionWithVendor>[] = [
+ // {
+ // accessorKey: "submissionId",
+ // header: ({ column }) => (
+ // <DataTableColumnHeaderSimple column={column} title="제출 ID" />
+ // ),
+ // cell: ({ row }) => (
+ // <div className="font-mono text-sm">
+ // {row.getValue("submissionId")}
+ // </div>
+ // ),
+ // enableSorting: true,
+ // enableHiding: true,
+ // size: 400,
+ // minSize: 400,
+ // },
+
+ {
+ id: "vendorInfo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="협력업체" />
+ ),
+ cell: ({ row }) => {
+ const vendor = row.original.vendor;
+ return (
+ <div className="space-y-1">
+ <div className="font-medium">{vendor.vendorName}</div>
+ <div className="text-sm text-muted-foreground">
+ {vendor.vendorCode} • {vendor.countryCode}
+ </div>
+ </div>
+ );
+ },
+ enableSorting: false,
+ size: 200,
+ },
+
+ {
+ accessorKey: "evaluationYear",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="평가연도" />
+ ),
+ cell: ({ row }) => (
+ <Badge variant="outline">
+ {row.getValue("evaluationYear")}년
+ </Badge>
+ ),
+ size: 60,
+ },
+
+ {
+ accessorKey: "evaluationRound",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="평가회차" />
+ ),
+ cell: ({ row }) => {
+ const round = row.getValue("evaluationRound") as string;
+ return round ? (
+ <Badge variant="secondary">{round}</Badge>
+ ) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 60,
+ },
+ ]
+
+ // ----------------------------------------------------------------
+ // 3) 상태 정보 컬럼들
+ // ----------------------------------------------------------------
+ const statusColumns: ColumnDef<EvaluationSubmissionWithVendor>[] = [
+ {
+ accessorKey: "submissionStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="제출상태" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("submissionStatus") as string;
+ const badgeInfo = getStatusBadge(status);
+
+ return (
+ <Badge
+ variant={badgeInfo.variant}
+ className={`flex items-center gap-1 ${badgeInfo.className || ''}`}
+ >
+ {badgeInfo.icon}
+ {badgeInfo.label}
+ </Badge>
+ );
+ },
+ size: 120,
+ },
+
+ {
+ accessorKey: "submittedAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="제출일시" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("submittedAt") as Date;
+ return date ? formatDate(date) : (
+ <span className="text-muted-foreground">-</span>
+ );
+ },
+ size: 140,
+ },
+
+ {
+ id: "reviewInfo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="검토정보" />
+ ),
+ cell: ({ row }) => {
+ const reviewedAt = row.original.reviewedAt;
+ const reviewedBy = row.original.reviewedBy;
+
+ if (!reviewedAt) {
+ return <span className="text-muted-foreground">미검토</span>;
+ }
+
+ return (
+ <div className="space-y-1">
+ <div className="text-sm">{formatDate(reviewedAt)}</div>
+ {reviewedBy && (
+ <div className="text-xs text-muted-foreground">{reviewedBy}</div>
+ )}
+ </div>
+ );
+ },
+ enableSorting: false,
+ size: 140,
+ },
+ ]
+
+ // ----------------------------------------------------------------
+ // 4) 점수 및 통계 컬럼들
+ // ----------------------------------------------------------------
+ const scoreColumns: ColumnDef<EvaluationSubmissionWithVendor>[] = [
+ {
+ id: "generalProgress",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="일반평가" />
+ ),
+ cell: ({ row }) => {
+ const totalItems = row.original.totalGeneralItems || 0;
+ const completedItems = row.original.completedGeneralItems || 0;
+ const completionRate = totalItems > 0 ? (completedItems / totalItems) * 100 : 0;
+
+ return (
+ <div className="text-center space-y-1">
+ {/* ❌ 점수 표시 제거 */}
+ <div className="font-medium">
+ {completionRate === 100 ? "완료" : "진행중"}
+ </div>
+ <div className="flex items-center gap-1">
+ <Badge variant="outline" className="text-xs">
+ {completedItems}/{totalItems}개
+ </Badge>
+ {completionRate > 0 && (
+ <span className="text-xs text-muted-foreground">
+ ({completionRate.toFixed(0)}%)
+ </span>
+ )}
+ </div>
+ {/* 📊 진행률 바 */}
+ <div className="w-full bg-gray-200 rounded-full h-1">
+ <div
+ className={`h-1 rounded-full transition-all duration-300 ${
+ completionRate === 100
+ ? 'bg-green-500'
+ : completionRate >= 50
+ ? 'bg-blue-500'
+ : 'bg-yellow-500'
+ }`}
+ style={{ width: `${completionRate}%` }}
+ />
+ </div>
+ </div>
+ );
+ },
+ enableSorting: false,
+ size: 120,
+ },
+
+ {
+ id: "esgScore",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="ESG평가" />
+ ),
+ cell: ({ row }) => {
+ const averageScore = row.original.averageEsgScore;
+ const totalItems = row.original.totalEsgItems || 0;
+ const completedItems = row.original.completedEsgItems || 0;
+ const completionRate = totalItems > 0 ? (completedItems / totalItems) * 100 : 0;
+ const isKorean = row.original.vendor.countryCode === 'KR';
+
+ if (!isKorean) {
+ return (
+ <div className="text-center text-muted-foreground">
+ <Badge variant="outline">해당없음</Badge>
+ </div>
+ );
+ }
+
+ return (
+ <div className="text-center space-y-1">
+ {/* ✅ ESG는 평균점수 표시 */}
+ <div className="font-medium">
+ {averageScore ? (
+ <span className="text-blue-600">
+ 평균 {parseFloat(averageScore.toString()).toFixed(1)}점
+ </span>
+ ) : (
+ <span className="text-muted-foreground">미완료</span>
+ )}
+ </div>
+ <div className="flex items-center gap-1">
+ <Badge variant="outline" className="text-xs">
+ {completedItems}/{totalItems}개
+ </Badge>
+ {completionRate > 0 && (
+ <span className="text-xs text-muted-foreground">
+ ({completionRate.toFixed(0)}%)
+ </span>
+ )}
+ </div>
+ {/* 📊 진행률 바 */}
+ <div className="w-full bg-gray-200 rounded-full h-1">
+ <div
+ className={`h-1 rounded-full transition-all duration-300 ${
+ completionRate === 100
+ ? 'bg-green-500'
+ : completionRate >= 50
+ ? 'bg-blue-500'
+ : 'bg-yellow-500'
+ }`}
+ style={{ width: `${completionRate}%` }}
+ />
+ </div>
+ </div>
+ );
+ },
+ enableSorting: false,
+ size: 140,
+ },
+
+ {
+ id: "overallProgress",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="전체 진행률" />
+ ),
+ cell: ({ row }) => {
+ const totalGeneral = row.original.totalGeneralItems || 0;
+ const completedGeneral = row.original.completedGeneralItems || 0;
+ const totalEsg = row.original.totalEsgItems || 0;
+ const completedEsg = row.original.completedEsgItems || 0;
+ const isKorean = row.original.vendor.countryCode === 'KR';
+
+ const totalItems = totalGeneral + (isKorean ? totalEsg : 0);
+ const completedItems = completedGeneral + (isKorean ? completedEsg : 0);
+ const completionRate = totalItems > 0 ? (completedItems / totalItems) * 100 : 0;
+
+ return (
+ <div className="text-center space-y-2">
+ <div className="w-full bg-gray-200 rounded-full h-2">
+ <div
+ className={`h-2 rounded-full transition-all duration-300 ${
+ completionRate === 100
+ ? 'bg-green-500'
+ : completionRate >= 50
+ ? 'bg-blue-500'
+ : 'bg-yellow-500'
+ }`}
+ style={{ width: `${completionRate}%` }}
+ />
+ </div>
+ <div className="text-xs space-y-1">
+ <div className="font-medium">
+ {completionRate.toFixed(0)}% 완료
+ </div>
+ <div className="text-muted-foreground">
+ {completedItems}/{totalItems}개 항목
+ </div>
+ </div>
+ </div>
+ );
+ },
+ enableSorting: false,
+ size: 120,
+ },
+
+ {
+ id: "attachments",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="첨부파일" />
+ ),
+ cell: ({ row }) => {
+ const count = row.original._count.attachments;
+
+ return (
+ <div className="text-center">
+ <Badge variant="outline">
+ {count}개 파일
+ </Badge>
+ </div>
+ );
+ },
+ enableSorting: false,
+ size: 100,
+ },
+ ]
+
+
+ // ----------------------------------------------------------------
+ // 5) 메타데이터 컬럼들
+ // ----------------------------------------------------------------
+ const metaColumns: ColumnDef<EvaluationSubmissionWithVendor>[] = [
+ {
+ accessorKey: "createdAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="생성일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("createdAt") as Date;
+ return formatDate(date);
+ },
+ size: 140,
+ },
+ {
+ accessorKey: "updatedAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="수정일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("updatedAt") as Date;
+ return formatDate(date);
+ },
+ size: 140,
+ },
+ ]
+
+ // ----------------------------------------------------------------
+ // 6) actions 컬럼 (드롭다운 메뉴)
+ // ----------------------------------------------------------------
+ const actionsColumn: ColumnDef<EvaluationSubmissionWithVendor> = {
+ id: "actions",
+ header: "작업",
+ enableHiding: false,
+ cell: function Cell({ row }) {
+ const status = row.original.submissionStatus;
+ const isKorean = row.original.vendor.countryCode === 'KR';
+
+ return (
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" size="icon">
+ <Ellipsis className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "general_evaluation" })}
+ >
+ <FileTextIcon className="mr-2 h-4 w-4" />
+ 일반평가 작성
+ </DropdownMenuItem>
+
+ {isKorean && (
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "esg_evaluation" })}
+ >
+ <ClipboardListIcon className="mr-2 h-4 w-4" />
+ ESG평가 작성
+ </DropdownMenuItem>
+ )}
+
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ onClick={() => setRowAction({ row, type: "submit" })}
+ >
+ <Send className="mr-2 h-4 w-4" />
+ 제출
+ </DropdownMenuItem>
+
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )
+ },
+ size: 80,
+ }
+
+ // ----------------------------------------------------------------
+ // 7) 최종 컬럼 배열 (그룹화 버전)
+ // ----------------------------------------------------------------
+ return [
+ selectColumn,
+ {
+ id: "basicInfo",
+ header: "기본 정보",
+ columns: basicColumns,
+ },
+ {
+ id: "statusInfo",
+ header: "상태 정보",
+ columns: statusColumns,
+ },
+ {
+ id: "scoreInfo",
+ header: "점수 및 통계",
+ columns: scoreColumns,
+ },
+ {
+ id: "metadata",
+ header: "메타데이터",
+ columns: metaColumns,
+ },
+ actionsColumn,
+ ]
+}
+
+// ----------------------------------------------------------------
+// 8) 컬럼 설정 (필터링용)
+// ----------------------------------------------------------------
+export const evaluationSubmissionsColumnsConfig = [
+ {
+ id: "submissionId",
+ label: "제출 ID",
+ group: "기본 정보",
+ type: "text",
+ excelHeader: "Submission ID",
+ },
+ {
+ id: "vendorName",
+ label: "협력업체명",
+ group: "기본 정보",
+ type: "text",
+ excelHeader: "Vendor Name",
+ },
+ {
+ id: "vendorCode",
+ label: "협력업체 코드",
+ group: "기본 정보",
+ type: "text",
+ excelHeader: "Vendor Code",
+ },
+ {
+ id: "evaluationYear",
+ label: "평가연도",
+ group: "기본 정보",
+ type: "number",
+ excelHeader: "Evaluation Year",
+ },
+ {
+ id: "evaluationRound",
+ label: "평가회차",
+ group: "기본 정보",
+ type: "text",
+ excelHeader: "Evaluation Round",
+ },
+ {
+ id: "submissionStatus",
+ label: "제출상태",
+ group: "상태 정보",
+ type: "select",
+ options: [
+ { label: "임시저장", value: "draft" },
+ { label: "제출완료", value: "submitted" },
+ { label: "검토중", value: "under_review" },
+ { label: "승인", value: "approved" },
+ { label: "반려", value: "rejected" },
+ ],
+ excelHeader: "Submission Status",
+ },
+ {
+ id: "submittedAt",
+ label: "제출일시",
+ group: "상태 정보",
+ type: "date",
+ excelHeader: "Submitted At",
+ },
+ {
+ id: "reviewedAt",
+ label: "검토일시",
+ group: "상태 정보",
+ type: "date",
+ excelHeader: "Reviewed At",
+ },
+ {
+ id: "totalGeneralScore",
+ label: "일반평가 점수",
+ group: "점수 정보",
+ type: "number",
+ excelHeader: "Total General Score",
+ },
+ {
+ id: "totalEsgScore",
+ label: "ESG평가 점수",
+ group: "점수 정보",
+ type: "number",
+ excelHeader: "Total ESG Score",
+ },
+ {
+ id: "createdAt",
+ label: "생성일",
+ group: "메타데이터",
+ type: "date",
+ excelHeader: "Created At",
+ },
+ {
+ id: "updatedAt",
+ label: "수정일",
+ group: "메타데이터",
+ type: "date",
+ excelHeader: "Updated At",
+ },
+] as const; \ No newline at end of file