mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 23:35:45 +00:00
feat(FE-326,327): Add sortable table headers and styling
This commit is contained in:
+64
-12
@@ -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) => {
|
||||||
<th
|
const column = table
|
||||||
key={cell.id}
|
.getAllColumns()
|
||||||
colSpan={cell.colSpan}
|
.find((col) => col.id === cell.field);
|
||||||
rowSpan={cell.rowSpan}
|
|
||||||
className={
|
const canSort = column?.getCanSort();
|
||||||
cell.className || className.customHeaderCellClassName
|
const sortingState = column?.getIsSorted();
|
||||||
}
|
|
||||||
>
|
return (
|
||||||
{cell.content}
|
<th
|
||||||
</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>
|
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user