feat: implement server-side sorting in report expense

This commit is contained in:
ValdiANS
2026-05-19 11:51:17 +07:00
parent 456070491f
commit fd7b49ab93
@@ -39,7 +39,7 @@ import {
} from '@/services/api/master-data'; } from '@/services/api/master-data';
import { Supplier } from '@/types/api/master-data/supplier'; import { Supplier } from '@/types/api/master-data/supplier';
import { Nonstock } from '@/types/api/master-data/nonstock'; import { Nonstock } from '@/types/api/master-data/nonstock';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef, SortingState, Updater } from '@tanstack/react-table';
import { httpClient } from '@/services/http/client'; import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general'; import { BaseApiResponse } from '@/types/api/api-general';
import ButtonFilter from '@/components/helper/ButtonFilter'; import ButtonFilter from '@/components/helper/ButtonFilter';
@@ -73,6 +73,25 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10); const [pageSize, setPageSize] = useState(10);
// ===== SORTING STATE =====
const [sortBy, setSortBy] = useState('');
const [orderBy, setOrderBy] = useState('');
const sorting: SortingState = sortBy
? [{ id: sortBy, desc: orderBy === 'desc' }]
: [];
const handleSortingChange = (updater: Updater<SortingState>) => {
const next = typeof updater === 'function' ? updater(sorting) : updater;
if (next.length > 0) {
setSortBy(next[0].id);
setOrderBy(next[0].desc ? 'desc' : 'asc');
} else {
setSortBy('');
setOrderBy('');
}
};
const handleFilterModalOpenRef = useRef(() => {}); const handleFilterModalOpenRef = useRef(() => {});
const filterModal = useModal(); const filterModal = useModal();
@@ -252,6 +271,8 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
if (filterParams.category) { if (filterParams.category) {
params.append('category', filterParams.category); params.append('category', filterParams.category);
} }
if (sortBy) params.append('sort_by', sortBy);
if (orderBy) params.append('sort_order', orderBy);
Object.entries(extraParams ?? {}).forEach(([key, value]) => { Object.entries(extraParams ?? {}).forEach(([key, value]) => {
params.set(key, value); params.set(key, value);
@@ -259,7 +280,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
return params.toString(); return params.toString();
}, },
[filterParams] [filterParams, sortBy, orderBy]
); );
// ===== DATA FETCHING ===== // ===== DATA FETCHING =====
@@ -443,19 +464,23 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
return [ return [
{ {
header: 'No', header: 'No',
enableSorting: false,
cell: (props) => (page - 1) * pageSize + props.row.index + 1, cell: (props) => (page - 1) * pageSize + props.row.index + 1,
}, },
{ {
header: 'No. PO', header: 'No. PO',
accessorKey: 'po_number', accessorKey: 'po_number',
enableSorting: true,
}, },
{ {
header: 'No. Referensi', header: 'No. Referensi',
accessorKey: 'reference_number', accessorKey: 'reference_number',
enableSorting: true,
}, },
{ {
header: 'Tanggal Realisasi', header: 'Tanggal Realisasi',
accessorKey: 'realization_date', accessorKey: 'realization_date',
enableSorting: true,
cell: ({ row }) => { cell: ({ row }) => {
return formatDate(row.original?.realization_date, 'DD MMM, YYYY'); return formatDate(row.original?.realization_date, 'DD MMM, YYYY');
}, },
@@ -463,6 +488,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
{ {
header: 'Tanggal Transaksi', header: 'Tanggal Transaksi',
accessorKey: 'transaction_date', accessorKey: 'transaction_date',
enableSorting: true,
cell: ({ row }) => { cell: ({ row }) => {
return formatDate(row.original?.transaction_date, 'DD MMM, YYYY'); return formatDate(row.original?.transaction_date, 'DD MMM, YYYY');
}, },
@@ -470,21 +496,30 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
{ {
header: 'Kategori', header: 'Kategori',
accessorKey: 'category', accessorKey: 'category',
enableSorting: true,
}, },
{ {
header: 'Produk', header: 'Produk',
accessorKey: 'product',
enableSorting: true,
accessorFn: (row) => row.pengajuan?.nonstock?.name, accessorFn: (row) => row.pengajuan?.nonstock?.name,
}, },
{ {
header: 'Supplier', header: 'Supplier',
accessorKey: 'supplier',
enableSorting: true,
accessorFn: (row) => row.supplier?.name, accessorFn: (row) => row.supplier?.name,
}, },
{ {
header: 'Lokasi', header: 'Lokasi',
accessorKey: 'location',
enableSorting: true,
accessorFn: (row) => row.kandang?.location?.name, accessorFn: (row) => row.kandang?.location?.name,
}, },
{ {
header: 'Kandang', header: 'Kandang',
accessorKey: 'kandang',
enableSorting: true,
accessorFn: (row) => row.kandang?.name, accessorFn: (row) => row.kandang?.name,
}, },
{ {
@@ -492,23 +527,19 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
columns: [ columns: [
{ {
header: 'Qty', header: 'Qty',
id: 'qty_pengajuan', accessorKey: 'qty_pengajuan',
accessorFn: (row) => row.pengajuan?.qty,
cell: ({ row }) => cell: ({ row }) =>
row.original.pengajuan?.qty?.toLocaleString('id-ID') || '0', row.original.pengajuan?.qty?.toLocaleString('id-ID') || '0',
}, },
{ {
header: 'Harga', header: 'Harga',
id: 'harga_pengajuan', accessorKey: 'price_pengajuan',
accessorFn: (row) => row.pengajuan?.price,
cell: ({ row }) => cell: ({ row }) =>
formatCurrency(row.original.pengajuan?.price || 0), formatCurrency(row.original.pengajuan?.price || 0),
}, },
{ {
header: 'Total', header: 'Total',
id: 'total_pengajuan', accessorKey: 'total_pengajuan',
accessorFn: (row) =>
(row.pengajuan?.qty || 0) * (row.pengajuan?.price || 0),
cell: ({ row }) => { cell: ({ row }) => {
const total = const total =
(row.original.pengajuan?.qty || 0) * (row.original.pengajuan?.qty || 0) *
@@ -523,23 +554,19 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
columns: [ columns: [
{ {
header: 'Qty', header: 'Qty',
id: 'qty_realisasi', accessorKey: 'qty_realisasi',
accessorFn: (row) => row.realisasi?.qty,
cell: ({ row }) => cell: ({ row }) =>
row.original.realisasi?.qty?.toLocaleString('id-ID') || '0', row.original.realisasi?.qty?.toLocaleString('id-ID') || '0',
}, },
{ {
header: 'Harga', header: 'Harga',
id: 'harga_realisasi', accessorKey: 'price_realisasi',
accessorFn: (row) => row.realisasi?.price,
cell: ({ row }) => cell: ({ row }) =>
formatCurrency(row.original.realisasi?.price || 0), formatCurrency(row.original.realisasi?.price || 0),
}, },
{ {
header: 'Total', header: 'Total',
id: 'total_realisasi', accessorKey: 'total_realisasi',
accessorFn: (row) =>
(row.realisasi?.qty || 0) * (row.realisasi?.price || 0),
cell: ({ row }) => { cell: ({ row }) => {
const total = const total =
(row.original.realisasi?.qty || 0) * (row.original.realisasi?.qty || 0) *
@@ -550,6 +577,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
], ],
}, },
{ {
id: 'realization_status',
header: 'Status Pencairan', header: 'Status Pencairan',
cell: (props) => ( cell: (props) => (
<RealizationStatusBadge <RealizationStatusBadge
@@ -558,6 +586,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
), ),
}, },
{ {
id: 'bop_status',
header: 'Status BOP', header: 'Status BOP',
cell: (props) => ( cell: (props) => (
<ExpenseStatusBadge approval={props.row.original?.latest_approval} /> <ExpenseStatusBadge approval={props.row.original?.latest_approval} />
@@ -602,6 +631,9 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
totalItems={meta?.total_results || 0} totalItems={meta?.total_results || 0}
onPageChange={setPage} onPageChange={setPage}
onPageSizeChange={setPageSize} onPageSizeChange={setPageSize}
sorting={sorting}
setSorting={handleSortingChange}
manualSorting
className={{ className={{
containerClassName: 'w-full mb-0!', containerClassName: 'w-full mb-0!',
tableWrapperClassName: 'overflow-x-auto', tableWrapperClassName: 'overflow-x-auto',