fix: persist purchase table and set initial value to PurchaseFilterModal

This commit is contained in:
ValdiANS
2026-04-23 16:21:45 +07:00
parent a316120a78
commit c487e7f53e
2 changed files with 226 additions and 108 deletions
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { RefObject, useState, useEffect, useMemo } from 'react'; import { RefObject, useState, useEffect, useMemo, useCallback } from 'react';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -26,22 +26,32 @@ import { isResponseSuccess } from '@/lib/api-helper';
interface PurchaseFilterModalProps { interface PurchaseFilterModalProps {
ref: RefObject<HTMLDialogElement | null>; ref: RefObject<HTMLDialogElement | null>;
initialValues?: {
poDate: string;
category: OptionType<number>[];
status: OptionType<string>[];
supplier: OptionType<number> | null;
area: OptionType<number> | null;
location: OptionType<number> | null;
project_flock: OptionType<number> | null;
project_flock_kandang: OptionType<number> | null;
};
onSubmit?: (values: PurchaseFilter) => void; onSubmit?: (values: PurchaseFilter) => void;
onReset?: () => void; onReset?: () => void;
} }
const PurchaseFilterModal = ({ const PurchaseFilterModal = ({
ref, ref,
initialValues,
onSubmit, onSubmit,
onReset, onReset,
}: PurchaseFilterModalProps) => { }: PurchaseFilterModalProps) => {
const closeModalHandler = () => { const closeModalHandler = useCallback(() => {
ref.current?.close(); ref.current?.close();
}; }, [ref]);
// ===== DATE ERROR STATE ===== // ===== DATE ERROR STATE =====
const [dateErrorShown, setDateErrorShown] = useState(false); const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false);
// ===== CLEANUP TOAST ON UNMOUNT ===== // ===== CLEANUP TOAST ON UNMOUNT =====
useEffect(() => { useEffect(() => {
@@ -81,8 +91,12 @@ const PurchaseFilterModal = ({
'search' 'search'
); );
const [selectedAreaId, setSelectedAreaId] = useState(''); const [selectedAreaId, setSelectedAreaId] = useState(
const [selectedLocationId, setSelectedLocationId] = useState(''); initialValues?.area?.value ? String(initialValues.area.value) : ''
);
const [selectedLocationId, setSelectedLocationId] = useState(
initialValues?.location?.value ? String(initialValues.location.value) : ''
);
const { const {
setInputValue: setSupplierInputValue, setInputValue: setSupplierInputValue,
@@ -133,7 +147,8 @@ const PurchaseFilterModal = ({
project_flock: OptionType<number> | null; project_flock: OptionType<number> | null;
project_flock_kandang: OptionType<number> | null; project_flock_kandang: OptionType<number> | null;
}>({ }>({
initialValues: { // enableReinitialize: true,
initialValues: initialValues || {
poDate: '', poDate: '',
category: [], category: [],
status: [], status: [],
@@ -147,12 +162,18 @@ const PurchaseFilterModal = ({
const formattedValues = { const formattedValues = {
...values, ...values,
category: values.category.map((item) => String(item.value)), category: values.category.map((item) => String(item.value)),
category_labels: values.category,
status: values.status.map((item) => String(item.value)), status: values.status.map((item) => String(item.value)),
supplier_id: values.supplier?.value, supplier_id: values.supplier?.value,
supplier_label: values.supplier?.label,
area_id: values.area?.value, area_id: values.area?.value,
area_label: values.area?.label,
location_id: values.location?.value, location_id: values.location?.value,
location_label: values.location?.label,
project_flock_id: values.project_flock?.value, project_flock_id: values.project_flock?.value,
project_flock_label: values.project_flock?.label,
project_flock_kandang_id: values.project_flock_kandang?.value, project_flock_kandang_id: values.project_flock_kandang?.value,
project_flock_kandang_label: values.project_flock_kandang?.label,
}; };
onSubmit?.(formattedValues); onSubmit?.(formattedValues);
@@ -166,6 +187,17 @@ const PurchaseFilterModal = ({
}, },
}); });
const { resetForm, submitForm } = formik;
useEffect(() => {
setSelectedAreaId(
initialValues?.area?.value ? String(initialValues.area.value) : ''
);
setSelectedLocationId(
initialValues?.location?.value ? String(initialValues.location.value) : ''
);
}, [initialValues?.area, initialValues?.location]);
const projectFlockKandangOptions = useMemo(() => { const projectFlockKandangOptions = useMemo(() => {
if ( if (
!formik.values.project_flock || !formik.values.project_flock ||
@@ -197,6 +229,29 @@ const PurchaseFilterModal = ({
formik.setFieldValue('status', val); formik.setFieldValue('status', val);
}; };
const formikResetHandler = useCallback(() => {
resetForm({
values: {
poDate: '',
category: [],
status: [],
supplier: null,
area: null,
location: null,
project_flock: null,
project_flock_kandang: null,
},
});
setSelectedAreaId('');
setSelectedLocationId('');
onReset?.();
closeModalHandler();
}, [resetForm, onReset, closeModalHandler]);
const formikSubmitHandler = useCallback(async () => {
await submitForm();
}, [submitForm]);
return ( return (
<Modal <Modal
ref={ref} ref={ref}
@@ -206,7 +261,7 @@ const PurchaseFilterModal = ({
> >
<form <form
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
onReset={formik.handleReset} onReset={formikResetHandler}
className='w-full flex flex-col' className='w-full flex flex-col'
> >
{/* Modal Header */} {/* Modal Header */}
@@ -220,7 +275,9 @@ const PurchaseFilterModal = ({
type='button' type='button'
variant='ghost' variant='ghost'
color='none' color='none'
onClick={closeModalHandler} onClick={() => {
closeModalHandler();
}}
className='p-0 text-base-content/50 hover:text-base-content' className='p-0 text-base-content/50 hover:text-base-content'
> >
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
@@ -377,7 +434,8 @@ const PurchaseFilterModal = ({
</Button> </Button>
<Button <Button
type='submit' type='button'
onClick={formikSubmitHandler}
className='p-3 rounded-lg w-fit sm:w-full max-w-40 text-base-100 text-sm' className='p-3 rounded-lg w-fit sm:w-full max-w-40 text-base-100 text-sm'
> >
Apply Filter Apply Filter
+158 -98
View File
@@ -1,17 +1,7 @@
'use client'; 'use client';
import axios from 'axios'; import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
import {
ChangeEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
import useSWR from 'swr'; import useSWR from 'swr';
import useSWRInfinite from 'swr/infinite';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -31,17 +21,34 @@ import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/Purchase
import ButtonFilter from '@/components/helper/ButtonFilter'; import ButtonFilter from '@/components/helper/ButtonFilter';
import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal'; import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal';
import Dropdown from '@/components/dropdown/Dropdown'; import Dropdown from '@/components/dropdown/Dropdown';
import { OptionType } from '@/components/input/SelectInput';
import { cn, formatDate } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper'; import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
import { BaseApiResponse } from '@/types/api/api-general';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Purchase, PurchaseFilter } from '@/types/api/purchase/purchase'; import { Purchase, PurchaseFilter } from '@/types/api/purchase/purchase';
import { PurchaseApi } from '@/services/api/purchase'; import { PurchaseApi } from '@/services/api/purchase';
import { ExpenseApi } from '@/services/api/expense';
import { Expense } from '@/types/api/expense';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
type PurchaseTableFilters = {
search: string;
po_date: string;
approval_status: string;
product_category_id: string;
product_category_name: string;
supplier_id: string;
supplier_name: string;
area_id: string;
area_name: string;
location_id: string;
location_name: string;
project_flock_id: string;
project_flock_name: string;
project_flock_kandang_id: string;
project_flock_kandang_name: string;
};
// ===== STATUS BADGE UTILITIES ===== // ===== STATUS BADGE UTILITIES =====
const statusTextMap: Record<string, string> = { const statusTextMap: Record<string, string> = {
@@ -150,9 +157,6 @@ const RowOptionsMenu = ({
}; };
const PurchaseTable = () => { const PurchaseTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
// ===== STATE MANAGEMENT ===== // ===== STATE MANAGEMENT =====
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
@@ -168,21 +172,28 @@ const PurchaseTable = () => {
// ===== TABLE FILTER STATE ===== // ===== TABLE FILTER STATE =====
const { const {
state: tableFilterState, state: tableFilterState,
setFilters,
updateFilter, updateFilter,
setPage, setPage,
setPageSize, setPageSize,
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter<PurchaseTableFilters>({
initial: { initial: {
search: '', search: '',
po_date: '', po_date: '',
approval_status: '', approval_status: '',
product_category_id: '', product_category_id: '',
product_category_name: '',
supplier_id: '', supplier_id: '',
supplier_name: '',
area_id: '', area_id: '',
area_name: '',
location_id: '', location_id: '',
location_name: '',
project_flock_id: '', project_flock_id: '',
project_flock_name: '',
project_flock_kandang_id: '', project_flock_kandang_id: '',
project_flock_kandang_name: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
@@ -196,6 +207,16 @@ const PurchaseTable = () => {
project_flock_id: 'project_flock_id', project_flock_id: 'project_flock_id',
project_flock_kandang_id: 'project_flock_kandang_id', project_flock_kandang_id: 'project_flock_kandang_id',
}, },
excludeKeysFromUrl: [
'product_category_name',
'supplier_name',
'area_name',
'location_name',
'project_flock_name',
'project_flock_kandang_name',
],
persist: true,
storeName: 'purchase-table',
}); });
// ===== MODAL HOOKS ===== // ===== MODAL HOOKS =====
@@ -213,33 +234,6 @@ const PurchaseTable = () => {
PurchaseApi.getAllFetcher PurchaseApi.getAllFetcher
); );
const getKey = (
pageIndex: number,
previousPageData: BaseApiResponse<Expense>[] | null
) => {
if (pageIndex > 0 && !previousPageData) return null;
return `${ExpenseApi.basePath}?page=${pageIndex + 1}&limit=100`;
};
const { data: expensesPages } = useSWRInfinite(
getKey,
ExpenseApi.getAllFetcher
);
const expenseMap = useMemo(() => {
const map = new Map<string, number>();
if (!expensesPages) return map;
expensesPages.forEach((page) => {
if (isResponseSuccess(page)) {
page.data.forEach((expense: Expense) => {
map.set(expense.reference_number, expense.id);
});
}
});
return map;
}, [expensesPages]);
// ===== TABLE COLUMNS DEFINITION ===== // ===== TABLE COLUMNS DEFINITION =====
const purchaseColumns: ColumnDef<Purchase>[] = [ const purchaseColumns: ColumnDef<Purchase>[] = [
{ {
@@ -258,20 +252,16 @@ const PurchaseTable = () => {
return ( return (
<ul className='list-disc pl-4'> <ul className='list-disc pl-4'>
{poExpedition.map((exp, index) => { {poExpedition.map((exp, index) => {
const expenseId = expenseMap.get(exp.refrence); return (
if (expenseId) { <li key={index}>
return ( <Link
<li key={index}> href={`/expense/detail/?expenseId=${exp.id}`}
<Link className='p-0 h-auto text-primary underline'
href={`/expense/detail/?expenseId=${expenseId}`} >
className='p-0 h-auto text-primary underline' {exp.refrence}
> </Link>
{exp.refrence} </li>
</Link> );
</li>
);
}
return <li key={index}>{exp.refrence}</li>;
})} })}
</ul> </ul>
); );
@@ -422,58 +412,127 @@ const PurchaseTable = () => {
setIsDeleteLoading(false); setIsDeleteLoading(false);
}, [selectedPurchase?.id, refreshPurchaseRequests, deleteModal]); }, [selectedPurchase?.id, refreshPurchaseRequests, deleteModal]);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('purchase-table', pathname);
}, [pathname, setTableState]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = useCallback( const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => { (e) => {
setSearchValue(e.target.value);
updateFilter('search', e.target.value); updateFilter('search', e.target.value);
}, },
[updateFilter, setSearchValue] [updateFilter]
); );
const filterSubmitHandler = (values: PurchaseFilter) => { const filterSubmitHandler = (values: PurchaseFilter) => {
updateFilter('po_date', values.poDate); setFilters({
updateFilter('product_category_id', values.category.join(',')); po_date: values.poDate,
updateFilter('approval_status', values.status.join(',')); product_category_id: values.category.join(','),
updateFilter( product_category_name:
'supplier_id', values.category_labels?.map((item) => item.label).join(',') || '',
values.supplier_id ? String(values.supplier_id) : '' approval_status: values.status.join(','),
); supplier_id: values.supplier_id ? String(values.supplier_id) : '',
updateFilter('area_id', values.area_id ? String(values.area_id) : ''); supplier_name: values.supplier_label || '',
updateFilter( area_id: values.area_id ? String(values.area_id) : '',
'location_id', area_name: values.area_label || '',
values.location_id ? String(values.location_id) : '' location_id: values.location_id ? String(values.location_id) : '',
); location_name: values.location_label || '',
updateFilter( project_flock_id: values.project_flock_id
'project_flock_id', ? String(values.project_flock_id)
values.project_flock_id ? String(values.project_flock_id) : '' : '',
); project_flock_name: values.project_flock_label || '',
updateFilter( project_flock_kandang_id: values.project_flock_kandang_id
'project_flock_kandang_id',
values.project_flock_kandang_id
? String(values.project_flock_kandang_id) ? String(values.project_flock_kandang_id)
: '' : '',
); project_flock_kandang_name: values.project_flock_kandang_label || '',
});
}; };
const filterResetHandler = () => { const filterResetHandler = () => {
updateFilter('po_date', ''); setFilters({
updateFilter('product_category_id', ''); po_date: '',
updateFilter('approval_status', ''); product_category_id: '',
updateFilter('supplier_id', ''); product_category_name: '',
updateFilter('area_id', ''); approval_status: '',
updateFilter('location_id', ''); supplier_id: '',
updateFilter('project_flock_id', ''); supplier_name: '',
updateFilter('project_flock_kandang_id', ''); area_id: '',
area_name: '',
location_id: '',
location_name: '',
project_flock_id: '',
project_flock_name: '',
project_flock_kandang_id: '',
project_flock_kandang_name: '',
});
}; };
const purchaseFilterInitialValues = useMemo(() => {
const categoryIds = tableFilterState.product_category_id
? tableFilterState.product_category_id
.split(',')
.map((item) => item.trim())
.filter(Boolean)
: [];
const categoryLabels = tableFilterState.product_category_name
? tableFilterState.product_category_name
.split(',')
.map((item) => item.trim())
.filter(Boolean)
: [];
const approvalStatuses = tableFilterState.approval_status
? tableFilterState.approval_status
.split(',')
.map((item) => item.trim())
.filter(Boolean)
: [];
return {
poDate: tableFilterState.po_date,
category: categoryIds.map((value, index) => ({
value: Number(value),
label: categoryLabels[index] || value,
})),
status: approvalStatuses.map((value) => ({
value,
label:
PURCHASE_ORDER_APPROVAL_LINE.find((item) => item.step_name === value)
?.step_name || value,
})),
supplier: tableFilterState.supplier_id
? ({
value: Number(tableFilterState.supplier_id),
label:
tableFilterState.supplier_name || tableFilterState.supplier_id,
} as OptionType<number>)
: null,
area: tableFilterState.area_id
? ({
value: Number(tableFilterState.area_id),
label: tableFilterState.area_name || tableFilterState.area_id,
} as OptionType<number>)
: null,
location: tableFilterState.location_id
? ({
value: Number(tableFilterState.location_id),
label:
tableFilterState.location_name || tableFilterState.location_id,
} as OptionType<number>)
: null,
project_flock: tableFilterState.project_flock_id
? ({
value: Number(tableFilterState.project_flock_id),
label:
tableFilterState.project_flock_name ||
tableFilterState.project_flock_id,
} as OptionType<number>)
: null,
project_flock_kandang: tableFilterState.project_flock_kandang_id
? ({
value: Number(tableFilterState.project_flock_kandang_id),
label:
tableFilterState.project_flock_kandang_name ||
tableFilterState.project_flock_kandang_id,
} as OptionType<number>)
: null,
};
}, [tableFilterState]);
const exportToExcel = useCallback(async () => { const exportToExcel = useCallback(async () => {
setIsLoadingExportingToExcel(true); setIsLoadingExportingToExcel(true);
@@ -705,6 +764,7 @@ const PurchaseTable = () => {
<PurchaseFilterModal <PurchaseFilterModal
ref={filterModal.ref} ref={filterModal.ref}
initialValues={purchaseFilterInitialValues}
onSubmit={filterSubmitHandler} onSubmit={filterSubmitHandler}
onReset={filterResetHandler} onReset={filterResetHandler}
/> />