summaryrefslogtreecommitdiff
path: root/lib/material/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/material/table')
-rw-r--r--lib/material/table/material-detail-dialog.tsx359
-rw-r--r--lib/material/table/material-table-columns.tsx183
-rw-r--r--lib/material/table/material-table.tsx199
3 files changed, 741 insertions, 0 deletions
diff --git a/lib/material/table/material-detail-dialog.tsx b/lib/material/table/material-detail-dialog.tsx
new file mode 100644
index 00000000..aed0485c
--- /dev/null
+++ b/lib/material/table/material-detail-dialog.tsx
@@ -0,0 +1,359 @@
+"use client"
+
+import * as React from "react"
+import { getMaterialDetail } from "../services"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table"
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@/components/ui/tabs"
+import { Skeleton } from "@/components/ui/skeleton"
+import { Badge } from "@/components/ui/badge"
+
+import { formatDate } from "@/lib/utils"
+
+interface MaterialDetailDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ matnr: string | null
+}
+
+export function MaterialDetailDialog({
+ open,
+ onOpenChange,
+ matnr,
+}: MaterialDetailDialogProps) {
+ const [data, setData] = React.useState<Awaited<ReturnType<typeof getMaterialDetail>> | null>(null)
+ const [loading, setLoading] = React.useState(false)
+
+ React.useEffect(() => {
+ if (open && matnr) {
+ setLoading(true)
+ getMaterialDetail(matnr)
+ .then(setData)
+ .finally(() => setLoading(false))
+ }
+ }, [open, matnr])
+
+ if (!matnr) return null
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-6xl h-[90vh] flex flex-col">
+ <DialogHeader className="flex-shrink-0">
+ <DialogTitle>자재마스터 상세정보</DialogTitle>
+ <DialogDescription>
+ 자재코드: {matnr}
+ </DialogDescription>
+ </DialogHeader>
+
+ {loading ? (
+ <div className="space-y-4 p-4">
+ <Skeleton className="h-8 w-full" />
+ <Skeleton className="h-32 w-full" />
+ <Skeleton className="h-32 w-full" />
+ </div>
+ ) : data ? (
+ <Tabs defaultValue="basic" className="w-full flex flex-col flex-1 min-h-0">
+ <TabsList className="grid w-full grid-cols-5 flex-shrink-0">
+ <TabsTrigger value="basic">기본정보</TabsTrigger>
+ <TabsTrigger value="characteristics">특성할당</TabsTrigger>
+ <TabsTrigger value="classifications">클래스할당</TabsTrigger>
+ <TabsTrigger value="descriptions">설명</TabsTrigger>
+ <TabsTrigger value="units">단위</TabsTrigger>
+ </TabsList>
+
+ <div className="flex-1 overflow-y-auto min-h-0 mt-4">
+
+ <TabsContent value="basic" className="mt-0">
+ <div className="space-y-6">
+ <div>
+ <h3 className="text-lg font-semibold mb-3">기본 자재 정보</h3>
+ <Table>
+ <TableBody>
+ <TableRow>
+ <TableCell className="font-medium w-1/4">자재코드 (MATNR)</TableCell>
+ <TableCell>{data.material.MATNR || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">자재명 (ZZNAME)</TableCell>
+ <TableCell>{data.material.ZZNAME || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">프로젝트 (ZZPJT)</TableCell>
+ <TableCell>{data.material.ZZPJT || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">구 자재번호 (BISMT)</TableCell>
+ <TableCell>{data.material.BISMT || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">자재그룹 (MATKL)</TableCell>
+ <TableCell>{data.material.MATKL || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">자재유형 (MTART)</TableCell>
+ <TableCell>{data.material.MTART || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">기본단위 (MEINS)</TableCell>
+ <TableCell>{data.material.MEINS || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">산업섹터 (MBRSH)</TableCell>
+ <TableCell>{data.material.MBRSH || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">사업부 (SPART)</TableCell>
+ <TableCell>{data.material.SPART || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">규격 (ZZSPEC)</TableCell>
+ <TableCell>{data.material.ZZSPEC || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">설명 (ZZDESC)</TableCell>
+ <TableCell>{data.material.ZZDESC || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">PLM ID (ZZPLMID)</TableCell>
+ <TableCell>{data.material.ZZPLMID || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">삭제플래그 (LVORM)</TableCell>
+ <TableCell>
+ {data.material.LVORM ? (
+ <Badge variant="destructive">삭제됨</Badge>
+ ) : (
+ <Badge variant="secondary">활성</Badge>
+ )}
+ </TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">자재상태 (MSTAE)</TableCell>
+ <TableCell>{data.material.MSTAE || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">총 중량 (BRGEW)</TableCell>
+ <TableCell>{data.material.BRGEW || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">순 중량 (NTGEW)</TableCell>
+ <TableCell>{data.material.NTGEW || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">중량단위 (GEWEI)</TableCell>
+ <TableCell>{data.material.GEWEI || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">크기/치수 (GROES)</TableCell>
+ <TableCell>{data.material.GROES || "-"}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">생성일시</TableCell>
+ <TableCell>{formatDate(data.material.createdAt, "KR")}</TableCell>
+ </TableRow>
+ <TableRow>
+ <TableCell className="font-medium">수정일시</TableCell>
+ <TableCell>{formatDate(data.material.updatedAt, "KR")}</TableCell>
+ </TableRow>
+ </TableBody>
+ </Table>
+ </div>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="characteristics" className="mt-0">
+ <div>
+ <h3 className="text-lg font-semibold mb-3">특성 할당 정보</h3>
+ {data.characteristics.length === 0 && (
+ <p className="text-muted-foreground mb-4">특성 할당 정보가 없습니다.</p>
+ )}
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead>특성명 (ATNAM)</TableHead>
+ <TableHead>특성값 (ATWRT)</TableHead>
+ <TableHead>특성내역 (ATBEZ)</TableHead>
+ <TableHead>특성값내역 (ATWTB)</TableHead>
+ <TableHead>클래스번호 (CLASS)</TableHead>
+ <TableHead>클래스유형 (KLART)</TableHead>
+ <TableHead>측정단위 (ATAWE)</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {data.characteristics.length > 0 ? (
+ data.characteristics.map((char, index) => (
+ <TableRow key={index}>
+ <TableCell className="font-medium">{char.ATNAM || "-"}</TableCell>
+ <TableCell>{char.ATWRT || "-"}</TableCell>
+ <TableCell>{char.ATBEZ || "-"}</TableCell>
+ <TableCell>{char.ATWTB || "-"}</TableCell>
+ <TableCell>{char.CLASS || "-"}</TableCell>
+ <TableCell>{char.KLART || "-"}</TableCell>
+ <TableCell>{char.ATAWE || "-"}</TableCell>
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell colSpan={7} className="text-center text-muted-foreground">
+ 데이터가 없습니다
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="classifications" className="mt-0">
+ <div>
+ <h3 className="text-lg font-semibold mb-3">클래스 할당 정보</h3>
+ {data.classifications.length === 0 && (
+ <p className="text-muted-foreground mb-4">클래스 할당 정보가 없습니다.</p>
+ )}
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead>클래스번호 (CLASS)</TableHead>
+ <TableHead>클래스유형 (KLART)</TableHead>
+ <TableHead>생성일시</TableHead>
+ <TableHead>수정일시</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {data.classifications.length > 0 ? (
+ data.classifications.map((cls, index) => (
+ <TableRow key={index}>
+ <TableCell className="font-medium">{cls.CLASS || "-"}</TableCell>
+ <TableCell>{cls.KLART || "-"}</TableCell>
+ <TableCell>{formatDate(cls.createdAt, "KR")}</TableCell>
+ <TableCell>{formatDate(cls.updatedAt, "KR")}</TableCell>
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell colSpan={4} className="text-center text-muted-foreground">
+ 데이터가 없습니다
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="descriptions" className="mt-0">
+ <div>
+ <h3 className="text-lg font-semibold mb-3">자재 설명 정보</h3>
+ {data.descriptions.length === 0 && (
+ <p className="text-muted-foreground mb-4">자재 설명 정보가 없습니다.</p>
+ )}
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead>자재설명 (MAKTX)</TableHead>
+ <TableHead>언어 (SPRAS)</TableHead>
+ <TableHead>생성일시</TableHead>
+ <TableHead>수정일시</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {data.descriptions.length > 0 ? (
+ data.descriptions.map((desc, index) => (
+ <TableRow key={index}>
+ <TableCell className="font-medium">{desc.MAKTX || "-"}</TableCell>
+ <TableCell>{desc.SPRAS || "-"}</TableCell>
+ <TableCell>{formatDate(desc.createdAt, "KR")}</TableCell>
+ <TableCell>{formatDate(desc.updatedAt, "KR")}</TableCell>
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell colSpan={4} className="text-center text-muted-foreground">
+ 데이터가 없습니다
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="units" className="mt-0">
+ <div>
+ <h3 className="text-lg font-semibold mb-3">단위 정보</h3>
+ {data.units.length === 0 && (
+ <p className="text-muted-foreground mb-4">단위 정보가 없습니다.</p>
+ )}
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead>대체단위 (MEINH)</TableHead>
+ <TableHead>분모 (UMREN)</TableHead>
+ <TableHead>분자 (UMREZ)</TableHead>
+ <TableHead>길이 (LAENG)</TableHead>
+ <TableHead>폭 (BREIT)</TableHead>
+ <TableHead>높이 (HOEHE)</TableHead>
+ <TableHead>부피 (VOLUM)</TableHead>
+ <TableHead>부피단위 (VOLEH)</TableHead>
+ <TableHead>총중량 (BRGEW)</TableHead>
+ <TableHead>중량단위 (GEWEI)</TableHead>
+ <TableHead>치수단위 (MEABM)</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {data.units.length > 0 ? (
+ data.units.map((unit, index) => (
+ <TableRow key={index}>
+ <TableCell className="font-medium">{unit.MEINH || "-"}</TableCell>
+ <TableCell>{unit.UMREN || "-"}</TableCell>
+ <TableCell>{unit.UMREZ || "-"}</TableCell>
+ <TableCell>{unit.LAENG || "-"}</TableCell>
+ <TableCell>{unit.BREIT || "-"}</TableCell>
+ <TableCell>{unit.HOEHE || "-"}</TableCell>
+ <TableCell>{unit.VOLUM || "-"}</TableCell>
+ <TableCell>{unit.VOLEH || "-"}</TableCell>
+ <TableCell>{unit.BRGEW || "-"}</TableCell>
+ <TableCell>{unit.GEWEI || "-"}</TableCell>
+ <TableCell>{unit.MEABM || "-"}</TableCell>
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell colSpan={11} className="text-center text-muted-foreground">
+ 데이터가 없습니다
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </div>
+ </TabsContent>
+ </div>
+ </Tabs>
+ ) : (
+ <div className="text-center py-8">
+ <p className="text-muted-foreground">데이터를 찾을 수 없습니다.</p>
+ </div>
+ )}
+ </DialogContent>
+ </Dialog>
+ )
+}
diff --git a/lib/material/table/material-table-columns.tsx b/lib/material/table/material-table-columns.tsx
new file mode 100644
index 00000000..dd405770
--- /dev/null
+++ b/lib/material/table/material-table-columns.tsx
@@ -0,0 +1,183 @@
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import { type DataTableRowAction } from "@/types/table"
+
+import { formatDate } from "@/lib/utils"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Button } from "@/components/ui/button"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+
+// Material 타입 정의 (서비스에서 반환되는 타입과 일치)
+type Material = {
+ id: number;
+ MATKL: string | null; // 자재그룹
+ MATNR: string | null; // 자재코드
+ ZZNAME: string | null; // 자재명
+ ZZPJT: string | null; // 프로젝트
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<Material> | null>>
+}
+
+/**
+ * Material 테이블 컬럼 정의
+ */
+export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Material>[] {
+
+ // ----------------------------------------------------------------
+ // 1) select 컬럼 (체크박스)
+ // ----------------------------------------------------------------
+ const selectColumn: ColumnDef<Material> = {
+ 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"
+ />
+ ),
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ }
+
+ // ----------------------------------------------------------------
+ // 2) 데이터 컬럼들
+ // ----------------------------------------------------------------
+ const dataColumns: ColumnDef<Material>[] = [
+ {
+ accessorKey: "MATKL",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="자재그룹" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("MATKL") as string | null
+ return (
+ <div className="font-medium">
+ {value || "-"}
+ </div>
+ )
+ },
+ enableSorting: true,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "MATNR",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="자재코드" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("MATNR") as string | null
+ return (
+ <div className="font-medium">
+ {value || "-"}
+ </div>
+ )
+ },
+ enableSorting: true,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "ZZNAME",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="자재명" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("ZZNAME") as string | null
+ return (
+ <div className="max-w-[200px] truncate">
+ {value || "-"}
+ </div>
+ )
+ },
+ enableSorting: true,
+ },
+ {
+ accessorKey: "ZZPJT",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="프로젝트" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("ZZPJT") as string | null
+ return (
+ <div className="max-w-[150px] truncate">
+ {value || "-"}
+ </div>
+ )
+ },
+ enableSorting: true,
+ },
+ {
+ accessorKey: "createdAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="생성일시" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("createdAt") as Date
+ return (
+ <div className="text-sm text-muted-foreground">
+ {formatDate(value, "KR")}
+ </div>
+ )
+ },
+ enableSorting: true,
+ },
+ {
+ accessorKey: "updatedAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="수정일시" />
+ ),
+ cell: ({ row }) => {
+ const value = row.getValue("updatedAt") as Date
+ return (
+ <div className="text-sm text-muted-foreground">
+ {formatDate(value, "KR")}
+ </div>
+ )
+ },
+ enableSorting: true,
+ },
+ ]
+
+ // ----------------------------------------------------------------
+ // 3) actions 컬럼 (상세보기)
+ // ----------------------------------------------------------------
+ const actionsColumn: ColumnDef<Material> = {
+ id: "actions",
+ enableHiding: false,
+ cell: function Cell({ row }) {
+ return (
+ <Button onClick={() => setRowAction({ row, type: "view" })}>
+ 상세
+ </Button>
+ )
+ },
+ size: 40,
+ }
+
+ // ----------------------------------------------------------------
+ // 4) 최종 컬럼 배열
+ // ----------------------------------------------------------------
+ return [
+ selectColumn,
+ ...dataColumns,
+ actionsColumn,
+ ]
+}
diff --git a/lib/material/table/material-table.tsx b/lib/material/table/material-table.tsx
new file mode 100644
index 00000000..6870a030
--- /dev/null
+++ b/lib/material/table/material-table.tsx
@@ -0,0 +1,199 @@
+"use client"
+
+import * as React from "react"
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+ DataTableRowAction,
+} from "@/types/table"
+
+import { useDataTable } from "@/hooks/use-data-table"
+import { DataTable } from "@/components/data-table/data-table"
+import { InfiniteDataTable } from "@/components/data-table/infinite-data-table"
+import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
+import { Button } from "@/components/ui/button"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+
+import { getMaterials } from "../services"
+import { getColumns } from "./material-table-columns"
+import { MaterialDetailDialog } from "./material-detail-dialog"
+import { ViewModeToggle } from "@/components/data-table/view-mode-toggle"
+
+// Material 타입 정의 (서비스에서 반환되는 타입과 일치)
+type Material = {
+ id: number;
+ MATKL: string | null; // 자재그룹(=자재그룹코드)
+ MATNR: string | null; // 자재코드
+ ZZNAME: string | null; // 자재명
+ ZZPJT: string | null; // 프로젝트
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+interface MaterialTableProps {
+ promises?: Promise<
+ [
+ Awaited<ReturnType<typeof getMaterials>>,
+ ]
+ >
+}
+
+export function MaterialTable({ promises }: MaterialTableProps) {
+ // 페이지네이션 모드 데이터
+ const paginationData = promises ? React.use(promises) : null
+ const [{ data = [], pageCount = 0 }] = paginationData || [{ data: [], pageCount: 0 }]
+
+ console.log('MaterialTable data:', data.length, 'materials')
+
+ const [rowAction, setRowAction] =
+ React.useState<DataTableRowAction<Material> | null>(null)
+
+ const columns = React.useMemo(
+ () => getColumns({ setRowAction }),
+ [setRowAction]
+ )
+
+ // 기존 필터 필드들
+ const filterFields: DataTableFilterField<Material>[] = [
+ {
+ id: "MATKL",
+ label: "자재그룹",
+ },
+ {
+ id: "MATNR",
+ label: "자재코드",
+ },
+ ]
+
+ const advancedFilterFields: DataTableAdvancedFilterField<Material>[] = [
+ {
+ id: "MATKL",
+ label: "자재그룹",
+ type: "text",
+ },
+ {
+ id: "MATNR",
+ label: "자재코드",
+ type: "text",
+ },
+ {
+ id: "ZZNAME",
+ label: "자재명",
+ type: "text",
+ },
+ {
+ id: "ZZPJT",
+ label: "프로젝트",
+ type: "text",
+ },
+ ]
+
+ // 확장된 useDataTable 훅 사용 (pageSize 기반 자동 전환)
+ const {
+ table,
+ infiniteScroll,
+ isInfiniteMode,
+ handlePageSizeChange,
+ } = useDataTable({
+ data,
+ columns,
+ pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "createdAt", desc: true }],
+ columnPinning: { left: ["select"], right: ["actions"] },
+ },
+ getRowId: (originalRow) => String(originalRow.id),
+ shallow: false,
+ clearOnDefault: true,
+ // 무한 스크롤 설정
+ infiniteScrollConfig: {
+ apiEndpoint: "/api/table/materials/infinite",
+ tableName: "materials",
+ maxPageSize: 100,
+ },
+ })
+
+ return (
+ <div className="w-full space-y-2.5 overflow-x-auto" style={{maxWidth:'100vw'}}>
+
+ {/* 모드 토글 */}
+ <div className="flex items-center justify-between">
+ <ViewModeToggle
+ isInfiniteMode={isInfiniteMode}
+ onSwitch={handlePageSizeChange}
+ />
+ </div>
+
+ {/* 에러 상태 (무한 스크롤 모드) */}
+ {isInfiniteMode && infiniteScroll?.error && (
+ <Alert variant="destructive">
+ <AlertDescription>
+ 데이터를 불러오는 중 오류가 발생했습니다.
+ <Button
+ variant="link"
+ size="sm"
+ onClick={() => infiniteScroll.reset()}
+ className="ml-2 p-0 h-auto"
+ >
+ 다시 시도
+ </Button>
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {/* 로딩 상태가 아닐 때만 테이블 렌더링 */}
+ {!(isInfiniteMode && infiniteScroll?.isLoading && infiniteScroll?.isEmpty) ? (
+ <>
+ {/* 도구 모음 */}
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ />
+
+ {/* 테이블 렌더링 */}
+ {isInfiniteMode ? (
+ // 무한 스크롤 모드: InfiniteDataTable 사용
+ <InfiniteDataTable
+ table={table}
+ hasNextPage={infiniteScroll?.hasNextPage || false}
+ isLoadingMore={infiniteScroll?.isLoadingMore || false}
+ onLoadMore={infiniteScroll?.onLoadMore}
+ totalCount={infiniteScroll?.totalCount}
+ isEmpty={infiniteScroll?.isEmpty || false}
+ compact={false}
+ autoSizeColumns={true}
+ />
+ ) : (
+ // 페이지네이션 모드: DataTable 사용
+ <DataTable
+ table={table}
+ compact={false}
+ autoSizeColumns={true}
+ />
+ )}
+ </>
+ ) : (
+ /* 로딩 스켈레톤 (무한 스크롤 초기 로딩) */
+ <div className="space-y-3">
+ <div className="text-sm text-muted-foreground mb-4">
+ 무한 스크롤 모드로 데이터를 로드하고 있습니다...
+ </div>
+ {Array.from({ length: 10 }).map((_, i) => (
+ <div key={i} className="h-12 w-full bg-muted animate-pulse rounded" />
+ ))}
+ </div>
+ )}
+
+ {/* 상세보기 다이얼로그 */}
+ <MaterialDetailDialog
+ open={rowAction?.type === "view"}
+ onOpenChange={() => setRowAction(null)}
+ matnr={rowAction?.row.original?.MATNR || null}
+ />
+ </div>
+ )
+}