diff options
Diffstat (limited to 'lib/tech-vendors/possible-items/possible-items-table.tsx')
| -rw-r--r-- | lib/tech-vendors/possible-items/possible-items-table.tsx | 425 |
1 files changed, 255 insertions, 170 deletions
diff --git a/lib/tech-vendors/possible-items/possible-items-table.tsx b/lib/tech-vendors/possible-items/possible-items-table.tsx index 9c024a93..b54e12d4 100644 --- a/lib/tech-vendors/possible-items/possible-items-table.tsx +++ b/lib/tech-vendors/possible-items/possible-items-table.tsx @@ -1,171 +1,256 @@ -"use client"
-
-import * as React from "react"
-import type {
- DataTableAdvancedFilterField,
- DataTableFilterField,
- DataTableRowAction,
-} from "@/types/table"
-import { toast } from "sonner"
-
-import { useDataTable } from "@/hooks/use-data-table"
-import { DataTable } from "@/components/data-table/data-table"
-import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@/components/ui/alert-dialog"
-
-import { getColumns } from "./possible-items-columns"
-import {
- getTechVendorPossibleItems,
- deleteTechVendorPossibleItem,
-} from "../service"
-import type { TechVendorPossibleItem } from "../validations"
-import { PossibleItemsTableToolbarActions } from "./possible-items-toolbar-actions"
-import { AddItemDialog } from "./add-item-dialog"
-
-interface TechVendorPossibleItemsTableProps {
- promises: Promise<
- [
- Awaited<ReturnType<typeof getTechVendorPossibleItems>>,
- ]
- >
- vendorId: number
-}
-
-export function TechVendorPossibleItemsTable({
- promises,
- vendorId,
-}: TechVendorPossibleItemsTableProps) {
- // Suspense로 받아온 데이터
- const [{ data, pageCount }] = React.use(promises)
- const [rowAction, setRowAction] = React.useState<DataTableRowAction<TechVendorPossibleItem> | null>(null)
- const [showAddDialog, setShowAddDialog] = React.useState(false)
- const [showDeleteAlert, setShowDeleteAlert] = React.useState(false)
- const [isDeleting, setIsDeleting] = React.useState(false)
-
- // getColumns() 호출 시, setRowAction을 주입
- const columns = React.useMemo(
- () => getColumns({ setRowAction }),
- [setRowAction]
- )
-
- // 단일 아이템 삭제 핸들러
- async function handleDeleteItem() {
- if (!rowAction || rowAction.type !== "delete") return
-
- setIsDeleting(true)
- try {
- const { success, error } = await deleteTechVendorPossibleItem(
- rowAction.row.original.id,
- vendorId
- )
-
- if (!success) {
- throw new Error(error)
- }
-
- toast.success("아이템이 삭제되었습니다")
- setShowDeleteAlert(false)
- setRowAction(null)
- } catch (err) {
- toast.error(err instanceof Error ? err.message : "아이템 삭제 중 오류가 발생했습니다")
- } finally {
- setIsDeleting(false)
- }
- }
-
- const filterFields: DataTableFilterField<TechVendorPossibleItem>[] = [
- { id: "itemCode", label: "아이템 코드" },
- { id: "workType", label: "공종" },
- ]
-
- const advancedFilterFields: DataTableAdvancedFilterField<TechVendorPossibleItem>[] = [
- { id: "itemCode", label: "아이템 코드", type: "text" },
- { id: "workType", label: "공종", type: "text" },
- { id: "itemList", label: "아이템명", type: "text" },
- { id: "shipTypes", label: "선종", type: "text" },
- { id: "subItemList", label: "서브아이템", type: "text" },
- { id: "createdAt", label: "등록일", type: "date" },
- { id: "updatedAt", label: "수정일", type: "date" },
- ]
-
- const { table } = useDataTable({
- data,
- columns,
- pageCount,
- filterFields,
- enablePinning: true,
- enableAdvancedFilter: true,
- initialState: {
- sorting: [{ id: "createdAt", desc: true }],
- columnPinning: { right: ["actions"] },
- },
- getRowId: (originalRow) => String(originalRow.id),
- shallow: false,
- clearOnDefault: true,
- })
-
- // rowAction 상태 변경 감지
- React.useEffect(() => {
- if (rowAction?.type === "delete") {
- setShowDeleteAlert(true)
- }
- }, [rowAction])
-
- return (
- <>
- <DataTable table={table}>
- <DataTableAdvancedToolbar
- table={table}
- filterFields={advancedFilterFields}
- shallow={false}
- >
- <PossibleItemsTableToolbarActions
- table={table}
- vendorId={vendorId}
- onAdd={() => setShowAddDialog(true)}
- />
- </DataTableAdvancedToolbar>
- </DataTable>
-
- {/* Add Item Dialog */}
- <AddItemDialog
- open={showAddDialog}
- onOpenChange={setShowAddDialog}
- vendorId={vendorId}
- />
-
- {/* Delete Confirmation Dialog */}
- <AlertDialog open={showDeleteAlert} onOpenChange={setShowDeleteAlert}>
- <AlertDialogContent>
- <AlertDialogHeader>
- <AlertDialogTitle>아이템 삭제</AlertDialogTitle>
- <AlertDialogDescription>
- 이 아이템을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.
- </AlertDialogDescription>
- </AlertDialogHeader>
- <AlertDialogFooter>
- <AlertDialogCancel onClick={() => setRowAction(null)}>
- 취소
- </AlertDialogCancel>
- <AlertDialogAction
- onClick={handleDeleteItem}
- disabled={isDeleting}
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
- >
- {isDeleting ? "삭제 중..." : "삭제"}
- </AlertDialogAction>
- </AlertDialogFooter>
- </AlertDialogContent>
- </AlertDialog>
- </>
- )
+"use client" + +import * as React from "react" +import type { + DataTableAdvancedFilterField, + DataTableFilterField, + DataTableRowAction, +} from "@/types/table" +import { toast } from "sonner" + +import { useDataTable } from "@/hooks/use-data-table" +import { DataTable } from "@/components/data-table/data-table" +import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { ScrollArea } from "@/components/ui/scroll-area" + +import { getColumns } from "./possible-items-columns" +import { getTechVendorPossibleItems } from "../../tech-vendor-possible-items/service" +import { deleteTechVendorPossibleItem, getTechVendorDetailById } from "../service" +import type { TechVendorPossibleItem } from "../validations" +import { PossibleItemsTableToolbarActions } from "./possible-items-toolbar-actions" +import { AddItemDialog } from "./add-item-dialog" // 주석처리 + +interface TechVendorPossibleItemsTableProps { + promises: Promise< + [ + Awaited<ReturnType<typeof getTechVendorPossibleItems>>, + ] + > + vendorId: number +} + +export function TechVendorPossibleItemsTable({ + promises, + vendorId, +}: TechVendorPossibleItemsTableProps) { + // Suspense로 받아온 데이터 + const [{ data, pageCount }] = React.use(promises) + const [rowAction, setRowAction] = React.useState<DataTableRowAction<TechVendorPossibleItem> | null>(null) + const [showAddDialog, setShowAddDialog] = React.useState(false) // 주석처리 + const [showDeleteAlert, setShowDeleteAlert] = React.useState(false) + const [isDeleting, setIsDeleting] = React.useState(false) + + // vendor 정보와 items 다이얼로그 관련 상태 + const [vendorInfo, setVendorInfo] = React.useState<{ + id: number + vendorName: string + status: string + items: string | null + } | null>(null) + const [showItemsDialog, setShowItemsDialog] = React.useState(false) + const [isLoadingVendor, setIsLoadingVendor] = React.useState(false) + + // getColumns() 호출 시, setRowAction을 주입 + const columns = React.useMemo( + () => getColumns({ setRowAction }), + [setRowAction] + ) + + // vendor 정보 가져오기 + React.useEffect(() => { + async function fetchVendorInfo() { + try { + setIsLoadingVendor(true) + const vendorData = await getTechVendorDetailById(vendorId) + setVendorInfo(vendorData) + } catch (error) { + console.error("Error fetching vendor info:", error) + toast.error("벤더 정보를 불러오는데 실패했습니다") + } finally { + setIsLoadingVendor(false) + } + } + + fetchVendorInfo() + }, [vendorId]) + + // 단일 아이템 삭제 핸들러 + async function handleDeleteItem() { + if (!rowAction || rowAction.type !== "delete") return + + setIsDeleting(true) + try { + const { success, error } = await deleteTechVendorPossibleItem( + rowAction.row.original.id, + vendorId + ) + + if (!success) { + throw new Error(error) + } + + toast.success("아이템이 삭제되었습니다") + setShowDeleteAlert(false) + setRowAction(null) + } catch (err) { + toast.error(err instanceof Error ? err.message : "아이템 삭제 중 오류가 발생했습니다") + } finally { + setIsDeleting(false) + } + } + + const filterFields: DataTableFilterField<TechVendorPossibleItem>[] = [ + { id: "vendorId", label: "벤더 ID" }, + { id: "techVendorType", label: "아이템 타입" }, + ] + + const advancedFilterFields: DataTableAdvancedFilterField<TechVendorPossibleItem>[] = [ + { id: "vendorId", label: "벤더 ID", type: "number" }, + { id: "itemCode", label: "아이템 코드", type: "text" }, + { id: "workType", label: "공종", type: "text" }, + { id: "itemList", label: "아이템명", type: "text" }, + { id: "shipTypes", label: "선종", type: "text" }, + { id: "subItemList", label: "서브아이템", type: "text" }, + { id: "techVendorType", label: "아이템 타입", type: "text" }, + { id: "createdAt", label: "등록일", type: "date" }, + { id: "updatedAt", label: "수정일", type: "date" }, + ] + + const { table } = useDataTable({ + data: data as TechVendorPossibleItem[], // 타입 단언 추가 + columns, + pageCount, + filterFields, + enablePinning: true, + enableAdvancedFilter: true, + initialState: { + sorting: [{ id: "createdAt", desc: true }], + columnPinning: { right: ["actions"] }, + }, + getRowId: (originalRow) => String(originalRow.id), + shallow: false, + clearOnDefault: true, + }) + + // rowAction 상태 변경 감지 + React.useEffect(() => { + if (rowAction?.type === "delete") { + setShowDeleteAlert(true) + } + }, [rowAction]) + + // 견적비교용 벤더인지 확인 + const isQuoteComparisonVendor = vendorInfo?.status === "QUOTE_COMPARISON" + + + return ( + <> + <DataTable table={table}> + <DataTableAdvancedToolbar + table={table} + filterFields={advancedFilterFields} + shallow={false} + > + <div className="flex items-center gap-2"> + {/* 견적비교용 벤더일 때만 items 버튼 표시 */} + {isQuoteComparisonVendor && ( + <Button + variant="outline" + size="sm" + onClick={() => setShowItemsDialog(true)} + disabled={isLoadingVendor} + className="flex items-center gap-2" + > + <Badge variant="secondary" className="text-xs"> + 수기입력된 자재 항목 + </Badge> + </Button> + )} + + <PossibleItemsTableToolbarActions + table={table} + vendorId={vendorId} + onAdd={() => setShowAddDialog(true)} // 주석처리 + /> + </div> + </DataTableAdvancedToolbar> + </DataTable> + + {/* Add Item Dialog */} + <AddItemDialog + open={showAddDialog} + onOpenChange={setShowAddDialog} + vendorId={vendorId} + /> + + {/* Vendor Items Dialog */} + <Dialog open={showItemsDialog} onOpenChange={setShowItemsDialog}> + <DialogContent className="max-w-2xl max-h-[80vh]"> + <DialogHeader> + <DialogTitle>벤더 수기입력 자재 항목</DialogTitle> + <DialogDescription> + {vendorInfo?.vendorName} 벤더가 직접 입력한 자재 항목입니다. + </DialogDescription> + </DialogHeader> + + <ScrollArea className="max-h-[60vh]"> + {vendorInfo?.items && vendorInfo.items.length > 0 ? ( + <div className="space-y-3"> + <p className="text-sm text-muted-foreground mt-1"> + {vendorInfo.items} + </p> + </div> + ) : ( + <div className="text-center py-8 text-muted-foreground"> + <p>등록된 수기입력 자재 항목이 없습니다.</p> + </div> + )} + </ScrollArea> + </DialogContent> + </Dialog> + + {/* Delete Confirmation Dialog */} + <AlertDialog open={showDeleteAlert} onOpenChange={setShowDeleteAlert}> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>아이템 삭제</AlertDialogTitle> + <AlertDialogDescription> + 이 아이템을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel onClick={() => setRowAction(null)}> + 취소 + </AlertDialogCancel> + <AlertDialogAction + onClick={handleDeleteItem} + disabled={isDeleting} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + {isDeleting ? "삭제 중..." : "삭제"} + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + </> + ) }
\ No newline at end of file |
