137 lines
3.7 KiB
TypeScript
137 lines
3.7 KiB
TypeScript
/**
|
|
* TableView - DataView의 테이블 뷰
|
|
* - 기본 테이블 렌더링
|
|
* - 체크박스, 행 선택, 더블클릭 지원
|
|
*/
|
|
|
|
"use client";
|
|
|
|
import React from "react";
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface TableViewProps {
|
|
data: any[];
|
|
columns: string[];
|
|
tableName: string;
|
|
loading?: boolean;
|
|
selectedRows?: Set<string>;
|
|
onRowSelect?: (rowId: string) => void;
|
|
onRowDoubleClick?: (row: any) => void;
|
|
}
|
|
|
|
export function TableView({
|
|
data,
|
|
columns,
|
|
tableName,
|
|
loading = false,
|
|
selectedRows = new Set(),
|
|
onRowSelect,
|
|
onRowDoubleClick,
|
|
}: TableViewProps) {
|
|
// 로딩 스켈레톤
|
|
if (loading) {
|
|
return (
|
|
<div className="space-y-2 p-4">
|
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
<Skeleton key={i} className="h-10 w-full" />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 데이터 없음
|
|
if (data.length === 0) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
<div className="mb-2 text-sm font-semibold">데이터가 없습니다</div>
|
|
<p className="text-xs text-muted-foreground">아직 등록된 항목이 없습니다</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
{onRowSelect && (
|
|
<TableHead className="w-12">
|
|
<Checkbox
|
|
checked={selectedRows.size === data.length && data.length > 0}
|
|
onCheckedChange={(checked) => {
|
|
if (checked) {
|
|
data.forEach((row) => onRowSelect(row.id || String(row)));
|
|
} else {
|
|
data.forEach((row) => onRowSelect(row.id || String(row)));
|
|
}
|
|
}}
|
|
/>
|
|
</TableHead>
|
|
)}
|
|
{columns.map((column) => (
|
|
<TableHead key={column} className="text-xs font-medium sm:text-sm">
|
|
{column}
|
|
</TableHead>
|
|
))}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{data.map((row, rowIndex) => {
|
|
const rowId = row.id || String(rowIndex);
|
|
const isSelected = selectedRows.has(rowId);
|
|
|
|
return (
|
|
<TableRow
|
|
key={rowId}
|
|
className={cn(
|
|
"cursor-pointer transition-colors hover:bg-muted/50",
|
|
isSelected && "bg-muted"
|
|
)}
|
|
onDoubleClick={() => onRowDoubleClick?.(row)}
|
|
>
|
|
{onRowSelect && (
|
|
<TableCell className="w-12">
|
|
<Checkbox
|
|
checked={isSelected}
|
|
onCheckedChange={() => onRowSelect(rowId)}
|
|
/>
|
|
</TableCell>
|
|
)}
|
|
{columns.map((column) => (
|
|
<TableCell key={column} className="text-xs sm:text-sm">
|
|
{formatCellValue(row[column])}
|
|
</TableCell>
|
|
))}
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 셀 값 포맷팅
|
|
function formatCellValue(value: any): string {
|
|
if (value === null || value === undefined) {
|
|
return "";
|
|
}
|
|
|
|
if (typeof value === "boolean") {
|
|
return value ? "O" : "X";
|
|
}
|
|
|
|
if (value instanceof Date) {
|
|
return value.toLocaleDateString("ko-KR");
|
|
}
|
|
|
|
if (typeof value === "object") {
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
return String(value);
|
|
}
|