refactor(FE): Refactor tab actions to use memoized component

This commit is contained in:
rstubryan
2026-03-05 11:21:56 +07:00
parent ea88c3ce8e
commit 94ab48d3f6
@@ -20,7 +20,7 @@ import { generatePurchasesPerSupplierExcel } from '@/components/pages/report/log
import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportPDF'; import { generatePurchasesPerSupplierPDF } from '@/components/pages/report/logistic-stock/export/PurchasesPerSupplierExportPDF';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef } from '@tanstack/react-table';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import useSWR from 'swr'; import useSWR from 'swr';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
@@ -65,6 +65,8 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
const [dateErrorShown, setDateErrorShown] = useState(false); const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false); const [hasDateError, setHasDateError] = useState(false);
const handleFilterModalOpenRef = useRef(() => {});
const filterModal = useModal(); const filterModal = useModal();
// ===== OPTIONS ===== // ===== OPTIONS =====
@@ -104,11 +106,6 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
[] []
); );
const handleFilterModalOpen = () => {
filterModal.openModal();
formik.validateForm();
};
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<PurchasesPerSupplierFilterType>({ const formik = useFormik<PurchasesPerSupplierFilterType>({
initialValues: { initialValues: {
@@ -151,11 +148,18 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
}, },
}); });
handleFilterModalOpenRef.current = () => {
filterModal.openModal();
formik.validateForm();
};
const { setFieldValue } = formik;
// ===== DATE CHANGE HANDLERS ===== // ===== DATE CHANGE HANDLERS =====
const handleStartDateChange = useCallback( const handleStartDateChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value; const value = e.target.value;
formik.setFieldValue('start_date', value || null); setFieldValue('start_date', value || null);
if (value && formik.values.end_date) { if (value && formik.values.end_date) {
const startDate = new Date(value); const startDate = new Date(value);
@@ -180,13 +184,13 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
setHasDateError(false); setHasDateError(false);
} }
}, },
[formik, dateErrorShown] [setFieldValue, dateErrorShown, formik.values.end_date]
); );
const handleEndDateChange = useCallback( const handleEndDateChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value; const value = e.target.value;
formik.setFieldValue('end_date', value || null); setFieldValue('end_date', value || null);
if (value && formik.values.start_date) { if (value && formik.values.start_date) {
const startDateObj = new Date(formik.values.start_date); const startDateObj = new Date(formik.values.start_date);
@@ -210,7 +214,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
setDateErrorShown(false); setDateErrorShown(false);
} }
}, },
[formik, dateErrorShown] [setFieldValue, dateErrorShown, formik.values.start_date]
); );
// ===== DERIVED VALUES ===== // ===== DERIVED VALUES =====
@@ -443,88 +447,104 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
productCategoryOptions, productCategoryOptions,
]); ]);
// ===== REGISTER TAB ACTIONS TO STORE ===== // ===== TAB ACTIONS COMPONENT =====
const setTabActions = useTabActionsStore((state) => state.setTabActions); const TabActions = useMemo(() => {
const clearTabActions = useTabActionsStore((state) => state.clearTabActions); return function TabActionsComponent() {
const setTabActions = useTabActionsStore((state) => state.setTabActions);
const clearTabActions = useTabActionsStore(
(state) => state.clearTabActions
);
useEffect(() => { useEffect(() => {
setTabActions( setTabActions(
tabId, tabId,
<div className='flex flex-row gap-3'> <div className='flex flex-row gap-3'>
<ButtonFilter <ButtonFilter
values={formik.values} values={filterParams}
fieldGroups={[['start_date', 'end_date']]} fieldGroups={[['start_date', 'end_date']]}
onClick={handleFilterModalOpen} onClick={() => handleFilterModalOpenRef.current()}
variant='outline'
className='px-3 py-2.5'
/>
<Dropdown
align='end'
direction='bottom'
className={{
content:
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
}}
trigger={
<Button
variant='outline' variant='outline'
color='none' className='px-3 py-2.5'
isLoading={isAnyExportLoading} />
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
<Dropdown
align='end'
direction='bottom'
className={{
content:
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
}}
trigger={
<Button
variant='outline'
color='none'
isLoading={isAnyExportLoading}
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
>
<div className='flex flex-row items-center gap-1.5'>
<Icon
icon='heroicons:cloud-arrow-down'
width={20}
height={20}
/>
<span>Export</span>
<div className='w-px self-stretch bg-base-content/10' />
<Icon
icon='heroicons:chevron-down'
width={14}
height={14}
/>
</div>
</Button>
}
> >
<div className='flex flex-row items-center gap-1.5'> <Button
<Icon variant='ghost'
icon='heroicons:cloud-arrow-down' color='none'
width={20} onClick={handleExportExcel}
height={20} isLoading={isExcelExportLoading}
/> className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:table-cells' width={20} height={20} />
Export to Excel
</Button>
<Button
variant='ghost'
color='none'
onClick={handleExportPdf}
isLoading={isPdfExportLoading}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:document' width={20} height={20} />
Export to PDF
</Button>
</Dropdown>
</div>
);
}, [setTabActions]);
<span>Export</span> useEffect(() => {
return () => {
clearTabActions(tabId);
};
}, [clearTabActions]);
<div className='w-px self-stretch bg-base-content/10' /> return null;
};
<Icon icon='heroicons:chevron-down' width={14} height={14} />
</div>
</Button>
}
>
<Button
variant='ghost'
color='none'
onClick={handleExportExcel}
isLoading={isExcelExportLoading}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:table-cells' width={20} height={20} />
Export to Excel
</Button>
<Button
variant='ghost'
color='none'
onClick={handleExportPdf}
isLoading={isPdfExportLoading}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:document' width={20} height={20} />
Export to PDF
</Button>
</Dropdown>
</div>
);
}, [ }, [
tabId, tabId,
formik.values, filterParams,
isAnyExportLoading, isAnyExportLoading,
filterModal.open, handleExportExcel,
setTabActions, handleExportPdf,
isExcelExportLoading,
isPdfExportLoading,
]); ]);
useEffect(() => { const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
return () => {
clearTabActions(tabId);
};
}, [tabId, clearTabActions]);
const getTableColumns = ( const getTableColumns = (
summary: LogisticPurchasePerSupplierSummary summary: LogisticPurchasePerSupplierSummary
@@ -704,6 +724,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
return ( return (
<> <>
{TabActionsElement}
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'> <div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
{!isSubmitted ? ( {!isSubmitted ? (
<PurchasePerSupplierSkeleton <PurchasePerSupplierSkeleton