mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 23:05:46 +00:00
refactor(FE-326): Remove custom header rows and simplify Table
This commit is contained in:
+45
-163
@@ -14,8 +14,6 @@ import {
|
|||||||
SortingState,
|
SortingState,
|
||||||
OnChangeFn,
|
OnChangeFn,
|
||||||
Row,
|
Row,
|
||||||
HeaderGroup,
|
|
||||||
Column,
|
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { rankItem } from '@tanstack/match-sorter-utils';
|
import { rankItem } from '@tanstack/match-sorter-utils';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -34,21 +32,6 @@ interface TableClassNames {
|
|||||||
bodyRowClassName?: string;
|
bodyRowClassName?: string;
|
||||||
bodyColumnClassName?: string;
|
bodyColumnClassName?: string;
|
||||||
paginationClassName?: string;
|
paginationClassName?: string;
|
||||||
customHeaderRowClassName?: string;
|
|
||||||
customHeaderCellClassName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomHeaderRow {
|
|
||||||
id: string;
|
|
||||||
cells: Array<{
|
|
||||||
id: string;
|
|
||||||
content: ReactNode;
|
|
||||||
colSpan?: number;
|
|
||||||
rowSpan?: number;
|
|
||||||
className?: string;
|
|
||||||
field?: string;
|
|
||||||
}>;
|
|
||||||
className?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableProps<TData extends object> {
|
export interface TableProps<TData extends object> {
|
||||||
@@ -69,13 +52,6 @@ export interface TableProps<TData extends object> {
|
|||||||
rowSelection?: Record<string, boolean>;
|
rowSelection?: Record<string, boolean>;
|
||||||
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
setRowSelection?: OnChangeFn<Record<string, boolean>>;
|
||||||
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
||||||
customHeaderRows?: CustomHeaderRow[];
|
|
||||||
renderCustomHeaders?: boolean;
|
|
||||||
onCustomHeaderCellRender?: <TData extends object>(
|
|
||||||
cell: ReactNode,
|
|
||||||
column: Column<TData, unknown>,
|
|
||||||
headerGroup: HeaderGroup<TData>
|
|
||||||
) => ReactNode;
|
|
||||||
renderFooter?: boolean;
|
renderFooter?: boolean;
|
||||||
footerContent?: ReactNode;
|
footerContent?: ReactNode;
|
||||||
}
|
}
|
||||||
@@ -111,8 +87,6 @@ const Table = <TData extends object>({
|
|||||||
bodyRowClassName: '',
|
bodyRowClassName: '',
|
||||||
bodyColumnClassName: '',
|
bodyColumnClassName: '',
|
||||||
paginationClassName: '',
|
paginationClassName: '',
|
||||||
customHeaderRowClassName: '',
|
|
||||||
customHeaderCellClassName: '',
|
|
||||||
},
|
},
|
||||||
emptyContent = emptyContentDefaultValue,
|
emptyContent = emptyContentDefaultValue,
|
||||||
sorting,
|
sorting,
|
||||||
@@ -121,9 +95,6 @@ const Table = <TData extends object>({
|
|||||||
rowSelection,
|
rowSelection,
|
||||||
setRowSelection,
|
setRowSelection,
|
||||||
enableRowSelection,
|
enableRowSelection,
|
||||||
customHeaderRows = [],
|
|
||||||
renderCustomHeaders = false,
|
|
||||||
onCustomHeaderCellRender,
|
|
||||||
renderFooter = false,
|
renderFooter = false,
|
||||||
footerContent,
|
footerContent,
|
||||||
}: TableProps<TData>) => {
|
}: TableProps<TData>) => {
|
||||||
@@ -228,143 +199,55 @@ const Table = <TData extends object>({
|
|||||||
<div className={className.tableWrapperClassName}>
|
<div className={className.tableWrapperClassName}>
|
||||||
<table className={className.tableClassName}>
|
<table className={className.tableClassName}>
|
||||||
<thead className={className.tableHeaderClassName}>
|
<thead className={className.tableHeaderClassName}>
|
||||||
{renderCustomHeaders &&
|
|
||||||
customHeaderRows.length > 0 &&
|
|
||||||
customHeaderRows.map((headerRow) => (
|
|
||||||
<tr
|
|
||||||
key={headerRow.id}
|
|
||||||
className={
|
|
||||||
headerRow.className || className.customHeaderRowClassName
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{headerRow.cells.map((cell) => {
|
|
||||||
const column = table
|
|
||||||
.getAllColumns()
|
|
||||||
.find((col) => col.id === cell.field);
|
|
||||||
|
|
||||||
const canSort = column?.getCanSort();
|
|
||||||
const sortingState = column?.getIsSorted();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<th
|
|
||||||
key={cell.id}
|
|
||||||
colSpan={cell.colSpan}
|
|
||||||
rowSpan={cell.rowSpan}
|
|
||||||
onClick={
|
|
||||||
canSort
|
|
||||||
? column?.getToggleSortingHandler()
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
className={cn(
|
|
||||||
cell.className || className.customHeaderCellClassName,
|
|
||||||
canSort ? 'cursor-pointer select-none' : ''
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'flex items-center gap-1',
|
|
||||||
cell.className?.includes('text-center')
|
|
||||||
? 'justify-center'
|
|
||||||
: ''
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{cell.content}
|
|
||||||
|
|
||||||
{canSort && (
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<Icon
|
|
||||||
icon='lucide:arrow-up'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
className={cn(
|
|
||||||
'transition-all ease-in-out duration-200',
|
|
||||||
sortingState === 'asc'
|
|
||||||
? 'text-black'
|
|
||||||
: 'text-black/30'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
icon='lucide:arrow-down'
|
|
||||||
width={12}
|
|
||||||
height={12}
|
|
||||||
className={cn(
|
|
||||||
'transition-all ease-in-out duration-200',
|
|
||||||
sortingState === 'desc'
|
|
||||||
? 'text-black'
|
|
||||||
: 'text-black/30'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<tr key={headerGroup.id} className={className.headerRowClassName}>
|
<tr key={headerGroup.id} className={className.headerRowClassName}>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => (
|
||||||
let cellContent = flexRender(
|
<th
|
||||||
header.column.columnDef.header,
|
key={header.id}
|
||||||
header.getContext()
|
colSpan={header.colSpan}
|
||||||
);
|
onClick={header.column.getToggleSortingHandler()}
|
||||||
|
className={cn(
|
||||||
if (onCustomHeaderCellRender) {
|
header.column.getCanSort()
|
||||||
cellContent = onCustomHeaderCellRender(
|
? 'cursor-pointer select-none'
|
||||||
cellContent,
|
: '',
|
||||||
header.column,
|
className.headerColumnClassName
|
||||||
headerGroup
|
)}
|
||||||
);
|
>
|
||||||
}
|
<div className='flex items-center gap-1'>
|
||||||
|
{flexRender(
|
||||||
return (
|
header.column.columnDef.header,
|
||||||
<th
|
header.getContext()
|
||||||
key={header.id}
|
|
||||||
colSpan={header.colSpan}
|
|
||||||
rowSpan={header.rowSpan}
|
|
||||||
onClick={header.column.getToggleSortingHandler()}
|
|
||||||
className={cn(
|
|
||||||
header.column.getCanSort()
|
|
||||||
? 'cursor-pointer select-none'
|
|
||||||
: '',
|
|
||||||
className.headerColumnClassName
|
|
||||||
)}
|
)}
|
||||||
>
|
|
||||||
<div className='flex items-center gap-1'>
|
|
||||||
{cellContent}
|
|
||||||
|
|
||||||
{header.column.getCanSort() && (
|
{header.column.getCanSort() && (
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<Icon
|
<Icon
|
||||||
icon='lucide:arrow-up'
|
icon='lucide:arrow-up'
|
||||||
width={12}
|
width={12}
|
||||||
height={12}
|
height={12}
|
||||||
className={cn(
|
className={cn(
|
||||||
'transition-all ease-in-out duration-200',
|
'transition-all ease-in-out duration-200',
|
||||||
header.column.getIsSorted() === 'asc'
|
header.column.getIsSorted() === 'asc'
|
||||||
? 'text-black'
|
? 'text-black'
|
||||||
: 'text-black/30'
|
: 'text-black/30'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
icon='lucide:arrow-down'
|
icon='lucide:arrow-down'
|
||||||
width={12}
|
width={12}
|
||||||
height={12}
|
height={12}
|
||||||
className={cn(
|
className={cn(
|
||||||
'transition-all ease-in-out duration-200',
|
'transition-all ease-in-out duration-200',
|
||||||
header.column.getIsSorted() === 'desc'
|
header.column.getIsSorted() === 'desc'
|
||||||
? 'text-black'
|
? 'text-black'
|
||||||
: 'text-black/30'
|
: 'text-black/30'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
);
|
))}
|
||||||
})}
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</thead>
|
</thead>
|
||||||
@@ -383,7 +266,6 @@ const Table = <TData extends object>({
|
|||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
{renderFooter && footerContent}
|
{renderFooter && footerContent}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
import Table, { CustomHeaderRow } from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import Card from '@/components/Card';
|
import Card from '@/components/Card';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
|
import { formatCurrency, formatNumber, formatDate } from '@/lib/helper';
|
||||||
@@ -16,101 +16,6 @@ interface SalesReportTableProps {
|
|||||||
initialValues?: BaseClosingSales;
|
initialValues?: BaseClosingSales;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderCell {
|
|
||||||
id: string;
|
|
||||||
content: React.ReactNode;
|
|
||||||
colSpan?: number;
|
|
||||||
rowSpan?: number;
|
|
||||||
className: string;
|
|
||||||
field?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateCustomHeaders = (template: {
|
|
||||||
groups: Array<{
|
|
||||||
label: string;
|
|
||||||
field?: string;
|
|
||||||
rowSpan?: number;
|
|
||||||
colSpan?: number;
|
|
||||||
subLabels?: string[];
|
|
||||||
}>;
|
|
||||||
}): CustomHeaderRow[] => {
|
|
||||||
const mainRow: Array<{
|
|
||||||
id: string;
|
|
||||||
content: React.ReactNode;
|
|
||||||
colSpan?: number;
|
|
||||||
rowSpan?: number;
|
|
||||||
className: string;
|
|
||||||
}> = [];
|
|
||||||
const subRow: Array<{
|
|
||||||
id: string;
|
|
||||||
content: React.ReactNode;
|
|
||||||
colSpan?: number;
|
|
||||||
rowSpan?: number;
|
|
||||||
className: string;
|
|
||||||
}> = [];
|
|
||||||
let subColumnIndex = 0;
|
|
||||||
|
|
||||||
template.groups.forEach((group) => {
|
|
||||||
if (group.subLabels) {
|
|
||||||
const mainCell: HeaderCell = {
|
|
||||||
id: `${group.field || 'group'}-${subColumnIndex}`,
|
|
||||||
content: group.label,
|
|
||||||
colSpan: group.colSpan,
|
|
||||||
className:
|
|
||||||
'px-4 py-3 text-xs font-semibold text-gray-700 text-center whitespace-nowrap border border-gray-200',
|
|
||||||
};
|
|
||||||
|
|
||||||
mainRow.push(mainCell);
|
|
||||||
|
|
||||||
group.subLabels.forEach((subLabel) => {
|
|
||||||
const subCell: HeaderCell = {
|
|
||||||
id: `sub-${subColumnIndex}`,
|
|
||||||
content: subLabel,
|
|
||||||
className:
|
|
||||||
'px-4 py-3 text-xs font-semibold text-gray-700 text-left whitespace-nowrap border border-gray-200',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (group.label === 'Jumlah') {
|
|
||||||
subCell.field = subLabel === 'Kuantitas' ? 'quantity' : 'weight';
|
|
||||||
}
|
|
||||||
|
|
||||||
subRow.push(subCell);
|
|
||||||
subColumnIndex++;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const mainCell: HeaderCell = {
|
|
||||||
id: `${group.field}-header`,
|
|
||||||
content: group.label,
|
|
||||||
rowSpan: group.rowSpan,
|
|
||||||
className:
|
|
||||||
'px-4 py-3 text-xs font-semibold text-gray-700 text-left whitespace-nowrap border border-gray-200',
|
|
||||||
};
|
|
||||||
|
|
||||||
mainCell.field = group.field;
|
|
||||||
|
|
||||||
mainRow.push(mainCell);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const rows: CustomHeaderRow[] = [
|
|
||||||
{
|
|
||||||
id: 'main-header',
|
|
||||||
cells: mainRow,
|
|
||||||
className: 'bg-gray-50',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (subRow.length > 0) {
|
|
||||||
rows.push({
|
|
||||||
id: 'sub-header',
|
|
||||||
cells: subRow,
|
|
||||||
className: 'bg-gray-50',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SalesReportTable = ({
|
const SalesReportTable = ({
|
||||||
type = 'detail',
|
type = 'detail',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -310,7 +215,7 @@ const SalesReportTable = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge variant='soft' size='xs' color={getStatusColor(status)}>
|
<Badge variant='soft' size='sm' color={getStatusColor(status)}>
|
||||||
{status || '-'}
|
{status || '-'}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
@@ -320,33 +225,6 @@ const SalesReportTable = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const headerTemplate = {
|
|
||||||
groups: [
|
|
||||||
{ label: 'Tanggal Realisasi', field: 'realization_date', rowSpan: 2 },
|
|
||||||
{ label: 'Umur', field: 'age', rowSpan: 2 },
|
|
||||||
{ label: 'No. DO', field: 'do_number', rowSpan: 2 },
|
|
||||||
{ label: 'Produk', field: 'product', rowSpan: 2 },
|
|
||||||
{ label: 'Customer', field: 'customer', rowSpan: 2 },
|
|
||||||
{
|
|
||||||
label: 'Jumlah',
|
|
||||||
colSpan: 2,
|
|
||||||
subLabels: ['Qty', 'Kg'],
|
|
||||||
},
|
|
||||||
{ label: 'AVG (Kg)', field: 'avg_weight', rowSpan: 2 },
|
|
||||||
{ label: 'Harga Mitra (Rp)', field: 'price_partner', rowSpan: 2 },
|
|
||||||
{ label: 'Total Mitra (Rp)', field: 'total_mitra', rowSpan: 2 },
|
|
||||||
{ label: 'Harga Act (Rp)', field: 'price_act', rowSpan: 2 },
|
|
||||||
{ label: 'Total Act (Rp)', field: 'total_act', rowSpan: 2 },
|
|
||||||
{ label: 'Kandang', field: 'kandang', rowSpan: 2 },
|
|
||||||
{ label: 'Status Pembayaran', field: 'payment_status', rowSpan: 2 },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const salesCustomHeaderRows = useMemo(
|
|
||||||
() => generateCustomHeaders(headerTemplate),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full'>
|
<section className='w-full'>
|
||||||
@@ -361,8 +239,6 @@ const SalesReportTable = ({
|
|||||||
<Table
|
<Table
|
||||||
data={salesData}
|
data={salesData}
|
||||||
columns={salesColumns}
|
columns={salesColumns}
|
||||||
renderCustomHeaders={true}
|
|
||||||
customHeaderRows={salesCustomHeaderRows}
|
|
||||||
renderFooter={salesData.length > 0}
|
renderFooter={salesData.length > 0}
|
||||||
footerContent={
|
footerContent={
|
||||||
<tfoot>
|
<tfoot>
|
||||||
@@ -374,13 +250,13 @@ const SalesReportTable = ({
|
|||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '></td>
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
|
||||||
{formatNumber(totals.totalQuantity)}
|
{formatNumber(totals.totalQuantity)}
|
||||||
</td>
|
</td>
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
|
||||||
{formatNumber(totals.totalWeight)}
|
{formatNumber(totals.totalWeight)}
|
||||||
</td>
|
</td>
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
|
||||||
{formatNumber(totals.avgWeight)}
|
{formatNumber(totals.avgWeight)}
|
||||||
</td>
|
</td>
|
||||||
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
|
||||||
@@ -403,7 +279,9 @@ const SalesReportTable = ({
|
|||||||
className={{
|
className={{
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
tableClassName: 'w-full table-auto text-sm',
|
tableClassName: 'w-full table-auto text-sm',
|
||||||
headerRowClassName: 'hidden',
|
headerRowClassName: 'border-b border-b-gray-200',
|
||||||
|
headerColumnClassName:
|
||||||
|
'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap',
|
||||||
bodyRowClassName:
|
bodyRowClassName:
|
||||||
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
||||||
bodyColumnClassName:
|
bodyColumnClassName:
|
||||||
|
|||||||
Reference in New Issue
Block a user