feat(FE-326,327): Add sortable table headers and styling

This commit is contained in:
rstubryan
2025-12-04 20:14:29 +07:00
parent b095208fae
commit 7d9a88cf3b
2 changed files with 113 additions and 51 deletions
+56 -4
View File
@@ -46,6 +46,7 @@ export interface CustomHeaderRow {
colSpan?: number; colSpan?: number;
rowSpan?: number; rowSpan?: number;
className?: string; className?: string;
field?: string;
}>; }>;
className?: string; className?: string;
} }
@@ -236,18 +237,69 @@ const Table = <TData extends object>({
headerRow.className || className.customHeaderRowClassName headerRow.className || className.customHeaderRowClassName
} }
> >
{headerRow.cells.map((cell) => ( {headerRow.cells.map((cell) => {
const column = table
.getAllColumns()
.find((col) => col.id === cell.field);
const canSort = column?.getCanSort();
const sortingState = column?.getIsSorted();
return (
<th <th
key={cell.id} key={cell.id}
colSpan={cell.colSpan} colSpan={cell.colSpan}
rowSpan={cell.rowSpan} rowSpan={cell.rowSpan}
className={ onClick={
cell.className || className.customHeaderCellClassName 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} {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> </th>
))} );
})}
</tr> </tr>
))} ))}
@@ -14,6 +14,15 @@ interface SalesReportTableProps {
initialValues?: BaseClosingSales; initialValues?: BaseClosingSales;
} }
interface HeaderCell {
id: string;
content: React.ReactNode;
colSpan?: number;
rowSpan?: number;
className: string;
field?: string;
}
const generateCustomHeaders = (template: { const generateCustomHeaders = (template: {
groups: Array<{ groups: Array<{
label: string; label: string;
@@ -41,31 +50,43 @@ const generateCustomHeaders = (template: {
template.groups.forEach((group) => { template.groups.forEach((group) => {
if (group.subLabels) { if (group.subLabels) {
mainRow.push({ const mainCell: HeaderCell = {
id: `${group.field || 'group'}-${subColumnIndex}`, id: `${group.field || 'group'}-${subColumnIndex}`,
content: group.label, content: group.label,
colSpan: group.colSpan, colSpan: group.colSpan,
className: className:
'px-4 py-3 text-xs font-semibold text-gray-700 text-center whitespace-nowrap border border-gray-300', '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) => { group.subLabels.forEach((subLabel) => {
subRow.push({ const subCell: HeaderCell = {
id: `sub-${subColumnIndex}`, id: `sub-${subColumnIndex}`,
content: subLabel, content: subLabel,
className: className:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left whitespace-nowrap border border-gray-300 border-t-0', '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 === 'Ekor' ? 'quantity' : 'weight';
}
subRow.push(subCell);
subColumnIndex++; subColumnIndex++;
}); });
} else { } else {
mainRow.push({ const mainCell: HeaderCell = {
id: `${group.field}-header`, id: `${group.field}-header`,
content: group.label, content: group.label,
rowSpan: group.rowSpan, rowSpan: group.rowSpan,
className: className:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left whitespace-nowrap border border-gray-300', '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);
} }
}); });
@@ -371,7 +392,7 @@ const SalesReportTable = ({
}, },
{ label: 'AVG (Kg)', field: 'average', rowSpan: 2 }, { label: 'AVG (Kg)', field: 'average', rowSpan: 2 },
{ label: 'Harga Mitra (Rp)', field: 'price_partner', rowSpan: 2 }, { label: 'Harga Mitra (Rp)', field: 'price_partner', rowSpan: 2 },
{ label: 'Total Mitra (Rp)', field: 'total_partner', rowSpan: 2 }, { label: 'Total Mitra (Rp)', field: 'total_mitra', rowSpan: 2 },
{ label: 'Harga Act (Rp)', field: 'price_act', rowSpan: 2 }, { label: 'Harga Act (Rp)', field: 'price_act', rowSpan: 2 },
{ label: 'Total Act (Rp)', field: 'total_act', rowSpan: 2 }, { label: 'Total Act (Rp)', field: 'total_act', rowSpan: 2 },
{ label: 'Kandang', field: 'kandang', rowSpan: 2 }, { label: 'Kandang', field: 'kandang', rowSpan: 2 },
@@ -413,49 +434,37 @@ const SalesReportTable = ({
renderFooter={salesBroilerData.length > 0} renderFooter={salesBroilerData.length > 0}
footerContent={ footerContent={
<tfoot> <tfoot>
<tr className='bg-gray-100 font-semibold'> <tr className='bg-gray-100 font-semibold border border-gray-200'>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300 border-t-0'> <td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap '>
Total Penjualan Total Penjualan
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300 border-t-0'> <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> <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 border border-gray-300 border-t-0'> <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>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300 border-t-0'>
-
</td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300 border-t-0'>
-
</td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right border border-gray-300 border-t-0'>
{formatNumber(totals.totalQuantity)} {formatNumber(totals.totalQuantity)}
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right border border-gray-300 border-t-0'> <td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
{formatNumber(totals.totalWeight)} {formatNumber(totals.totalWeight)}
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right border border-gray-300 border-t-0'> <td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
{formatNumber(totals.avgWeight)} {formatNumber(totals.avgWeight)}
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right border border-gray-300 border-t-0'> <td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
{formatCurrency(totals.avgPricePartner)} {formatCurrency(totals.avgPricePartner)}
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right border border-gray-300 border-t-0'> <td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
{formatCurrency(totals.totalPartner)} {formatCurrency(totals.totalPartner)}
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right border border-gray-300 border-t-0'> <td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
{formatCurrency(totals.avgPriceAct)} {formatCurrency(totals.avgPriceAct)}
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right border border-gray-300 border-t-0'> <td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap text-right '>
{formatCurrency(totals.totalAct)} {formatCurrency(totals.totalAct)}
</td> </td>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300 border-t-0'> <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>
<td className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300 border-t-0'>
-
</td>
</tr> </tr>
</tfoot> </tfoot>
} }
@@ -463,9 +472,10 @@ const SalesReportTable = ({
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: 'hidden',
bodyRowClassName: 'hover:bg-gray-50 transition-colors', 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',
bodyColumnClassName: bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap border border-gray-300', 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
/> />
</Card> </Card>