mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'feat/server-side-sorting-purchasing-expense' into 'development'
[FEAT/FE] Server-Side Sorting Purchasing & Expense See merge request mbugroup/lti-web-client!475
This commit is contained in:
@@ -1,18 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||||
ChangeEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import {
|
import {
|
||||||
CellContext,
|
CellContext,
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
Row,
|
Row,
|
||||||
SortingState,
|
SortingState,
|
||||||
|
Updater,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
@@ -47,7 +42,8 @@ import { BaseApiResponse } from '@/types/api/api-general';
|
|||||||
|
|
||||||
type ExpenseTableFilters = {
|
type ExpenseTableFilters = {
|
||||||
search: string;
|
search: string;
|
||||||
nameSort: string;
|
sort_by: string;
|
||||||
|
order_by: string;
|
||||||
transactionDate: string;
|
transactionDate: string;
|
||||||
realizationDate: string;
|
realizationDate: string;
|
||||||
locationId: string;
|
locationId: string;
|
||||||
@@ -242,7 +238,8 @@ const ExpensesTable = () => {
|
|||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
search: '',
|
search: '',
|
||||||
nameSort: '',
|
sort_by: '',
|
||||||
|
order_by: '',
|
||||||
transactionDate: '',
|
transactionDate: '',
|
||||||
realizationDate: '',
|
realizationDate: '',
|
||||||
locationId: '',
|
locationId: '',
|
||||||
@@ -261,7 +258,8 @@ const ExpensesTable = () => {
|
|||||||
paramMap: {
|
paramMap: {
|
||||||
page: 'page',
|
page: 'page',
|
||||||
pageSize: 'limit',
|
pageSize: 'limit',
|
||||||
nameSort: 'sort_name',
|
sort_by: 'sort_by',
|
||||||
|
order_by: 'sort_order',
|
||||||
transactionDate: 'transaction_date',
|
transactionDate: 'transaction_date',
|
||||||
realizationDate: 'realization_date',
|
realizationDate: 'realization_date',
|
||||||
locationId: 'location_id',
|
locationId: 'location_id',
|
||||||
@@ -319,7 +317,26 @@ const ExpensesTable = () => {
|
|||||||
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
||||||
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const sorting: SortingState = tableFilterState.sort_by
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: tableFilterState.sort_by,
|
||||||
|
desc: tableFilterState.order_by === 'desc',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const handleSortingChange = (updater: Updater<SortingState>) => {
|
||||||
|
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
|
if (next.length > 0) {
|
||||||
|
updateFilter('sort_by', next[0].id, true);
|
||||||
|
updateFilter('order_by', next[0].desc ? 'desc' : 'asc', true);
|
||||||
|
} else {
|
||||||
|
updateFilter('sort_by', '', true);
|
||||||
|
updateFilter('order_by', '', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||||
const selectedRowIds = Object.keys(rowSelection).map((item) =>
|
const selectedRowIds = Object.keys(rowSelection).map((item) =>
|
||||||
parseInt(item)
|
parseInt(item)
|
||||||
@@ -437,10 +454,12 @@ const ExpensesTable = () => {
|
|||||||
cell: (props) => props.row.original.location?.name ?? '-',
|
cell: (props) => props.row.original.location?.name ?? '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
accessorKey: 'created_user',
|
||||||
accessorFn: (row) => row.created_user.name ?? '-',
|
accessorFn: (row) => row.created_user.name ?? '-',
|
||||||
header: 'Nama Pengaju',
|
header: 'Nama Pengaju',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
accessorKey: 'supplier',
|
||||||
accessorFn: (row) => row.supplier.name ?? '-',
|
accessorFn: (row) => row.supplier.name ?? '-',
|
||||||
header: 'Uraian',
|
header: 'Uraian',
|
||||||
},
|
},
|
||||||
@@ -454,17 +473,20 @@ const ExpensesTable = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status Pencairan',
|
header: 'Status Pencairan',
|
||||||
|
enableSorting: false,
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<RealizationStatusBadge approval={props.row.original.latest_approval} />
|
<RealizationStatusBadge approval={props.row.original.latest_approval} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status BOP',
|
header: 'Status BOP',
|
||||||
|
enableSorting: false,
|
||||||
cell: (props) => (
|
cell: (props) => (
|
||||||
<ExpenseStatusBadge approval={props.row.original.latest_approval} />
|
<ExpenseStatusBadge approval={props.row.original.latest_approval} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
accessorKey: 'is_paid',
|
||||||
header: 'Status Lunas',
|
header: 'Status Lunas',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
return (
|
return (
|
||||||
@@ -478,6 +500,14 @@ const ExpensesTable = () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: 'Tanggal Dibuat',
|
||||||
|
cell: (props) =>
|
||||||
|
props.row.original.created_at
|
||||||
|
? formatDate(props.row.original.created_at, 'DD MMM YYYY')
|
||||||
|
: '-',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: 'Aksi',
|
header: 'Aksi',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
@@ -882,17 +912,6 @@ const ExpensesTable = () => {
|
|||||||
}
|
}
|
||||||
}, [getTableFilterQueryString]);
|
}, [getTableFilterQueryString]);
|
||||||
|
|
||||||
// track sorting
|
|
||||||
useEffect(() => {
|
|
||||||
const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
|
|
||||||
|
|
||||||
if (!isNameSorted) {
|
|
||||||
updateFilter('nameSort', '', false);
|
|
||||||
} else {
|
|
||||||
updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
|
|
||||||
}
|
|
||||||
}, [sorting, updateFilter]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
@@ -1051,7 +1070,8 @@ const ExpensesTable = () => {
|
|||||||
'page',
|
'page',
|
||||||
'pageSize',
|
'pageSize',
|
||||||
'search',
|
'search',
|
||||||
'nameSort',
|
'sort_by',
|
||||||
|
'order_by',
|
||||||
'userId',
|
'userId',
|
||||||
'locationName',
|
'locationName',
|
||||||
'vendorName',
|
'vendorName',
|
||||||
@@ -1152,7 +1172,8 @@ const ExpensesTable = () => {
|
|||||||
onPageSizeChange={setPageSize}
|
onPageSizeChange={setPageSize}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={handleSortingChange}
|
||||||
|
manualSorting
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
setRowSelection={setRowSelection}
|
setRowSelection={setRowSelection}
|
||||||
enableRowSelection={tableEnableRowSelectionHandler}
|
enableRowSelection={tableEnableRowSelectionHandler}
|
||||||
|
|||||||
@@ -772,6 +772,14 @@ const MarketingTable = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: 'Tanggal Dibuat',
|
||||||
|
cell: (props) =>
|
||||||
|
props.row.original.created_at
|
||||||
|
? formatDate(props.row.original.created_at, 'DD MMM yyyy')
|
||||||
|
: '-',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
maxSize: 80,
|
maxSize: 80,
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
import {
|
||||||
|
CellContext,
|
||||||
|
ColumnDef,
|
||||||
|
SortingState,
|
||||||
|
Updater,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@@ -34,6 +39,8 @@ import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
|
|||||||
|
|
||||||
type PurchaseTableFilters = {
|
type PurchaseTableFilters = {
|
||||||
search: string;
|
search: string;
|
||||||
|
sort_by: string;
|
||||||
|
order_by: string;
|
||||||
po_date: string;
|
po_date: string;
|
||||||
approval_status: string;
|
approval_status: string;
|
||||||
product_category_id: string;
|
product_category_id: string;
|
||||||
@@ -157,18 +164,6 @@ const RowOptionsMenu = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PurchaseTable = () => {
|
const PurchaseTable = () => {
|
||||||
// ===== STATE MANAGEMENT =====
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
|
||||||
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
|
||||||
useState(false);
|
|
||||||
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
|
||||||
const [selectedPurchase, setSelectedPurchase] = useState<Purchase | null>(
|
|
||||||
null
|
|
||||||
);
|
|
||||||
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
|
||||||
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
|
||||||
|
|
||||||
// ===== TABLE FILTER STATE =====
|
// ===== TABLE FILTER STATE =====
|
||||||
const {
|
const {
|
||||||
state: tableFilterState,
|
state: tableFilterState,
|
||||||
@@ -180,6 +175,8 @@ const PurchaseTable = () => {
|
|||||||
} = useTableFilter<PurchaseTableFilters>({
|
} = useTableFilter<PurchaseTableFilters>({
|
||||||
initial: {
|
initial: {
|
||||||
search: '',
|
search: '',
|
||||||
|
sort_by: '',
|
||||||
|
order_by: '',
|
||||||
po_date: '',
|
po_date: '',
|
||||||
approval_status: '',
|
approval_status: '',
|
||||||
product_category_id: '',
|
product_category_id: '',
|
||||||
@@ -198,6 +195,8 @@ const PurchaseTable = () => {
|
|||||||
paramMap: {
|
paramMap: {
|
||||||
page: 'page',
|
page: 'page',
|
||||||
pageSize: 'limit',
|
pageSize: 'limit',
|
||||||
|
sort_by: 'sort_by',
|
||||||
|
order_by: 'sort_order',
|
||||||
po_date: 'po_date',
|
po_date: 'po_date',
|
||||||
approval_status: 'approval_status',
|
approval_status: 'approval_status',
|
||||||
product_category_id: 'product_category_id',
|
product_category_id: 'product_category_id',
|
||||||
@@ -219,6 +218,36 @@ const PurchaseTable = () => {
|
|||||||
storeName: 'purchase-table',
|
storeName: 'purchase-table',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ===== STATE MANAGEMENT =====
|
||||||
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||||
|
useState(false);
|
||||||
|
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
||||||
|
const [selectedPurchase, setSelectedPurchase] = useState<Purchase | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
||||||
|
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
||||||
|
const sorting: SortingState = tableFilterState.sort_by
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: tableFilterState.sort_by,
|
||||||
|
desc: tableFilterState.order_by === 'desc',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const handleSortingChange = (updater: Updater<SortingState>) => {
|
||||||
|
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
|
if (next.length > 0) {
|
||||||
|
updateFilter('sort_by', next[0].id, true);
|
||||||
|
updateFilter('order_by', next[0].desc ? 'desc' : 'asc', true);
|
||||||
|
} else {
|
||||||
|
updateFilter('sort_by', '', true);
|
||||||
|
updateFilter('order_by', '', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ===== MODAL HOOKS =====
|
// ===== MODAL HOOKS =====
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
@@ -238,6 +267,7 @@ const PurchaseTable = () => {
|
|||||||
const purchaseColumns: ColumnDef<Purchase>[] = [
|
const purchaseColumns: ColumnDef<Purchase>[] = [
|
||||||
{
|
{
|
||||||
header: 'No. PR/PO',
|
header: 'No. PR/PO',
|
||||||
|
enableSorting: false,
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const { pr_number, po_number } = props.row.original;
|
const { pr_number, po_number } = props.row.original;
|
||||||
return po_number ? po_number : pr_number;
|
return po_number ? po_number : pr_number;
|
||||||
@@ -278,7 +308,7 @@ const PurchaseTable = () => {
|
|||||||
cell: (props) => props.row.original.requester_name || '-',
|
cell: (props) => props.row.original.requester_name || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'products.name',
|
accessorKey: 'products',
|
||||||
header: 'Produk',
|
header: 'Produk',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const products = props.row.original.products;
|
const products = props.row.original.products;
|
||||||
@@ -293,7 +323,7 @@ const PurchaseTable = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'location.name',
|
accessorKey: 'location',
|
||||||
header: 'Lokasi',
|
header: 'Lokasi',
|
||||||
cell: (props) => props.row.original.location?.name || '-',
|
cell: (props) => props.row.original.location?.name || '-',
|
||||||
},
|
},
|
||||||
@@ -323,6 +353,7 @@ const PurchaseTable = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Aging',
|
header: 'Aging',
|
||||||
|
enableSorting: false,
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const purchase = props.row.original;
|
const purchase = props.row.original;
|
||||||
if (!purchase.po_date) return '-';
|
if (!purchase.po_date) return '-';
|
||||||
@@ -334,6 +365,7 @@ const PurchaseTable = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
header: 'Status Approval',
|
header: 'Status Approval',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const approval = props.row.original.latest_approval;
|
const approval = props.row.original.latest_approval;
|
||||||
@@ -378,6 +410,14 @@ const PurchaseTable = () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: 'Tanggal Dibuat',
|
||||||
|
cell: (props) =>
|
||||||
|
props.row.original.created_at
|
||||||
|
? formatDate(props.row.original.created_at, 'DD MMM YYYY')
|
||||||
|
: '-',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: 'Aksi',
|
header: 'Aksi',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
@@ -658,6 +698,7 @@ const PurchaseTable = () => {
|
|||||||
'search',
|
'search',
|
||||||
'filter_by',
|
'filter_by',
|
||||||
'sort_by',
|
'sort_by',
|
||||||
|
'order_by',
|
||||||
'product_category_name',
|
'product_category_name',
|
||||||
'supplier_name',
|
'supplier_name',
|
||||||
'area_name',
|
'area_name',
|
||||||
@@ -771,7 +812,8 @@ const PurchaseTable = () => {
|
|||||||
onPageSizeChange={setPageSize}
|
onPageSizeChange={setPageSize}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={handleSortingChange}
|
||||||
|
manualSorting
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn('p-3 mb-0'),
|
containerClassName: cn('p-3 mb-0'),
|
||||||
headerColumnClassName: 'text-nowrap',
|
headerColumnClassName: 'text-nowrap',
|
||||||
|
|||||||
Reference in New Issue
Block a user