summaryrefslogtreecommitdiff
path: root/lib/vendor-regular-registrations
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-regular-registrations')
-rw-r--r--lib/vendor-regular-registrations/table/major-items-update-dialog.tsx167
-rw-r--r--lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx632
2 files changed, 360 insertions, 439 deletions
diff --git a/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx b/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx
index 26741a1b..04635d2c 100644
--- a/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx
+++ b/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx
@@ -3,7 +3,6 @@
import * as React from "react"
import { useState } from "react"
import { toast } from "sonner"
-import { X, Plus, Search } from "lucide-react"
import {
Dialog,
@@ -13,18 +12,11 @@ import {
DialogTitle,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Badge } from "@/components/ui/badge"
import { Label } from "@/components/ui/label"
-import { searchItemsForPQ } from "@/lib/items/service"
+import { MaterialGroupSelector } from "@/components/common/material/material-group-selector"
+import { MaterialSearchItem } from "@/lib/material/material-group-service"
import { updateMajorItems } from "../service"
-// PQ 대상 품목 타입 정의
-interface PQItem {
- itemCode: string
- itemName: string
-}
-
interface MajorItemsUpdateDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
@@ -43,12 +35,7 @@ export function MajorItemsUpdateDialog({
onSuccess,
}: MajorItemsUpdateDialogProps) {
const [isLoading, setIsLoading] = useState(false)
- const [selectedItems, setSelectedItems] = useState<PQItem[]>([])
-
- // 아이템 검색 관련 상태
- const [itemSearchQuery, setItemSearchQuery] = useState<string>("")
- const [filteredItems, setFilteredItems] = useState<PQItem[]>([])
- const [showItemDropdown, setShowItemDropdown] = useState<boolean>(false)
+ const [selectedMaterials, setSelectedMaterials] = useState<MaterialSearchItem[]>([])
// 기존 아이템들 파싱 및 초기화
React.useEffect(() => {
@@ -56,65 +43,26 @@ export function MajorItemsUpdateDialog({
try {
const parsedItems = JSON.parse(currentItems)
if (Array.isArray(parsedItems)) {
- setSelectedItems(parsedItems)
+ // materialGroupCode와 materialGroupDescription이 있는 항목들만 변환
+ const materials = parsedItems
+ .filter((item: any) => item.materialGroupCode && item.materialGroupDescription)
+ .map((item: any) => ({
+ materialGroupCode: item.materialGroupCode,
+ materialGroupDescription: item.materialGroupDescription,
+ materialGroupUom: item.materialGroupUom,
+ displayText: `${item.materialGroupCode} - ${item.materialGroupDescription}`,
+ }))
+ setSelectedMaterials(materials)
}
} catch (error) {
console.error("기존 주요품목 파싱 오류:", error)
- setSelectedItems([])
+ setSelectedMaterials([])
}
} else if (open) {
- setSelectedItems([])
+ setSelectedMaterials([])
}
}, [open, currentItems])
- // 아이템 검색 필터링
- React.useEffect(() => {
- if (itemSearchQuery.trim() === "") {
- setFilteredItems([])
- setShowItemDropdown(false)
- return
- }
-
- const searchItems = async () => {
- try {
- const results = await searchItemsForPQ(itemSearchQuery)
- setFilteredItems(results)
- setShowItemDropdown(true)
- } catch (error) {
- console.error("아이템 검색 오류:", error)
- toast.error("아이템 검색 중 오류가 발생했습니다.")
- setFilteredItems([])
- setShowItemDropdown(false)
- }
- }
-
- // 디바운싱: 300ms 후에 검색 실행
- const timeoutId = setTimeout(searchItems, 300)
- return () => clearTimeout(timeoutId)
- }, [itemSearchQuery])
-
- // 아이템 선택 함수
- const handleSelectItem = (item: PQItem) => {
- // 이미 선택된 아이템인지 확인
- const isAlreadySelected = selectedItems.some(selectedItem =>
- selectedItem.itemCode === item.itemCode
- )
-
- if (!isAlreadySelected) {
- setSelectedItems(prev => [...prev, item])
- }
-
- // 검색 초기화
- setItemSearchQuery("")
- setFilteredItems([])
- setShowItemDropdown(false)
- }
-
- // 아이템 제거 함수
- const handleRemoveItem = (itemCode: string) => {
- setSelectedItems(prev => prev.filter(item => item.itemCode !== itemCode))
- }
-
const handleSave = async () => {
if (!registrationId) {
toast.error("등록 ID가 없습니다.")
@@ -123,9 +71,16 @@ export function MajorItemsUpdateDialog({
setIsLoading(true)
try {
+ // MaterialSearchItem을 DB 저장 형식으로 변환
+ const itemsToSave = selectedMaterials.map(material => ({
+ materialGroupCode: material.materialGroupCode,
+ materialGroupDescription: material.materialGroupDescription,
+ materialGroupUom: material.materialGroupUom,
+ }))
+
const result = await updateMajorItems(
registrationId,
- JSON.stringify(selectedItems)
+ JSON.stringify(itemsToSave)
)
if (result.success) {
@@ -153,72 +108,20 @@ export function MajorItemsUpdateDialog({
</DialogDescription>
</DialogHeader>
- <div className="space-y-6 mt-6">
- {/* 선택된 아이템들 표시 */}
- {selectedItems.length > 0 && (
- <div className="space-y-2">
- <Label>선택된 주요품목 ({selectedItems.length}개)</Label>
- <div className="flex flex-wrap gap-2 max-h-40 overflow-y-auto border rounded-md p-3">
- {selectedItems.map((item) => (
- <Badge key={item.itemCode} variant="secondary" className="flex items-center gap-1">
- <span className="text-xs">
- {item.itemCode} - {item.itemName}
- </span>
- <Button
- type="button"
- variant="ghost"
- size="sm"
- className="h-4 w-4 p-0 hover:bg-destructive hover:text-destructive-foreground"
- onClick={() => handleRemoveItem(item.itemCode)}
- >
- <X className="h-3 w-3" />
- </Button>
- </Badge>
- ))}
- </div>
- </div>
- )}
-
- {/* 검색 입력 */}
+ <div className="space-y-4 mt-6">
<div className="space-y-2">
- <Label>품목 검색 및 추가</Label>
- <div className="relative">
- <div className="relative">
- <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
- <Input
- placeholder="아이템 코드 또는 이름으로 검색하세요"
- value={itemSearchQuery}
- onChange={(e) => setItemSearchQuery(e.target.value)}
- className="pl-9"
- />
- </div>
-
- {/* 검색 결과 드롭다운 */}
- {showItemDropdown && (
- <div className="absolute top-full left-0 right-0 z-50 mt-1 max-h-48 overflow-y-auto bg-background border rounded-md shadow-lg">
- {filteredItems.length > 0 ? (
- filteredItems.map((item) => (
- <button
- key={item.itemCode}
- type="button"
- className="w-full px-3 py-2 text-left text-sm hover:bg-muted focus:bg-muted focus:outline-none"
- onClick={() => handleSelectItem(item)}
- >
- <div className="font-medium">{item.itemCode}</div>
- <div className="text-muted-foreground text-xs">{item.itemName}</div>
- </button>
- ))
- ) : (
- <div className="px-3 py-2 text-sm text-muted-foreground">
- 검색 결과가 없습니다.
- </div>
- )}
- </div>
- )}
- </div>
-
+ <Label>자재그룹 검색 및 선택</Label>
+ <MaterialGroupSelector
+ selectedMaterials={selectedMaterials}
+ onMaterialsChange={setSelectedMaterials}
+ singleSelect={false}
+ placeholder="자재그룹을 검색하세요..."
+ noValuePlaceHolder="자재그룹을 선택해주세요"
+ closeOnSelect={false}
+ showInitialData={true}
+ />
<div className="text-xs text-muted-foreground">
- 아이템 코드나 이름을 입력하여 검색하고 선택하세요.
+ 선택됨: {selectedMaterials.length}개
</div>
</div>
</div>
diff --git a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx
index b6f9289f..cd748fd8 100644
--- a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx
+++ b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx
@@ -1,307 +1,325 @@
-"use client"
-
-import { type ColumnDef } from "@tanstack/react-table"
-import { Checkbox } from "@/components/ui/checkbox"
-import { Badge } from "@/components/ui/badge"
-import { format } from "date-fns"
-
-import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
-import { VendorRegularRegistration, statusLabels, statusColors } from "@/config/vendorRegularRegistrationsColumnsConfig"
-import { DocumentStatusDialog } from "@/components/vendor-regular-registrations/document-status-dialog"
-import { Button } from "@/components/ui/button"
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
-import { Eye, FileText, Ellipsis, Shield, Package } from "lucide-react"
-import { toast } from "sonner"
-import { useState } from "react"
-import { SafetyQualificationUpdateDialog } from "./safety-qualification-update-dialog"
-import { MajorItemsUpdateDialog } from "./major-items-update-dialog"
-
-export function getColumns(): ColumnDef<VendorRegularRegistration>[] {
-
- return [
- {
- id: "select",
- header: ({ table }) => (
- <Checkbox
- checked={
- table.getIsAllPageRowsSelected() ||
- (table.getIsSomePageRowsSelected() && "indeterminate")
- }
- onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
- aria-label="Select all"
- className="translate-y-[2px]"
- />
- ),
- cell: ({ row }) => (
- <Checkbox
- checked={row.getIsSelected()}
- onCheckedChange={(value) => row.toggleSelected(!!value)}
- aria-label="Select row"
- className="translate-y-[2px]"
- />
- ),
- enableSorting: false,
- enableHiding: false,
- },
- {
- accessorKey: "status",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="Status" />
- ),
- cell: ({ row }) => {
- const status = row.getValue("status") as string
- return (
- <Badge
- variant="secondary"
- className={statusColors[status as keyof typeof statusColors]}
- >
- {statusLabels[status as keyof typeof statusLabels] || status}
- </Badge>
- )
- },
- filterFn: (row, id, value) => {
- return Array.isArray(value) && value.includes(row.getValue(id))
- },
- },
- {
- accessorKey: "potentialCode",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="잠재코드" />
- ),
- cell: ({ row }) => row.getValue("potentialCode") || "-",
- },
- {
- accessorKey: "businessNumber",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="사업자번호" />
- ),
- },
- {
- accessorKey: "companyName",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="업체명" />
- ),
- },
- {
- accessorKey: "majorItems",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="주요품목" />
- ),
- cell: ({ row }) => {
- const majorItems = row.getValue("majorItems") as string
- try {
- const items = majorItems ? JSON.parse(majorItems) : []
- if (items.length === 0) return "-"
-
- // 첫 번째 아이템을 itemCode-itemName 형태로 표시
- const firstItem = items[0]
- let displayText = ""
-
- if (typeof firstItem === 'string') {
- displayText = firstItem
- } else if (typeof firstItem === 'object') {
- const code = firstItem.itemCode || firstItem.code || ""
- const name = firstItem.itemName || firstItem.name || firstItem.materialGroupName || ""
- if (code && name) {
- displayText = `${code}-${name}`
- } else {
- displayText = name || code || String(firstItem)
- }
- } else {
- displayText = String(firstItem)
- }
-
- // 나머지 개수 표시
- if (items.length > 1) {
- displayText += ` 외 ${items.length - 1}개`
- }
-
- return displayText
- } catch {
- return majorItems || "-"
- }
- },
- },
- {
- accessorKey: "establishmentDate",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="설립일자" />
- ),
- cell: ({ row }) => {
- const date = row.getValue("establishmentDate") as string
- return date ? format(new Date(date), "yyyy.MM.dd") : "-"
- },
- },
- {
- accessorKey: "representative",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="대표자명" />
- ),
- cell: ({ row }) => row.getValue("representative") || "-",
- },
- {
- id: "documentStatus",
- header: "진행현황",
- cell: ({ row }) => {
- const DocumentStatusCell = () => {
- const [documentDialogOpen, setDocumentDialogOpen] = useState(false)
- const registration = row.original
-
- // 문서 현황 계산 (국가별 요구사항 적용)
- const isForeign = registration.country !== 'KR'
- const requiredDocs = isForeign ? 4 : 3 // 외자: 4개(통장사본 포함), 내자: 3개(통장사본 제외)
- const submittedDocs = Object.values(registration.documentSubmissions).filter(Boolean).length
- const incompleteDocs = requiredDocs - submittedDocs
-
- // 기본계약 현황 계산
- const totalContracts = registration.basicContracts?.length || 0
- const completedContracts = registration.basicContracts?.filter(c => c.status === "VENDOR_SIGNED" || c.status === "COMPLETED").length || 0
- const incompleteContracts = totalContracts - completedContracts
-
- // 안전적격성 평가 현황
- const safetyCompleted = !!registration.safetyQualificationContent
-
- // 추가정보 현황
- const additionalInfoCompleted = registration.additionalInfo
-
- // 전체 미완료 항목 계산
- const totalIncomplete =
- (incompleteDocs > 0 ? 1 : 0) +
- incompleteContracts +
- (!safetyCompleted ? 1 : 0) +
- (!additionalInfoCompleted ? 1 : 0)
-
- const isAllComplete = totalIncomplete === 0
-
- return (
- <>
- <div className="space-y-1">
- <Button
- variant="ghost"
- size="sm"
- onClick={() => setDocumentDialogOpen(true)}
- className="h-auto p-1 text-left justify-start"
- >
- <div className="space-y-0.5">
- {isAllComplete ? (
- <div className="text-xs text-green-600 font-medium">모든 항목 완료</div>
- ) : (
- <div className="text-xs text-orange-600 font-medium">
- 총 {totalIncomplete}건 미완료
- </div>
- )}
- </div>
- </Button>
- </div>
- <DocumentStatusDialog
- open={documentDialogOpen}
- onOpenChange={setDocumentDialogOpen}
- registration={registration}
- isVendorUser={false}
- />
- </>
- )
- }
-
- return <DocumentStatusCell />
- },
- },
- {
- accessorKey: "registrationRequestDate",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="등록요청일" />
- ),
- cell: ({ row }) => {
- const date = row.getValue("registrationRequestDate") as string
- return date ? format(new Date(date), "yyyy.MM.dd") : "-"
- },
- },
- {
- accessorKey: "assignedDepartment",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="담당부서" />
- ),
- cell: ({ row }) => row.getValue("assignedDepartment") || "-",
- },
- {
- accessorKey: "assignedUser",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="담당자" />
- ),
- cell: ({ row }) => row.getValue("assignedUser") || "-",
- },
- {
- accessorKey: "remarks",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="비고" />
- ),
- cell: ({ row }) => row.getValue("remarks") || "-",
- },
- {
- id: "actions",
- cell: ({ row }) => {
- const ActionsDropdownCell = () => {
- const [safetyQualificationSheetOpen, setSafetyQualificationSheetOpen] = useState(false)
- const [majorItemsSheetOpen, setMajorItemsSheetOpen] = useState(false)
- const registration = row.original
-
- return (
- <>
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- aria-label="Open menu"
- variant="ghost"
- className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
- >
- <Ellipsis className="h-4 w-4" />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end" className="w-[160px]">
- <DropdownMenuItem
- onClick={() => setSafetyQualificationSheetOpen(true)}
- >
- <Shield className="mr-2 h-4 w-4" />
- 안전적격성 평가
- </DropdownMenuItem>
- <DropdownMenuItem
- onClick={() => setMajorItemsSheetOpen(true)}
- >
- <Package className="mr-2 h-4 w-4" />
- 주요품목 등록
- </DropdownMenuItem>
-
- </DropdownMenuContent>
- </DropdownMenu>
- <SafetyQualificationUpdateDialog
- open={safetyQualificationSheetOpen}
- onOpenChange={setSafetyQualificationSheetOpen}
- registrationId={registration.id}
- vendorName={registration.companyName}
- currentContent={registration.safetyQualificationContent}
- onSuccess={() => {
- // 페이지 새로고침 또는 데이터 리페치
- window.location.reload()
- }}
- />
- <MajorItemsUpdateDialog
- open={majorItemsSheetOpen}
- onOpenChange={setMajorItemsSheetOpen}
- registrationId={registration.id}
- vendorName={registration.companyName}
- currentItems={registration.majorItems}
- onSuccess={() => {
- // 페이지 새로고침 또는 데이터 리페치
- window.location.reload()
- }}
- />
-
- </>
- )
- }
-
- return <ActionsDropdownCell />
- },
- },
- ]
-}
+"use client"
+
+import { type ColumnDef } from "@tanstack/react-table"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Badge } from "@/components/ui/badge"
+import { format } from "date-fns"
+
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { VendorRegularRegistration, statusLabels, statusColors } from "@/config/vendorRegularRegistrationsColumnsConfig"
+import { DocumentStatusDialog } from "@/components/vendor-regular-registrations/document-status-dialog"
+import { Button } from "@/components/ui/button"
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
+import { Ellipsis, Shield, Package } from "lucide-react"
+import { useState } from "react"
+import { SafetyQualificationUpdateDialog } from "./safety-qualification-update-dialog"
+import { MajorItemsUpdateDialog } from "./major-items-update-dialog"
+
+export function getColumns(): ColumnDef<VendorRegularRegistration>[] {
+
+ return [
+ {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ className="translate-y-[2px]"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="Select row"
+ className="translate-y-[2px]"
+ />
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "status",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Status" />
+ ),
+ cell: ({ row }) => {
+ const status = row.getValue("status") as string
+ return (
+ <Badge
+ variant="secondary"
+ className={statusColors[status as keyof typeof statusColors]}
+ >
+ {statusLabels[status as keyof typeof statusLabels] || status}
+ </Badge>
+ )
+ },
+ filterFn: (row, id, value) => {
+ return Array.isArray(value) && value.includes(row.getValue(id))
+ },
+ },
+ {
+ accessorKey: "potentialCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="잠재코드" />
+ ),
+ cell: ({ row }) => row.getValue("potentialCode") || "-",
+ },
+ {
+ accessorKey: "businessNumber",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="사업자번호" />
+ ),
+ },
+ {
+ accessorKey: "companyName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="업체명" />
+ ),
+ },
+ {
+ accessorKey: "majorItems",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="주요품목" />
+ ),
+ cell: ({ row }) => {
+ const majorItems = row.getValue("majorItems") as string
+ try {
+ const items = majorItems ? JSON.parse(majorItems) : []
+ if (items.length === 0) return "-"
+
+ // 모든 아이템을 표시
+ return (
+ <div className="flex flex-wrap gap-1 max-w-md">
+ {items.map((item: unknown, index: number) => {
+ let displayText = ""
+
+ if (typeof item === 'string') {
+ displayText = item
+ } else if (typeof item === 'object' && item !== null) {
+ // materialGroup 형태 처리
+ const itemObj = item as Record<string, unknown>
+ const materialGroupCode = (itemObj.materialGroupCode as string) || ""
+ const materialGroupDescription = (itemObj.materialGroupDescription as string) || ""
+
+ if (materialGroupCode && materialGroupDescription) {
+ displayText = `${materialGroupCode} - ${materialGroupDescription}`
+ } else if (materialGroupDescription) {
+ displayText = materialGroupDescription
+ } else if (materialGroupCode) {
+ displayText = materialGroupCode
+ } else {
+ // 알 수 없는 형태
+ console.warn("알 수 없는 품목 형식:", item)
+ displayText = JSON.stringify(item)
+ }
+ } else {
+ displayText = String(item)
+ }
+
+ return (
+ <Badge
+ key={`${row.original.id}-item-${index}`}
+ variant="outline"
+ className="text-xs"
+ >
+ {displayText}
+ </Badge>
+ )
+ })}
+ </div>
+ )
+ } catch (error) {
+ // JSON 파싱 실패 시 원본 문자열 표시
+ console.error("주요품목 파싱 오류:", error)
+ return majorItems || "-"
+ }
+ },
+ },
+ {
+ accessorKey: "establishmentDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="설립일자" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("establishmentDate") as string
+ return date ? format(new Date(date), "yyyy.MM.dd") : "-"
+ },
+ },
+ {
+ accessorKey: "representative",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="대표자명" />
+ ),
+ cell: ({ row }) => row.getValue("representative") || "-",
+ },
+ {
+ id: "documentStatus",
+ header: "진행현황",
+ cell: ({ row }) => {
+ const DocumentStatusCell = () => {
+ const [documentDialogOpen, setDocumentDialogOpen] = useState(false)
+ const registration = row.original
+
+ // 문서 현황 계산 (국가별 요구사항 적용)
+ const isForeign = registration.country !== 'KR'
+ const requiredDocs = isForeign ? 4 : 3 // 외자: 4개(통장사본 포함), 내자: 3개(통장사본 제외)
+ const submittedDocs = Object.values(registration.documentSubmissions).filter(Boolean).length
+ const incompleteDocs = requiredDocs - submittedDocs
+
+ // 기본계약 현황 계산
+ const totalContracts = registration.basicContracts?.length || 0
+ const completedContracts = registration.basicContracts?.filter(c => c.status === "VENDOR_SIGNED" || c.status === "COMPLETED").length || 0
+ const incompleteContracts = totalContracts - completedContracts
+
+ // 안전적격성 평가 현황
+ const safetyCompleted = !!registration.safetyQualificationContent
+
+ // 추가정보 현황
+ const additionalInfoCompleted = registration.additionalInfo
+
+ // 전체 미완료 항목 계산
+ const totalIncomplete =
+ (incompleteDocs > 0 ? 1 : 0) +
+ incompleteContracts +
+ (!safetyCompleted ? 1 : 0) +
+ (!additionalInfoCompleted ? 1 : 0)
+
+ const isAllComplete = totalIncomplete === 0
+
+ return (
+ <>
+ <div className="space-y-1">
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => setDocumentDialogOpen(true)}
+ className="h-auto p-1 text-left justify-start"
+ >
+ <div className="space-y-0.5">
+ {isAllComplete ? (
+ <div className="text-xs text-green-600 font-medium">모든 항목 완료</div>
+ ) : (
+ <div className="text-xs text-orange-600 font-medium">
+ 총 {totalIncomplete}건 미완료
+ </div>
+ )}
+ </div>
+ </Button>
+ </div>
+ <DocumentStatusDialog
+ open={documentDialogOpen}
+ onOpenChange={setDocumentDialogOpen}
+ registration={registration}
+ isVendorUser={false}
+ />
+ </>
+ )
+ }
+
+ return <DocumentStatusCell />
+ },
+ },
+ {
+ accessorKey: "registrationRequestDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="등록요청일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("registrationRequestDate") as string
+ return date ? format(new Date(date), "yyyy.MM.dd") : "-"
+ },
+ },
+ {
+ accessorKey: "assignedDepartment",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="담당부서" />
+ ),
+ cell: ({ row }) => row.getValue("assignedDepartment") || "-",
+ },
+ {
+ accessorKey: "assignedUser",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="담당자" />
+ ),
+ cell: ({ row }) => row.getValue("assignedUser") || "-",
+ },
+ {
+ accessorKey: "remarks",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="비고" />
+ ),
+ cell: ({ row }) => row.getValue("remarks") || "-",
+ },
+ {
+ id: "actions",
+ cell: ({ row }) => {
+ const ActionsDropdownCell = () => {
+ const [safetyQualificationSheetOpen, setSafetyQualificationSheetOpen] = useState(false)
+ const [majorItemsSheetOpen, setMajorItemsSheetOpen] = useState(false)
+ const registration = row.original
+
+ return (
+ <>
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ aria-label="Open menu"
+ variant="ghost"
+ className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
+ >
+ <Ellipsis className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end" className="w-[160px]">
+ <DropdownMenuItem
+ onClick={() => setSafetyQualificationSheetOpen(true)}
+ >
+ <Shield className="mr-2 h-4 w-4" />
+ 안전적격성 평가
+ </DropdownMenuItem>
+ <DropdownMenuItem
+ onClick={() => setMajorItemsSheetOpen(true)}
+ >
+ <Package className="mr-2 h-4 w-4" />
+ 주요품목 등록
+ </DropdownMenuItem>
+
+ </DropdownMenuContent>
+ </DropdownMenu>
+ <SafetyQualificationUpdateDialog
+ open={safetyQualificationSheetOpen}
+ onOpenChange={setSafetyQualificationSheetOpen}
+ registrationId={registration.id}
+ vendorName={registration.companyName}
+ currentContent={registration.safetyQualificationContent}
+ onSuccess={() => {
+ // 페이지 새로고침 또는 데이터 리페치
+ window.location.reload()
+ }}
+ />
+ <MajorItemsUpdateDialog
+ open={majorItemsSheetOpen}
+ onOpenChange={setMajorItemsSheetOpen}
+ registrationId={registration.id}
+ vendorName={registration.companyName}
+ currentItems={registration.majorItems}
+ onSuccess={() => {
+ // 페이지 새로고침 또는 데이터 리페치
+ window.location.reload()
+ }}
+ />
+
+ </>
+ )
+ }
+
+ return <ActionsDropdownCell />
+ },
+ },
+ ]
+}