refactor(WEB): DataTable 개선 및 회계 상세 컴포넌트 리팩토링
- DataTable 컴포넌트 기능 확장 및 코드 개선 - 회계 상세 컴포넌트(Bill/Deposit/Purchase/Sales/Withdrawal) 리팩토링 - 엑셀 다운로드 유틸리티 개선 - 대시보드 및 각종 리스트 페이지 업데이트 - dashboard_type2 페이지 추가 - 프론트엔드 개선 로드맵 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { ReactNode, memo, useCallback } from "react";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -204,6 +204,62 @@ function getAlignClass<T>(column: Column<T>): string {
|
||||
}
|
||||
}
|
||||
|
||||
// 메모이즈드 행 컴포넌트 — 행 데이터가 변경되지 않으면 리렌더링 스킵
|
||||
interface DataTableRowProps<T extends object> {
|
||||
row: T;
|
||||
rowIndex: number;
|
||||
columns: Column<T>[];
|
||||
onRowClick?: (row: T) => void;
|
||||
hoverable: boolean;
|
||||
striped: boolean;
|
||||
compact: boolean;
|
||||
rowKey: string;
|
||||
}
|
||||
|
||||
function DataTableRowInner<T extends object>({
|
||||
row,
|
||||
rowIndex,
|
||||
columns,
|
||||
onRowClick,
|
||||
hoverable,
|
||||
striped,
|
||||
compact,
|
||||
}: DataTableRowProps<T>) {
|
||||
const handleClick = useCallback(() => {
|
||||
onRowClick?.(row);
|
||||
}, [onRowClick, row]);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
onClick={onRowClick ? handleClick : undefined}
|
||||
className={cn(
|
||||
onRowClick && "cursor-pointer",
|
||||
hoverable && "hover:bg-muted/50",
|
||||
striped && rowIndex % 2 === 1 && "bg-muted/20",
|
||||
compact && "h-10"
|
||||
)}
|
||||
>
|
||||
{columns.map((column) => {
|
||||
const value = column.key in row ? row[column.key as keyof T] : null;
|
||||
return (
|
||||
<TableCell
|
||||
key={String(column.key)}
|
||||
className={cn(
|
||||
getAlignClass(column),
|
||||
column.className,
|
||||
compact && "py-2"
|
||||
)}
|
||||
>
|
||||
{renderCell(column, value, row, rowIndex)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
const MemoizedDataTableRow = memo(DataTableRowInner) as typeof DataTableRowInner;
|
||||
|
||||
export function DataTable<T extends object>({
|
||||
columns,
|
||||
data,
|
||||
@@ -216,6 +272,11 @@ export function DataTable<T extends object>({
|
||||
hoverable = true,
|
||||
compact = false
|
||||
}: DataTableProps<T>) {
|
||||
const stableOnRowClick = useCallback(
|
||||
(row: T) => onRowClick?.(row),
|
||||
[onRowClick]
|
||||
);
|
||||
|
||||
return (
|
||||
<Card className="hidden md:block">
|
||||
<CardContent className="p-0">
|
||||
@@ -252,32 +313,17 @@ export function DataTable<T extends object>({
|
||||
</TableRow>
|
||||
) : (
|
||||
data.map((row, rowIndex) => (
|
||||
<TableRow
|
||||
<MemoizedDataTableRow<T>
|
||||
key={row[keyField] ? String(row[keyField]) : `row-${rowIndex}`}
|
||||
onClick={() => onRowClick?.(row)}
|
||||
className={cn(
|
||||
onRowClick && "cursor-pointer",
|
||||
hoverable && "hover:bg-muted/50",
|
||||
striped && rowIndex % 2 === 1 && "bg-muted/20",
|
||||
compact && "h-10"
|
||||
)}
|
||||
>
|
||||
{columns.map((column) => {
|
||||
const value = column.key in row ? row[column.key as keyof T] : null;
|
||||
return (
|
||||
<TableCell
|
||||
key={String(column.key)}
|
||||
className={cn(
|
||||
getAlignClass(column),
|
||||
column.className,
|
||||
compact && "py-2"
|
||||
)}
|
||||
>
|
||||
{renderCell(column, value, row, rowIndex)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
row={row}
|
||||
rowIndex={rowIndex}
|
||||
columns={columns}
|
||||
onRowClick={onRowClick ? stableOnRowClick : undefined}
|
||||
hoverable={hoverable}
|
||||
striped={striped}
|
||||
compact={compact}
|
||||
rowKey={row[keyField] ? String(row[keyField]) : `row-${rowIndex}`}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
|
||||
Reference in New Issue
Block a user