diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:31 +0000 |
| commit | aa86729f9a2ab95346a2851e3837de1c367aae17 (patch) | |
| tree | b601b18b6724f2fb449c7fa9ea50cbd652a8077d /lib/evaluation-target-list/table/evaluation-targets-columns.tsx | |
| parent | 95bbe9c583ff841220da1267630e7b2025fc36dc (diff) | |
(대표님) 20250620 작업사항
Diffstat (limited to 'lib/evaluation-target-list/table/evaluation-targets-columns.tsx')
| -rw-r--r-- | lib/evaluation-target-list/table/evaluation-targets-columns.tsx | 459 |
1 files changed, 322 insertions, 137 deletions
diff --git a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx index b1e19434..93807ef9 100644 --- a/lib/evaluation-target-list/table/evaluation-targets-columns.tsx +++ b/lib/evaluation-target-list/table/evaluation-targets-columns.tsx @@ -7,6 +7,11 @@ import { Button } from "@/components/ui/button"; import { Pencil, Eye, MessageSquare, Check, X } from "lucide-react"; import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"; import { EvaluationTargetWithDepartments } from "@/db/schema"; +import { EditEvaluationTargetSheet } from "./update-evaluation-target"; + +interface GetColumnsProps { + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<EvaluationTargetWithDepartments> | null>>; +} // 상태별 색상 매핑 const getStatusBadgeVariant = (status: string) => { @@ -36,8 +41,8 @@ const getConsensusBadge = (consensusStatus: boolean | null) => { // 구분 배지 const getDivisionBadge = (division: string) => { return ( - <Badge variant={division === "OCEAN" ? "default" : "secondary"}> - {division === "OCEAN" ? "해양" : "조선"} + <Badge variant={division === "PLANT" ? "default" : "secondary"}> + {division === "PLANT" ? "해양" : "조선"} </Badge> ); }; @@ -46,7 +51,7 @@ const getDivisionBadge = (division: string) => { const getMaterialTypeBadge = (materialType: string) => { const typeMap = { EQUIPMENT: "기자재", - BULK: "벌크", + BULK: "벌크", EQUIPMENT_BULK: "기자재/벌크" }; return <Badge variant="outline">{typeMap[materialType] || materialType}</Badge>; @@ -61,8 +66,23 @@ const getDomesticForeignBadge = (domesticForeign: string) => { ); }; -export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDepartments>[] { +// 평가 상태 배지 +const getApprovalBadge = (isApproved: boolean | null) => { + if (isApproved === null) { + return <Badge variant="outline" className="text-xs">대기중</Badge>; + } + if (isApproved === true) { + return <Badge variant="default" className="bg-green-600 text-xs">승인</Badge>; + } + return <Badge variant="destructive" className="text-xs">거부</Badge>; +}; + +export function getEvaluationTargetsColumns({setRowAction}:GetColumnsProps): ColumnDef<EvaluationTargetWithDepartments>[] { return [ + // ═══════════════════════════════════════════════════════════════ + // 기본 정보 + // ═══════════════════════════════════════════════════════════════ + // Checkbox { id: "select", @@ -102,46 +122,6 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep cell: ({ row }) => getDivisionBadge(row.getValue("division")), size: 80, }, - - // ░░░ 벤더 코드 ░░░ - { - accessorKey: "vendorCode", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더 코드" />, - cell: ({ row }) => ( - <span className="font-mono text-sm">{row.getValue("vendorCode")}</span> - ), - size: 120, - }, - - // ░░░ 벤더명 ░░░ - { - accessorKey: "vendorName", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더명" />, - cell: ({ row }) => ( - <div className="truncate max-w-[200px]" title={row.getValue<string>("vendorName")!}> - {row.getValue("vendorName") as string} - </div> - ), - size: 200, - }, - - // ░░░ 내외자 ░░░ - { - accessorKey: "domesticForeign", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="내외자" />, - cell: ({ row }) => getDomesticForeignBadge(row.getValue("domesticForeign")), - size: 80, - }, - - // ░░░ 자재구분 ░░░ - { - accessorKey: "materialType", - header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재구분" />, - cell: ({ row }) => getMaterialTypeBadge(row.getValue("materialType")), - size: 120, - }, - - // ░░░ 상태 ░░░ { accessorKey: "status", header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="상태" />, @@ -161,6 +141,54 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep size: 100, }, + // ░░░ 벤더 코드 ░░░ + + { + header: "협력업체 정보", + columns: [ + { + accessorKey: "vendorCode", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더 코드" />, + cell: ({ row }) => ( + <span className="font-mono text-sm">{row.getValue("vendorCode")}</span> + ), + size: 120, + }, + + // ░░░ 벤더명 ░░░ + { + accessorKey: "vendorName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="벤더명" />, + cell: ({ row }) => ( + <div className="truncate max-w-[200px]" title={row.getValue<string>("vendorName")!}> + {row.getValue("vendorName") as string} + </div> + ), + size: 200, + }, + + // ░░░ 내외자 ░░░ + { + accessorKey: "domesticForeign", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="내외자" />, + cell: ({ row }) => getDomesticForeignBadge(row.getValue("domesticForeign")), + size: 80, + }, + + ] + }, + + // ░░░ 자재구분 ░░░ + { + accessorKey: "materialType", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="자재구분" />, + cell: ({ row }) => getMaterialTypeBadge(row.getValue("materialType")), + size: 120, + }, + + // ░░░ 상태 ░░░ + + // ░░░ 의견 일치 여부 ░░░ { accessorKey: "consensusStatus", @@ -169,56 +197,235 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep size: 100, }, - // ░░░ 담당자 현황 ░░░ + // ═══════════════════════════════════════════════════════════════ + // 주문 부서 그룹 + // ═══════════════════════════════════════════════════════════════ { - id: "reviewers", - header: "담당자 현황", - cell: ({ row }) => { - const reviewers = row.original.reviewers || []; - const totalReviewers = reviewers.length; - const completedReviews = reviewers.filter(r => r.review?.isApproved !== null).length; - const approvedReviews = reviewers.filter(r => r.review?.isApproved === true).length; - - return ( - <div className="flex items-center gap-2"> - <div className="text-xs"> - <span className="text-green-600 font-medium">{approvedReviews}</span> - <span className="text-muted-foreground">/{completedReviews}</span> - <span className="text-muted-foreground">/{totalReviewers}</span> - </div> - {totalReviewers > 0 && ( - <div className="flex gap-1"> - {reviewers.slice(0, 3).map((reviewer, idx) => ( - <div - key={idx} - className={`w-2 h-2 rounded-full ${ - reviewer.review?.isApproved === true - ? "bg-green-500" - : reviewer.review?.isApproved === false - ? "bg-red-500" - : "bg-gray-300" - }`} - title={`${reviewer.departmentCode}: ${ - reviewer.review?.isApproved === true - ? "승인" - : reviewer.review?.isApproved === false - ? "거부" - : "대기중" - }`} - /> - ))} - {totalReviewers > 3 && ( - <span className="text-xs text-muted-foreground">+{totalReviewers - 3}</span> - )} + header: "발주 평가 담당자", + columns: [ + { + accessorKey: "orderDepartmentName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, + cell: ({ row }) => { + const departmentName = row.getValue<string>("orderDepartmentName"); + return departmentName ? ( + <div className="truncate max-w-[120px]" title={departmentName}> + {departmentName} </div> - )} - </div> - ); - }, - size: 120, - enableSorting: false, + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 120, + }, + { + accessorKey: "orderReviewerName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + cell: ({ row }) => { + const reviewerName = row.getValue<string>("orderReviewerName"); + return reviewerName ? ( + <div className="truncate max-w-[100px]" title={reviewerName}> + {reviewerName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 100, + }, + { + accessorKey: "orderIsApproved", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, + cell: ({ row }) => getApprovalBadge(row.getValue("orderIsApproved")), + size: 80, + }, + ], + }, + + // ═══════════════════════════════════════════════════════════════ + // 조달 부서 그룹 + // ═══════════════════════════════════════════════════════════════ + { + header: "조달 평가 담당자", + columns: [ + { + accessorKey: "procurementDepartmentName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, + cell: ({ row }) => { + const departmentName = row.getValue<string>("procurementDepartmentName"); + return departmentName ? ( + <div className="truncate max-w-[120px]" title={departmentName}> + {departmentName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 120, + }, + { + accessorKey: "procurementReviewerName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + cell: ({ row }) => { + const reviewerName = row.getValue<string>("procurementReviewerName"); + return reviewerName ? ( + <div className="truncate max-w-[100px]" title={reviewerName}> + {reviewerName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 100, + }, + { + accessorKey: "procurementIsApproved", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, + cell: ({ row }) => getApprovalBadge(row.getValue("procurementIsApproved")), + size: 80, + }, + ], }, + // ═══════════════════════════════════════════════════════════════ + // 품질 부서 그룹 + // ═══════════════════════════════════════════════════════════════ + { + header: "품질 평가 담당자", + columns: [ + { + accessorKey: "qualityDepartmentName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, + cell: ({ row }) => { + const departmentName = row.getValue<string>("qualityDepartmentName"); + return departmentName ? ( + <div className="truncate max-w-[120px]" title={departmentName}> + {departmentName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 120, + }, + { + accessorKey: "qualityReviewerName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + cell: ({ row }) => { + const reviewerName = row.getValue<string>("qualityReviewerName"); + return reviewerName ? ( + <div className="truncate max-w-[100px]" title={reviewerName}> + {reviewerName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 100, + }, + { + accessorKey: "qualityIsApproved", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, + cell: ({ row }) => getApprovalBadge(row.getValue("qualityIsApproved")), + size: 80, + }, + ], + }, + + // ═══════════════════════════════════════════════════════════════ + // 설계 부서 그룹 + // ═══════════════════════════════════════════════════════════════ + { + header: "설계 평가 담당자", + columns: [ + { + accessorKey: "designDepartmentName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, + cell: ({ row }) => { + const departmentName = row.getValue<string>("designDepartmentName"); + return departmentName ? ( + <div className="truncate max-w-[120px]" title={departmentName}> + {departmentName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 120, + }, + { + accessorKey: "designReviewerName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + cell: ({ row }) => { + const reviewerName = row.getValue<string>("designReviewerName"); + return reviewerName ? ( + <div className="truncate max-w-[100px]" title={reviewerName}> + {reviewerName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 100, + }, + { + accessorKey: "designIsApproved", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, + cell: ({ row }) => getApprovalBadge(row.getValue("designIsApproved")), + size: 80, + }, + ], + }, + + // ═══════════════════════════════════════════════════════════════ + // CS 부서 그룹 + // ═══════════════════════════════════════════════════════════════ + { + header: "CS 평가 담당자", + columns: [ + { + accessorKey: "csDepartmentName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="부서명" />, + cell: ({ row }) => { + const departmentName = row.getValue<string>("csDepartmentName"); + return departmentName ? ( + <div className="truncate max-w-[120px]" title={departmentName}> + {departmentName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 120, + }, + { + accessorKey: "csReviewerName", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="담당자" />, + cell: ({ row }) => { + const reviewerName = row.getValue<string>("csReviewerName"); + return reviewerName ? ( + <div className="truncate max-w-[100px]" title={reviewerName}> + {reviewerName} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 100, + }, + { + accessorKey: "csIsApproved", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="평가" />, + cell: ({ row }) => getApprovalBadge(row.getValue("csIsApproved")), + size: 80, + }, + ], + }, + + // ═══════════════════════════════════════════════════════════════ + // 관리 정보 + // ═══════════════════════════════════════════════════════════════ + // ░░░ 관리자 의견 ░░░ { accessorKey: "adminComment", @@ -274,69 +481,47 @@ export function getEvaluationTargetsColumns(): ColumnDef<EvaluationTargetWithDep size: 100, }, + // ░░░ 생성일 ░░░ + { + accessorKey: "createdAt", + header: ({ column }) => <DataTableColumnHeaderSimple column={column} title="생성일" />, + cell: ({ row }) => { + const createdAt = row.getValue<Date>("createdAt"); + return createdAt ? ( + <span className="text-sm"> + {new Intl.DateTimeFormat("ko-KR", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }).format(new Date(createdAt))} + </span> + ) : ( + <span className="text-muted-foreground">-</span> + ); + }, + size: 100, + }, + // ░░░ Actions ░░░ { id: "actions", enableHiding: false, - size: 120, - minSize: 120, + size: 40, + minSize: 40, cell: ({ row }) => { - const record = row.original; - const [openDetail, setOpenDetail] = React.useState(false); - const [openEdit, setOpenEdit] = React.useState(false); - const [openRequest, setOpenRequest] = React.useState(false); - - return ( + return ( <div className="flex items-center gap-1"> <Button variant="ghost" size="icon" className="size-8" - onClick={() => setOpenDetail(true)} - aria-label="상세보기" - title="상세보기" - > - <Eye className="size-4" /> - </Button> - - <Button - variant="ghost" - size="icon" - className="size-8" - onClick={() => setOpenEdit(true)} + onClick={() => setRowAction({ row, type: "update" })} aria-label="수정" title="수정" > <Pencil className="size-4" /> </Button> - <Button - variant="ghost" - size="icon" - className="size-8" - onClick={() => setOpenRequest(true)} - aria-label="의견요청" - title="의견요청" - > - <MessageSquare className="size-4" /> - </Button> - - {/* TODO: 실제 다이얼로그 컴포넌트들로 교체 */} - {openDetail && ( - <div onClick={() => setOpenDetail(false)}> - {/* <EvaluationTargetDetailDialog /> */} - </div> - )} - {openEdit && ( - <div onClick={() => setOpenEdit(false)}> - {/* <EditEvaluationTargetDialog /> */} - </div> - )} - {openRequest && ( - <div onClick={() => setOpenRequest(false)}> - {/* <RequestReviewDialog /> */} - </div> - )} </div> ); }, |
