feat(sn): CS 기준 UI 통일 및 지도 팝업 표준화
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, type ReactNode } from "react";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
type ColumnDef,
|
||||
type PaginationState,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import {
|
||||
StandardDataTable,
|
||||
getStandardRowNumber,
|
||||
type ScrollMode,
|
||||
type UiHeadProps,
|
||||
} from "@common/business_components";
|
||||
|
||||
type MapPopupDataTableProps<T extends Record<string, unknown>> = {
|
||||
rows: T[];
|
||||
headlist: UiHeadProps[];
|
||||
title: ReactNode;
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
onPageChange: (page: number) => void;
|
||||
actions?: ReactNode;
|
||||
loading?: boolean;
|
||||
scrollMode?: ScrollMode;
|
||||
minTableWidth?: string;
|
||||
emptyMessage?: string;
|
||||
viewPagination?: boolean;
|
||||
fillAvailableHeight?: boolean;
|
||||
rowId?: (row: T, index: number) => string;
|
||||
selectedRowId?: string;
|
||||
onRowClick?: (row: T, index: number) => void;
|
||||
onCellClick?: (row: T, rowIndex: number, columnId: string) => void;
|
||||
clickableHeadIds?: string[];
|
||||
renderCell?: (
|
||||
row: T,
|
||||
headId: string,
|
||||
rowIndex: number,
|
||||
) => ReactNode | undefined;
|
||||
};
|
||||
|
||||
const ROW_NUMBER_IDS = new Set(["no", "rowNo", "rowNumber"]);
|
||||
|
||||
const formatCellValue = (value: unknown) => {
|
||||
if (value === null || value === undefined || value === "") return "-";
|
||||
if (typeof value === "number") return value.toLocaleString("ko-KR");
|
||||
return String(value);
|
||||
};
|
||||
|
||||
/** 지도 WindowPopup 목록을 CS 공통 표·순번·페이징 규칙으로 연결한다. */
|
||||
export default function MapPopupDataTable<T extends Record<string, unknown>>({
|
||||
rows,
|
||||
headlist,
|
||||
title,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
onPageChange,
|
||||
actions,
|
||||
loading = false,
|
||||
scrollMode = "none",
|
||||
minTableWidth = "100%",
|
||||
emptyMessage,
|
||||
viewPagination = true,
|
||||
fillAvailableHeight = false,
|
||||
rowId,
|
||||
selectedRowId,
|
||||
onRowClick,
|
||||
onCellClick,
|
||||
clickableHeadIds,
|
||||
renderCell,
|
||||
}: MapPopupDataTableProps<T>) {
|
||||
const visibleHeads = useMemo(
|
||||
() =>
|
||||
headlist.filter(
|
||||
(head) => head.visible !== false && head.id && head.id.length > 1,
|
||||
),
|
||||
[headlist],
|
||||
);
|
||||
const columns = useMemo<ColumnDef<T>[]>(
|
||||
() =>
|
||||
visibleHeads.map((head) => ({
|
||||
id: head.id,
|
||||
accessorFn: (row) => row[head.id],
|
||||
header: ROW_NUMBER_IDS.has(head.id) ? "순번" : head.title,
|
||||
cell: ({ row, getValue }) => {
|
||||
const rowIndex = row.index;
|
||||
const rendered = renderCell?.(row.original, head.id, rowIndex);
|
||||
const value = ROW_NUMBER_IDS.has(head.id)
|
||||
? getStandardRowNumber({
|
||||
total,
|
||||
rowIndex,
|
||||
pageIndex: page - 1,
|
||||
pageSize,
|
||||
})
|
||||
: rendered === undefined
|
||||
? formatCellValue(getValue())
|
||||
: rendered;
|
||||
const isClickable =
|
||||
Boolean(onCellClick) &&
|
||||
(!clickableHeadIds || clickableHeadIds.includes(head.id));
|
||||
|
||||
return isClickable ? (
|
||||
<button
|
||||
type="button"
|
||||
className="w-full cursor-pointer text-inherit"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onCellClick?.(row.original, rowIndex, head.id);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
) : (
|
||||
value
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
align:
|
||||
head.align === "left" || head.align === "right"
|
||||
? head.align
|
||||
: "center",
|
||||
width: head.widthPercent,
|
||||
},
|
||||
})),
|
||||
[
|
||||
clickableHeadIds,
|
||||
onCellClick,
|
||||
page,
|
||||
pageSize,
|
||||
renderCell,
|
||||
total,
|
||||
visibleHeads,
|
||||
],
|
||||
);
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: Math.max(page - 1, 0),
|
||||
pageSize,
|
||||
};
|
||||
const table = useReactTable({
|
||||
data: rows,
|
||||
columns,
|
||||
state: {
|
||||
pagination,
|
||||
rowSelection: selectedRowId ? { [selectedRowId]: true } : {},
|
||||
},
|
||||
rowCount: total,
|
||||
manualPagination: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getRowId: (row, index) => rowId?.(row, index) ?? String(index),
|
||||
onPaginationChange: (updater) => {
|
||||
const next =
|
||||
typeof updater === "function" ? updater(pagination) : updater;
|
||||
if (next.pageIndex === pagination.pageIndex) return;
|
||||
onPageChange(next.pageIndex + 1);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<StandardDataTable
|
||||
table={table}
|
||||
title={title}
|
||||
actions={actions}
|
||||
loading={loading}
|
||||
emptyMessage={emptyMessage}
|
||||
flushOuterMargin
|
||||
className="!w-full !max-w-full"
|
||||
scrollMode={scrollMode}
|
||||
minTableWidth={minTableWidth}
|
||||
viewPagination={viewPagination}
|
||||
fillAvailableHeight={fillAvailableHeight}
|
||||
onRowClick={
|
||||
onRowClick ? (row) => onRowClick(row, rows.indexOf(row)) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export type { MapPopupDataTableProps };
|
||||
Reference in New Issue
Block a user