diff options
Diffstat (limited to 'lib/vendors/table/vendors-table-columns.tsx')
| -rw-r--r-- | lib/vendors/table/vendors-table-columns.tsx | 195 |
1 files changed, 180 insertions, 15 deletions
diff --git a/lib/vendors/table/vendors-table-columns.tsx b/lib/vendors/table/vendors-table-columns.tsx index 0a5f066f..f2de179c 100644 --- a/lib/vendors/table/vendors-table-columns.tsx +++ b/lib/vendors/table/vendors-table-columns.tsx @@ -24,10 +24,17 @@ import { DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header" import { useRouter } from "next/navigation" -import { VendorWithType, vendors, VendorWithAttachments } from "@/db/schema/vendors" +import { VendorWithTypeAndMaterials, vendors, VendorWithAttachments } from "@/db/schema/vendors" import { modifyVendor } from "../service" import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" import { vendorColumnsConfig } from "@/config/vendorColumnsConfig" @@ -49,22 +56,33 @@ type StatusDisplayMap = { type NextRouter = ReturnType<typeof useRouter>; interface GetColumnsProps { - setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<VendorWithType> | null>>; + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<VendorWithTypeAndMaterials> | null>>; router: NextRouter; userId: number; } +// 권한 체크 헬퍼 함수들 (향후 실제 권한 시스템과 연동) +const checkEditAssociationPermission = (userId: number): boolean => { + // TODO: 실제 권한 체크 로직 구현 + // 예: 특정 역할(ADMIN, VENDOR_MANAGER 등)을 가진 사용자만 수정 가능 + // const userRoles = await getUserRoles(userId); + // return userRoles.includes('VENDOR_MANAGER') || userRoles.includes('ADMIN'); + + // 개발 중에는 모든 사용자가 수정 가능 + return true; +}; + /** * tanstack table 컬럼 정의 (중첩 헤더 버전) */ -export function getColumns({ setRowAction, router, userId }: GetColumnsProps): ColumnDef<VendorWithType>[] { +export function getColumns({ setRowAction, router, userId }: GetColumnsProps): ColumnDef<VendorWithTypeAndMaterials>[] { // ---------------------------------------------------------------- // 1) select 컬럼 (체크박스) // ---------------------------------------------------------------- - const selectColumn: ColumnDef<VendorWithType> = { + const selectColumn: ColumnDef<VendorWithTypeAndMaterials> = { id: "select", header: ({ table }) => ( <Checkbox @@ -85,7 +103,9 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C className="translate-y-0.5" /> ), - size: 40, + size: 50, + minSize: 50, + maxSize: 50, enableSorting: false, enableHiding: false, } @@ -93,7 +113,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C // ---------------------------------------------------------------- // 2) actions 컬럼 (Dropdown 메뉴) // ---------------------------------------------------------------- - const actionsColumn: ColumnDef<VendorWithType> = { + const actionsColumn: ColumnDef<VendorWithTypeAndMaterials> = { id: "actions", enableHiding: false, cell: function Cell({ row }) { @@ -157,11 +177,11 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C toast.promise( modifyVendor({ id: String(row.original.id), - status: value as VendorWithType["status"], + status: value as any, userId, vendorName: row.original.vendorName, // Required field from UpdateVendorSchema comment: `Status changed to ${value}` - }), + } as any), { loading: "Updating...", success: "Label updated", @@ -190,14 +210,16 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C </DropdownMenu> ) }, - size: 40, + size: 60, + minSize: 60, + maxSize: 60, } // ---------------------------------------------------------------- // 3) 일반 컬럼들을 "그룹"별로 묶어 중첩 columns 생성 // ---------------------------------------------------------------- // 3-1) groupMap: { [groupName]: ColumnDef<VendorWithType>[] } - const groupMap: Record<string, ColumnDef<VendorWithType>[]> = {} + const groupMap: Record<string, ColumnDef<VendorWithTypeAndMaterials>[]> = {} vendorColumnsConfig.forEach((cfg) => { // 만약 group가 없으면 "_noGroup" 처리 @@ -208,7 +230,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C } // child column 정의 - const childCol: ColumnDef<VendorWithType> = { + const childCol: ColumnDef<VendorWithTypeAndMaterials> = { accessorKey: cfg.id, enableResizing: true, header: ({ column }) => ( @@ -219,6 +241,9 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C group: cfg.group, type: cfg.type, }, + size: cfg.width || 150, + minSize: cfg.minWidth || 100, + maxSize: cfg.maxWidth, cell: ({ row, cell }) => { // Status 컬럼 렌더링 개선 - 아이콘과 더 선명한 배경색 사용 if (cfg.id === "status") { @@ -383,10 +408,148 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C return formatDate(dateVal, "KR") } + // 업체대표품목 컬럼들 처리 + if (cfg.id === "primaryMaterial1" || cfg.id === "primaryMaterial2" || cfg.id === "primaryMaterial3") { + const materialVal = cell.getValue() as string | null; + if (!materialVal) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm font-medium whitespace-pre-line max-w-[200px]"> + {materialVal} + </div> + ); + } + + // 성조회가입여부 처리 - 권한에 따라 드롭다운 또는 읽기전용 배지 + if (cfg.id === "isAssociationMember") { + const [isUpdating, setIsUpdating] = React.useState(false); + const memberVal = row.original.isAssociationMember as string | null; + + const getDisplayText = (value: string | null) => { + switch (value) { + case "Y": return "가입"; + case "N": return "미가입"; + case "E": return "해당없음"; + default: return "-"; + } + }; + + const getBadgeStyle = (value: string | null) => { + switch (value) { + case "Y": + return "bg-green-100 text-green-800 border-green-300"; + case "N": + return "bg-red-100 text-red-800 border-red-300"; + case "E": + return "bg-gray-100 text-gray-800 border-gray-300"; + default: + return "bg-gray-100 text-gray-800 border-gray-300"; + } + }; + + // 권한 체크: 성조회가입여부 수정 권한이 있는지 확인 + const hasEditPermission = checkEditAssociationPermission(userId); + + const handleValueChange = async (newValue: string) => { + // "NONE" 값을 null로 변환 + const actualValue = newValue === "NONE" ? null : newValue; + + setIsUpdating(true); + try { + await modifyVendor({ + id: String(row.original.id), + isAssociationMember: actualValue, + userId, + vendorName: row.original.vendorName, + comment: `성조회가입여부 변경: ${getDisplayText(memberVal)} → ${getDisplayText(actualValue)}` + } as any); + + toast.success("성조회가입여부가 업데이트되었습니다."); + } catch (error) { + toast.error("업데이트에 실패했습니다: " + getErrorMessage(error)); + } finally { + setIsUpdating(false); + } + }; + + // 권한이 있는 경우 드롭다운 표시 + if (hasEditPermission) { + return ( + <Select + value={memberVal || "NONE"} + onValueChange={handleValueChange} + disabled={isUpdating} + > + <SelectTrigger className="w-[120px] h-8"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + <SelectItem value="NONE">-</SelectItem> + <SelectItem value="Y">가입</SelectItem> + <SelectItem value="N">미가입</SelectItem> + <SelectItem value="E">해당없음</SelectItem> + </SelectContent> + </Select> + ); + } + + // 권한이 없는 경우 읽기전용 배지 표시 + return ( + <Badge variant="outline" className={getBadgeStyle(memberVal)}> + {getDisplayText(memberVal)} + </Badge> + ); + } + + // 최근 발주 실적 컬럼들 처리 + if (cfg.id === "recentPoNumber") { + const poNumber = cell.getValue() as string | null; + if (!poNumber) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm font-medium max-w-[150px] truncate" title={poNumber}> + {poNumber} + </div> + ); + } + + if (cfg.id === "recentPoOrderBy") { + const orderBy = cell.getValue() as string | null; + if (!orderBy) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm font-medium whitespace-pre-line max-w-[150px]"> + {orderBy} + </div> + ); + } + + if (cfg.id === "recentPoDate") { + const poDate = cell.getValue() as Date | null; + if (!poDate) return <span className="text-gray-400">-</span>; + + return ( + <div className="text-sm"> + {formatDate(poDate, "KR")} + </div> + ); + } + + // TODO 컬럼들 (UI만) - 모두 "-" 표시 + if (cfg.id === "regularEvaluationGrade" || cfg.id === "faContract" || + cfg.id === "avlRegistration" || cfg.id === "regularVendorRegistration" || + cfg.id === "recentDeliveryNumber" || cfg.id === "recentDeliveryBy") { + return <span className="text-gray-400">-</span>; + } + + // 날짜 컬럼들 (TODO) + if (cfg.id === "recentDeliveryDate") { + return <span className="text-gray-400">-</span>; + } + // code etc... return row.getValue(cfg.id) ?? "" }, - minSize: 150 } groupMap[groupName].push(childCol) @@ -395,7 +558,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C // ---------------------------------------------------------------- // 3-2) groupMap에서 실제 상위 컬럼(그룹)을 만들기 // ---------------------------------------------------------------- - const nestedColumns: ColumnDef<VendorWithType>[] = [] + const nestedColumns: ColumnDef<VendorWithTypeAndMaterials>[] = [] // 순서를 고정하고 싶다면 group 순서를 미리 정의하거나 sort해야 함 // 여기서는 그냥 Object.entries 순서 @@ -414,7 +577,7 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C }) // attachments 컬럼 타입 문제 해결을 위한 타입 단언 - const attachmentsColumn: ColumnDef<VendorWithType> = { + const attachmentsColumn: ColumnDef<VendorWithTypeAndMaterials> = { id: "attachments", header: ({ column }) => ( <DataTableColumnHeaderSimple column={column} title="" /> @@ -441,7 +604,9 @@ export function getColumns({ setRowAction, router, userId }: GetColumnsProps): C }, enableSorting: false, enableHiding: false, - minSize: 45, + size: 50, + minSize: 50, + maxSize: 50, }; |
