refactor(FE): Refactor date validation to use shared state and cleanup

This commit is contained in:
rstubryan
2026-02-25 15:56:12 +07:00
parent 0af7b172a0
commit a75d84556a
8 changed files with 371 additions and 56 deletions
+13 -18
View File
@@ -189,6 +189,7 @@ const FinanceTable = () => {
const [selectedFinance, setSelectedFinance] = useState<Finance | null>(null);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false);
// ===== Formik for Filter =====
const filterFormik = useFormik<FinanceTableFilterValues>({
@@ -335,10 +336,7 @@ const FinanceTable = () => {
const endDateObj = new Date(endDate);
if (endDateObj < startDate) {
filterFormik.setFieldError(
'end_date',
'Tanggal akhir tidak boleh masa lampau'
);
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
@@ -346,12 +344,14 @@ const FinanceTable = () => {
setDateErrorShown(true);
}
} else {
filterFormik.setFieldError('end_date', undefined);
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}
} else {
setHasDateError(false);
}
};
@@ -366,10 +366,7 @@ const FinanceTable = () => {
const endDate = new Date(value);
if (endDate < startDateObj) {
filterFormik.setFieldError(
'end_date',
'Tanggal akhir tidak boleh masa lampau'
);
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
@@ -380,7 +377,7 @@ const FinanceTable = () => {
}
}
filterFormik.setFieldError('end_date', undefined);
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
@@ -661,22 +658,20 @@ const FinanceTable = () => {
name='start_date'
label='Periode Tanggal (Mulai)'
value={filterFormik.values.start_date}
errorMessage={filterFormik.errors.start_date}
onChange={startDateChangeHandler}
errorMessage={
filterFormik.errors.end_date
? filterFormik.errors.end_date
: undefined
isError={
filterFormik.touched.start_date && Boolean(filterFormik.errors.start_date)
}
/>
<DateInput
name='end_date'
label='Periode Tanggal (Akhir)'
value={filterFormik.values.end_date}
errorMessage={filterFormik.errors.end_date}
onChange={endDateChangeHandler}
errorMessage={
filterFormik.errors.end_date
? filterFormik.errors.end_date
: undefined
isError={
(filterFormik.touched.end_date && Boolean(filterFormik.errors.end_date)) || hasDateError
}
/>
<DebouncedTextInput
@@ -1,7 +1,8 @@
'use client';
import { RefObject } from 'react';
import { RefObject, useState, useEffect } from 'react';
import { useFormik } from 'formik';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
import Modal from '@/components/Modal';
@@ -33,6 +34,36 @@ const TransferToLayingFilterModal = ({
ref.current?.close();
};
// ===== DATE ERROR STATE =====
const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false);
// ===== CLEANUP TOAST ON UNMOUNT =====
useEffect(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
}
};
}, [dateErrorShown]);
// ===== CLEANUP TOAST WHEN MODAL CLOSES =====
useEffect(() => {
const dialogElement = ref.current;
const handleModalClose = () => {
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
};
dialogElement?.addEventListener('close', handleModalClose);
return () => {
dialogElement?.removeEventListener('close', handleModalClose);
};
}, [ref, dateErrorShown]);
// Flock Source
const {
setInputValue: setFlockSourceInputValue,
@@ -138,24 +169,77 @@ const TransferToLayingFilterModal = ({
name='startDate'
placeholder='Tanggal Awal'
value={formik.values.startDate || ''}
onChange={formik.handleChange}
errorMessage={formik.errors.startDate}
onChange={(e) => {
const value = e.target.value;
formik.setFieldValue('startDate', value);
if (value && formik.values.endDate) {
const startDate = new Date(value);
const endDateObj = new Date(formik.values.endDate);
if (endDateObj < startDate) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
});
setDateErrorShown(true);
}
} else {
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}
} else {
setHasDateError(false);
}
}}
onBlur={formik.handleBlur}
isError={
formik.touched.startDate && Boolean(formik.errors.startDate)
}
/>
<hr className='w-full max-w-3 h-px border-base-content/10' />
<DateInput
name='endDate'
placeholder='Tanggal Akhir'
value={formik.values.endDate || ''}
onChange={formik.handleChange}
errorMessage={formik.errors.endDate}
onChange={(e) => {
const value = e.target.value;
formik.setFieldValue('endDate', value);
if (value && formik.values.startDate) {
const startDateObj = new Date(formik.values.startDate);
const endDate = new Date(value);
if (endDate < startDateObj) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
});
setDateErrorShown(true);
}
return;
}
}
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}}
onBlur={formik.handleBlur}
isError={formik.touched.endDate && !!formik.errors.endDate}
isError={
(formik.touched.endDate && Boolean(formik.errors.endDate)) || hasDateError
}
/>
</div>
{formik.touched.endDate && formik.errors.endDate && (
<span className='text-xs text-error mt-1'>
{formik.errors.endDate}
</span>
)}
<SelectInputCheckbox
label='Flock Asal'
@@ -250,6 +250,10 @@ const UniformityTable = () => {
useState<string>('');
const [, setFilterErrors] = useState<Record<string, string>>({});
// ===== DATE ERROR STATE =====
const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false);
const {
setInputValue: setFilterLocationInputValue,
options: filterLocationOptions,
@@ -792,6 +796,23 @@ const UniformityTable = () => {
}
}, [uniformities, rowSelection]);
useEffect(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
}
};
}, [dateErrorShown]);
useEffect(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
};
}, [filterModal.open, dateErrorShown]);
// ===== TABLE COLUMNS DEFINITION =====
const uniformityColumns: ColumnDef<Uniformity>[] = useMemo(
() => [
@@ -1245,9 +1266,38 @@ const UniformityTable = () => {
placeholder='Tanggal Mulai'
value={filterFormik.values.start_date}
errorMessage={filterFormik.errors.start_date}
onChange={(e) =>
filterFormik.setFieldValue('start_date', e.target.value)
}
onChange={(e) => {
const value = e.target.value;
filterFormik.setFieldValue('start_date', value);
if (value && filterFormik.values.end_date) {
const startDate = new Date(value);
const endDateObj = new Date(
filterFormik.values.end_date
);
if (endDateObj < startDate) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error(
'Tanggal akhir tidak boleh masa lampau',
{
duration: Infinity,
}
);
setDateErrorShown(true);
}
} else {
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}
} else {
setHasDateError(false);
}
}}
isError={
filterFormik.touched.start_date &&
Boolean(filterFormik.errors.start_date)
@@ -1259,13 +1309,38 @@ const UniformityTable = () => {
placeholder='Tanggal Akhir'
value={filterFormik.values.end_date}
errorMessage={filterFormik.errors.end_date}
onChange={(e) =>
filterFormik.setFieldValue('end_date', e.target.value)
}
isError={
filterFormik.touched.end_date &&
Boolean(filterFormik.errors.end_date)
}
onChange={(e) => {
const value = e.target.value;
filterFormik.setFieldValue('end_date', value);
if (value && filterFormik.values.start_date) {
const startDateObj = new Date(
filterFormik.values.start_date
);
const endDate = new Date(value);
if (endDate < startDateObj) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error(
'Tanggal akhir tidak boleh masa lampau',
{
duration: Infinity,
}
);
setDateErrorShown(true);
}
return;
}
}
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}}
isError={hasDateError}
/>
</div>
</div>
@@ -11,7 +11,19 @@ export type DebtSupplierFilterType = {
export const DebtSupplierFilterSchema: yup.ObjectSchema<DebtSupplierFilterType> =
yup.object({
startDate: yup.string().optional().notRequired(),
endDate: yup.string().optional().notRequired(),
endDate: yup
.string()
.optional()
.notRequired()
.test(
'is-greater-than-start',
'Tanggal akhir tidak boleh masa lampau',
function (value) {
const startDate = this.parent.startDate;
if (!startDate || !value) return true;
return new Date(value) >= new Date(startDate);
}
),
supplierIds: yup
.array()
.of(
@@ -844,19 +844,26 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
<DateInput
name='start_date'
value={formik.values.start_date || ''}
errorMessage={formik.errors.start_date}
onChange={handleStartDateChange}
className={{ wrapper: 'w-full' }}
isNestedModal
isError={
formik.touched.start_date && Boolean(formik.errors.start_date)
}
/>
<hr className='w-full max-w-3 h-px border-base-content/10' />
<DateInput
name='end_date'
value={formik.values.end_date || ''}
errorMessage={formik.errors.end_date}
onChange={handleEndDateChange}
className={{ wrapper: 'w-full' }}
isNestedModal
isError={hasDateError}
isError={
(formik.touched.end_date && Boolean(formik.errors.end_date)) || hasDateError
}
/>
</div>
</div>
@@ -87,6 +87,10 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
});
const [isSubmitted, setIsSubmitted] = useState(false);
// ===== DATE ERROR STATE =====
const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false);
const filterModal = useModal();
const {
@@ -106,6 +110,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
const handleFilterModalOpen = () => {
filterModal.openModal();
formik.validateForm();
};
// ===== FORMIK SETUP =====
@@ -349,6 +354,23 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
};
}, [tabId, clearTabActions]);
useEffect(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
}
};
}, [dateErrorShown]);
useEffect(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
};
}, [filterModal.open, dateErrorShown]);
const getTableColumns = (supplier?: DebtSupplier): ColumnDef<DebtRow>[] => [
{
id: 'no',
@@ -723,7 +745,31 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
name='startDate'
value={formik.values.startDate || ''}
onChange={(e) => {
formik.setFieldValue('startDate', e.target.value || null);
const value = e.target.value;
formik.setFieldValue('startDate', value || null);
if (value && formik.values.endDate) {
const startDate = new Date(value);
const endDateObj = new Date(formik.values.endDate);
if (endDateObj < startDate) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
});
setDateErrorShown(true);
}
} else {
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}
} else {
setHasDateError(false);
}
}}
className={{ wrapper: 'w-full' }}
isError={
@@ -737,10 +783,36 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
name='endDate'
value={formik.values.endDate || ''}
onChange={(e) => {
formik.setFieldValue('endDate', e.target.value || null);
const value = e.target.value;
formik.setFieldValue('endDate', value || null);
if (value && formik.values.startDate) {
const startDateObj = new Date(formik.values.startDate);
const endDate = new Date(value);
if (endDate < startDateObj) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
});
setDateErrorShown(true);
}
return;
}
}
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}}
className={{ wrapper: 'w-full' }}
isError={formik.touched.endDate && !!formik.errors.endDate}
isError={
(formik.touched.endDate && !!formik.errors.endDate) ||
hasDateError
}
errorMessage={formik.errors.endDate}
isNestedModal
/>
@@ -842,18 +842,25 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
<DateInput
name='start_date'
value={formik.values.start_date || ''}
errorMessage={formik.errors.start_date}
onChange={handleStartDateChange}
className={{ wrapper: 'w-full' }}
isNestedModal
isError={
formik.touched.start_date && Boolean(formik.errors.start_date)
}
/>
<hr className='w-full max-w-3 h-px border-base-content/10' />
<DateInput
name='end_date'
value={formik.values.end_date || ''}
errorMessage={formik.errors.end_date}
onChange={handleEndDateChange}
className={{ wrapper: 'w-full' }}
isNestedModal
isError={hasDateError}
isError={
(formik.touched.end_date && Boolean(formik.errors.end_date)) || hasDateError
}
/>
</div>
</div>
@@ -80,6 +80,10 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
// ===== FILTER STATE =====
const [filterParams, setFilterParams] = useState<FilterParams>({});
// ===== DATE ERROR STATE =====
const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false);
const filterModal = useModal();
// ===== OPTIONS =====
@@ -448,6 +452,23 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
};
}, [tabId, clearTabActions]);
useEffectHook(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
}
};
}, [dateErrorShown]);
useEffectHook(() => {
return () => {
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
};
}, [filterModal.open, dateErrorShown]);
const getTableColumns = (): ColumnDef<DailyMarketingRow>[] => {
const tableColumns: ColumnDef<DailyMarketingRow>[] = [
{
@@ -791,34 +812,76 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
placeholder='Pilih Tanggal Awal'
value={formik.values.start_date || ''}
onChange={(e) => {
formik.setFieldValue('start_date', e.target.value || null);
const value = e.target.value;
formik.setFieldValue('start_date', value || null);
if (value && formik.values.end_date) {
const startDate = new Date(value);
const endDateObj = new Date(formik.values.end_date);
if (endDateObj < startDate) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
});
setDateErrorShown(true);
}
} else {
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}
} else {
setHasDateError(false);
}
}}
className={{ wrapper: 'w-full' }}
errorMessage={formik.errors.start_date}
isError={
!!formik.errors.start_date && formik.touched.start_date
}
/>
{formik.errors.start_date && formik.touched.start_date && (
<div className='text-error text-xs mt-1'>
{formik.errors.start_date}
</div>
)}
<DateInput
name='end_date'
label='Tanggal Akhir'
placeholder='Pilih Tanggal Akhir'
value={formik.values.end_date || ''}
onChange={(e) => {
formik.setFieldValue('end_date', e.target.value || null);
const value = e.target.value;
formik.setFieldValue('end_date', value || null);
if (value && formik.values.start_date) {
const startDateObj = new Date(formik.values.start_date);
const endDate = new Date(value);
if (endDate < startDateObj) {
setHasDateError(true);
if (!dateErrorShown) {
toast.error('Tanggal akhir tidak boleh masa lampau', {
duration: Infinity,
});
setDateErrorShown(true);
}
return;
}
}
setHasDateError(false);
if (dateErrorShown) {
toast.dismiss();
setDateErrorShown(false);
}
}}
className={{ wrapper: 'w-full' }}
isError={!!formik.errors.end_date && formik.touched.end_date}
errorMessage={formik.errors.end_date}
isError={
(formik.errors.end_date && formik.touched.end_date) ||
hasDateError
}
/>
{formik.errors.end_date && formik.touched.end_date && (
<div className='text-error text-xs mt-1'>
{formik.errors.end_date}
</div>
)}
</div>
</div>