From 79e41d8a6ffc6cb0a25c5a459aeb0761df376efa Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 5 May 2026 16:10:57 +0700 Subject: [PATCH] fix: implement table persist state in recording filter --- .../production/recording/RecordingTable.tsx | 422 +++++++----------- .../recording/filter/RecordingFilter.ts | 51 ++- 2 files changed, 184 insertions(+), 289 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 04c36ef2..f5050844 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -1,13 +1,6 @@ 'use client'; -import axios from 'axios'; -import React, { - useCallback, - useState, - useMemo, - useEffect, - useRef, -} from 'react'; +import React, { useCallback, useState, useMemo, useEffect } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import { SortingState, CellContext, ColumnDef } from '@tanstack/react-table'; @@ -46,8 +39,6 @@ import { useTableFilter } from '@/services/hooks/useTableFilter'; import toast from 'react-hot-toast'; import StatusBadge from '@/components/helper/StatusBadge'; import CheckboxInput from '@/components/input/CheckboxInput'; -import { useUiStore } from '@/stores/ui/ui.store'; -import { usePathname } from 'next/navigation'; import { Color } from '@/types/theme'; import ButtonFilter from '@/components/helper/ButtonFilter'; import Dropdown from '@/components/Dropdown'; @@ -77,6 +68,26 @@ const getStatusBadgeColor = (status: string): Color => { return statusBadgeColorMap[normalizedStatus] || 'neutral'; }; +const isRecordingApproved = (recording: Recording): boolean => { + return ( + recording.approval?.action === 'APPROVED' && + recording.approval?.step_name === 'Disetujui' + ); +}; + +// ===== FILTER HELPERS ===== +const recordingApprovalStatusOptions: OptionType[] = [ + { value: 'CREATED', label: 'Pengajuan' }, + { value: 'UPDATED', label: 'Diperbarui' }, + { value: 'APPROVED', label: 'Disetujui' }, + { value: 'REJECTED', label: 'Ditolak' }, +]; + +const projectFlockCategoryOptions: OptionType[] = [ + { value: 'GROWING', label: 'Growing' }, + { value: 'LAYING', label: 'Laying' }, +]; + const RowOptionsMenu = ({ popoverPosition = 'bottom', props, @@ -268,25 +279,31 @@ const RowOptionsMenu = ({ }; const RecordingTable = () => { - const { searchValue, setSearchValue, setTableState } = useUiStore(); - const pathname = usePathname(); - const { state: tableFilterState, updateFilter, setPage, setPageSize, toQueryString: getTableFilterQueryString, - } = useTableFilter({ + } = useTableFilter<{ + search: string; + areaFilter: OptionType | null; + locationFilter: OptionType | null; + projectFlockFilter: OptionType | null; + kandangFilter: OptionType | null; + projectFlockKandangFilter: number | null; + approvalStatusFilter: OptionType | null; + projectFlockCategoryFilter: OptionType | null; + }>({ initial: { search: '', - areaFilter: '', - locationFilter: '', - projectFlockFilter: '', - kandangFilter: '', - projectFlockKandangFilter: '', - approvalStatusFilter: '', - projectFlockCategoryFilter: '', + areaFilter: null, + locationFilter: null, + projectFlockFilter: null, + kandangFilter: null, + projectFlockKandangFilter: null, + approvalStatusFilter: null, + projectFlockCategoryFilter: null, }, paramMap: { page: 'page', @@ -300,68 +317,73 @@ const RecordingTable = () => { approvalStatusFilter: 'approval_status', projectFlockCategoryFilter: 'project_flock_category', }, - }); - useEffect(() => { - updateFilter('search', searchValue); - }, [searchValue, updateFilter]); + persist: true, + storeName: 'recording-table', + }); // ===== FILTER MODAL STATE ===== const filterModal = useModal(); - // ===== FILTER STATE ===== - const [filterArea, setFilterArea] = useState(null); - const [filterLocation, setFilterLocation] = useState(null); - const [filterProjectFlock, setFilterProjectFlock] = - useState(null); - const [filterKandang, setFilterKandang] = useState(null); - const [, setFilterProjectFlockKandangId] = useState( - undefined - ); - const [filterLocationAreaId, setFilterLocationAreaId] = useState(''); - const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] = - useState(''); - // ===== FORMIK SETUP ===== const formik = useFormik({ initialValues: { - area_id: null, - location_id: null, - project_flock_id: null, - kandang_id: null, - project_flock_kandang_id: null, - approval_status: null, - project_flock_category: null, + area_id: tableFilterState.areaFilter, + location_id: tableFilterState.locationFilter, + project_flock_id: tableFilterState.projectFlockFilter, + kandang_id: tableFilterState.kandangFilter, + project_flock_kandang_id: tableFilterState.projectFlockKandangFilter, + approval_status: tableFilterState.approvalStatusFilter, + project_flock_category: tableFilterState.projectFlockCategoryFilter, }, validationSchema: RecordingFilterSchema, onSubmit: (values, { setSubmitting }) => { - updateFilter('areaFilter', values.area_id || ''); - updateFilter('locationFilter', values.location_id || ''); - updateFilter('projectFlockFilter', values.project_flock_id || ''); - updateFilter('kandangFilter', values.kandang_id || ''); + updateFilter('areaFilter', values.area_id, true); + updateFilter('locationFilter', values.location_id, true); + updateFilter('projectFlockFilter', values.project_flock_id, true); + updateFilter('kandangFilter', values.kandang_id, true); updateFilter( 'projectFlockKandangFilter', - values.project_flock_kandang_id || '' + values.project_flock_kandang_id, + true ); - updateFilter('approvalStatusFilter', values.approval_status || ''); + updateFilter('approvalStatusFilter', values.approval_status, true); updateFilter( 'projectFlockCategoryFilter', - values.project_flock_category || '' + values.project_flock_category, + true ); filterModal.closeModal(); setSubmitting(false); }, - onReset: () => { - updateFilter('areaFilter', ''); - updateFilter('locationFilter', ''); - updateFilter('projectFlockFilter', ''); - updateFilter('kandangFilter', ''); - updateFilter('projectFlockKandangFilter', ''); - updateFilter('approvalStatusFilter', ''); - updateFilter('projectFlockCategoryFilter', ''); - }, }); + const formikResetHandler = () => { + updateFilter('areaFilter', null, true); + updateFilter('locationFilter', null, true); + updateFilter('projectFlockFilter', null, true); + updateFilter('kandangFilter', null, true); + updateFilter('projectFlockKandangFilter', null, true); + updateFilter('approvalStatusFilter', null, true); + updateFilter('projectFlockCategoryFilter', null, true); + + formik.resetForm({ + values: { + area_id: null, + location_id: null, + project_flock_id: null, + kandang_id: null, + project_flock_kandang_id: null, + approval_status: null, + project_flock_category: null, + }, + }); + + filterModal.closeModal(); + }; + + const { project_flock_id, kandang_id } = formik.values; + const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState>({}); const selectedRowIds = Object.keys(rowSelection).map((item) => @@ -396,13 +418,6 @@ const RecordingTable = () => { ); // ===== LOCATION, AREA, KANDANG OPTIONS ===== - const locationParams = useMemo(() => { - if (filterLocationAreaId) { - return { area_id: filterLocationAreaId }; - } - return undefined; - }, [filterLocationAreaId]); - const { setInputValue: setLocationInputValue, options: locationOptions, @@ -413,7 +428,9 @@ const RecordingTable = () => { 'id', 'name', 'search', - locationParams + { + area_id: String(formik.values.area_id?.value), + } ); const { @@ -428,13 +445,6 @@ const RecordingTable = () => { 'search' ); - const projectFlockParams = useMemo(() => { - if (filterProjectFlockLocationId) { - return { location_id: filterProjectFlockLocationId }; - } - return undefined; - }, [filterProjectFlockLocationId]); - const { setInputValue: setProjectFlockInputValue, options: projectFlockOptions, @@ -446,34 +456,41 @@ const RecordingTable = () => { 'id', 'flock_name', 'search', - projectFlockParams + { + location_id: String(formik.values.location_id?.value), + } ); const kandangOptions = useMemo(() => { - if (!filterProjectFlock || !projectFlocksRawData) return []; + if (!project_flock_id || !projectFlocksRawData) return []; if (!isResponseSuccess(projectFlocksRawData)) return []; const data = projectFlocksRawData.data as ProjectFlock[]; - const selectedProjectFlockData = data.find( - (pf) => pf.id === filterProjectFlock.value + const selectedProjectFlockData = data.find((pf) => + pf.id === formik.values.project_flock_id?.value + ? Number(formik.values.project_flock_id.value) + : 0 ); if (!selectedProjectFlockData?.kandangs) return []; + return selectedProjectFlockData.kandangs.map((k) => ({ value: k.id, label: k.name || '', })); - }, [filterProjectFlock, projectFlocksRawData]); + }, [project_flock_id, projectFlocksRawData]); // ===== PROJECT FLOCK KANDANG LOOKUP ===== const projectFlockKandangLookupUrl = useMemo(() => { - if (!filterProjectFlock || !filterKandang) return null; + if (!project_flock_id?.value || !kandang_id?.value) return null; + const params = new URLSearchParams({ - project_flock_id: filterProjectFlock.value.toString(), - kandang_id: filterKandang.value.toString(), + project_flock_id: project_flock_id.value.toString(), + kandang_id: kandang_id.value.toString(), }); + return `${ProjectFlockApi.basePath}/kandangs/lookup?${params.toString()}`; - }, [filterProjectFlock, filterKandang]); + }, [project_flock_id, kandang_id]); const { data: projectFlockKandangLookupData } = useSWR( projectFlockKandangLookupUrl, @@ -495,154 +512,45 @@ const RecordingTable = () => { ? projectFlockKandangLookupData.data : undefined; - const formikRef = useRef(formik); - - useEffect(() => { - formikRef.current = formik; - }); - useEffect(() => { if (projectFlockKandangLookup?.id) { const pfkId = String(projectFlockKandangLookup.id); - setFilterProjectFlockKandangId(projectFlockKandangLookup.id); - formikRef.current.setFieldValue('project_flock_kandang_id', pfkId); + formik.setFieldValue('project_flock_kandang_id', pfkId); } else { - setFilterProjectFlockKandangId(undefined); - formikRef.current.setFieldValue('project_flock_kandang_id', null); + formik.setFieldValue('project_flock_kandang_id', null); } }, [projectFlockKandangLookup]); // ===== FILTER HANDLERS ===== - const handleFilterAreaChange = useCallback( - (val: OptionType | OptionType[] | null) => { - const area = val as OptionType | null; - const areaId = area?.value ? String(area.value) : null; + const handleFilterAreaChange = (val: OptionType | OptionType[] | null) => { + formik.setFieldValue('area_id', val); + formik.setFieldValue('location_id', null); + formik.setFieldValue('project_flock_id', null); + formik.setFieldValue('kandang_id', null); + formik.setFieldValue('project_flock_kandang_id', null); + }; - formik.setFieldValue('area_id', areaId); - formik.setFieldValue('location_id', null); - formik.setFieldValue('project_flock_id', null); - formik.setFieldValue('kandang_id', null); - formik.setFieldValue('project_flock_kandang_id', null); + const handleFilterLocationChange = ( + val: OptionType | OptionType[] | null + ) => { + formik.setFieldValue('location_id', val); + formik.setFieldValue('project_flock_id', null); + formik.setFieldValue('kandang_id', null); + formik.setFieldValue('project_flock_kandang_id', null); + }; - setFilterArea(area); - setFilterLocation(null); - setFilterProjectFlock(null); - setFilterKandang(null); - setFilterLocationAreaId(areaId || ''); - setFilterProjectFlockLocationId(''); - }, - [formik] - ); + const handleFilterProjectFlockChange = ( + val: OptionType | OptionType[] | null + ) => { + formik.setFieldValue('project_flock_id', val); + formik.setFieldValue('kandang_id', null); + formik.setFieldValue('project_flock_kandang_id', null); + }; - const handleFilterLocationChange = useCallback( - (val: OptionType | OptionType[] | null) => { - const location = val as OptionType | null; - const locationId = location?.value ? String(location.value) : null; - - formik.setFieldValue('location_id', locationId); - formik.setFieldValue('project_flock_id', null); - formik.setFieldValue('kandang_id', null); - formik.setFieldValue('project_flock_kandang_id', null); - - setFilterLocation(location); - setFilterProjectFlock(null); - setFilterKandang(null); - setFilterProjectFlockLocationId(locationId || ''); - }, - [formik] - ); - - const handleFilterProjectFlockChange = useCallback( - (val: OptionType | OptionType[] | null) => { - const projectFlock = val as OptionType | null; - const projectFlockId = projectFlock?.value - ? String(projectFlock.value) - : null; - - formik.setFieldValue('project_flock_id', projectFlockId); - formik.setFieldValue('kandang_id', null); - formik.setFieldValue('project_flock_kandang_id', null); - - setFilterProjectFlock(projectFlock); - setFilterKandang(null); - }, - [formik] - ); - - const handleFilterKandangChange = useCallback( - (val: OptionType | OptionType[] | null) => { - const kandang = val as OptionType | null; - const kandangId = kandang?.value ? String(kandang.value) : null; - - formik.setFieldValue('kandang_id', kandangId); - formik.setFieldValue('project_flock_kandang_id', null); - - setFilterKandang(kandang); - }, - [formik] - ); - - // ===== FILTER HELPERS ===== - const areaIdValue = useMemo(() => { - if (!formik.values.area_id) return null; - return ( - areaOptions.find((opt) => String(opt.value) === formik.values.area_id) || - null - ); - }, [formik.values.area_id, areaOptions]); - - const locationIdValue = useMemo(() => { - if (!formik.values.location_id) return null; - return ( - locationOptions.find( - (opt) => String(opt.value) === formik.values.location_id - ) || null - ); - }, [formik.values.location_id, locationOptions]); - - const projectFlockIdValue = useMemo(() => { - if (!filterProjectFlock) return null; - return filterProjectFlock; - }, [filterProjectFlock]); - - const kandangIdValue = useMemo(() => { - if (!formik.values.kandang_id) return null; - return ( - kandangOptions.find( - (opt) => String(opt.value) === formik.values.kandang_id - ) || null - ); - }, [formik.values.kandang_id, kandangOptions]); - - const recordingApprovalStatusOptions: OptionType[] = [ - { value: 'CREATED', label: 'Pengajuan' }, - { value: 'UPDATED', label: 'Diperbarui' }, - { value: 'APPROVED', label: 'Disetujui' }, - { value: 'REJECTED', label: 'Ditolak' }, - ]; - - const projectFlockCategoryOptions: OptionType[] = [ - { value: 'GROWING', label: 'Growing' }, - { value: 'LAYING', label: 'Laying' }, - ]; - - const approvalStatusValue = useMemo(() => { - if (!formik.values.approval_status) return null; - return ( - recordingApprovalStatusOptions.find( - (opt) => opt.value === formik.values.approval_status - ) || null - ); - }, [formik.values.approval_status]); - - const projectFlockCategoryValue = useMemo(() => { - if (!formik.values.project_flock_category) return null; - return ( - projectFlockCategoryOptions.find( - (opt) => opt.value === formik.values.project_flock_category - ) || null - ); - }, [formik.values.project_flock_category]); + const handleFilterKandangChange = (val: OptionType | OptionType[] | null) => { + formik.setFieldValue('kandang_id', val); + formik.setFieldValue('project_flock_kandang_id', null); + }; // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { @@ -650,25 +558,9 @@ const RecordingTable = () => { formik.validateForm(); }; - const isRecordingApproved = useCallback((recording: Recording): boolean => { - return ( - recording.approval?.action === 'APPROVED' && - recording.approval?.step_name === 'Disetujui' - ); - }, []); - - useEffect(() => { - setTableState('recording-table', pathname); - }, [pathname, setTableState]); - - const searchChangeHandler = useCallback( - (e: React.ChangeEvent) => { - updateFilter('search', e.target.value); - setSearchValue(e.target.value); - setPage(1); - }, - [updateFilter, setSearchValue, setPage] - ); + const searchChangeHandler = (e: React.ChangeEvent) => { + updateFilter('search', e.target.value, true); + }; const singleDeleteHandler = async () => { setIsDeleteLoading(true); @@ -1220,7 +1112,7 @@ const RecordingTable = () => { return (
{value !== null && value !== undefined - ? `${value.toFixed(2)}%` + ? `${value.toFixed(2)} butir` : '-'}
); @@ -1236,7 +1128,7 @@ const RecordingTable = () => { return (
{value !== null && value !== undefined - ? `${value.toFixed(2)}%` + ? `${value.toFixed(2)} btr` : '-'}
); @@ -1572,13 +1464,13 @@ const RecordingTable = () => { -
+
{ label='Lokasi' placeholder='Pilih Lokasi' options={locationOptions} - value={locationIdValue} + value={formik.values.location_id} onChange={handleFilterLocationChange} onInputChange={setLocationInputValue} isLoading={isLoadingLocationOptions} isClearable onMenuScrollToBottom={loadMoreLocations} - isDisabled={!filterArea} + isDisabled={!formik.values.area_id?.value} className={{ wrapper: 'w-full' }} /> @@ -1605,13 +1497,13 @@ const RecordingTable = () => { label='Project Flock' placeholder='Pilih Project Flock' options={projectFlockOptions} - value={projectFlockIdValue} + value={formik.values.project_flock_id} onChange={handleFilterProjectFlockChange} onInputChange={setProjectFlockInputValue} isLoading={isLoadingProjectFlocks} isClearable onMenuScrollToBottom={loadMoreProjectFlocks} - isDisabled={!filterLocation} + isDisabled={!formik.values.location_id?.value} className={{ wrapper: 'w-full' }} /> @@ -1619,11 +1511,11 @@ const RecordingTable = () => { label='Kandang' placeholder='Pilih Kandang' options={kandangOptions} - value={kandangIdValue} + value={formik.values.kandang_id} onChange={handleFilterKandangChange} - isLoading={!filterProjectFlock} + isLoading={!formik.values.project_flock_id?.value} isClearable - isDisabled={!filterProjectFlock} + isDisabled={!formik.values.project_flock_id?.value} className={{ wrapper: 'w-full' }} /> @@ -1631,12 +1523,9 @@ const RecordingTable = () => { label='Kategori' placeholder='Pilih Kategori' options={projectFlockCategoryOptions} - value={projectFlockCategoryValue} + value={formik.values.project_flock_category} onChange={(val) => { - formik.setFieldValue( - 'project_flock_category', - !Array.isArray(val) && val ? String(val.value) : null - ); + formik.setFieldValue('project_flock_category', val); }} isClearable className={{ wrapper: 'w-full' }} @@ -1646,12 +1535,9 @@ const RecordingTable = () => { label='Status Approval' placeholder='Pilih Status Approval' options={recordingApprovalStatusOptions} - value={approvalStatusValue} + value={formik.values.approval_status} onChange={(val) => { - formik.setFieldValue( - 'approval_status', - !Array.isArray(val) && val ? String(val.value) : null - ); + formik.setFieldValue('approval_status', val); }} isClearable className={{ wrapper: 'w-full' }} @@ -1661,19 +1547,9 @@ const RecordingTable = () => { {/* Modal Footer */}
diff --git a/src/components/pages/production/recording/filter/RecordingFilter.ts b/src/components/pages/production/recording/filter/RecordingFilter.ts index 01caced0..c119494e 100644 --- a/src/components/pages/production/recording/filter/RecordingFilter.ts +++ b/src/components/pages/production/recording/filter/RecordingFilter.ts @@ -1,21 +1,40 @@ -import { string, object } from 'yup'; +import { OptionType } from '@/components/input/SelectInput'; +import * as Yup from 'yup'; -export const RecordingFilterSchema = object().shape({ - area_id: string().nullable(), - location_id: string().nullable(), - project_flock_id: string().nullable(), - kandang_id: string().nullable(), - project_flock_kandang_id: string().nullable(), - approval_status: string().nullable(), - project_flock_category: string().nullable(), +export const RecordingFilterSchema = Yup.object().shape({ + area_id: Yup.object({ + value: Yup.number().nullable(), + label: Yup.string().nullable(), + }).nullable(), + location_id: Yup.object({ + value: Yup.number().nullable(), + label: Yup.string().nullable(), + }).nullable(), + project_flock_id: Yup.object({ + value: Yup.number().nullable(), + label: Yup.string().nullable(), + }).nullable(), + kandang_id: Yup.object({ + value: Yup.number().nullable(), + label: Yup.string().nullable(), + }).nullable(), + project_flock_kandang_id: Yup.number().nullable(), + approval_status: Yup.object({ + value: Yup.string().nullable(), + label: Yup.string().nullable(), + }).nullable(), + project_flock_category: Yup.object({ + value: Yup.string().nullable(), + label: Yup.string().nullable(), + }).nullable(), }); export type RecordingFilterType = { - area_id: string | null; - location_id: string | null; - project_flock_id: string | null; - kandang_id: string | null; - project_flock_kandang_id: string | null; - approval_status: string | null; - project_flock_category: string | null; + area_id: OptionType | null; + location_id: OptionType | null; + project_flock_id: OptionType | null; + kandang_id: OptionType | null; + project_flock_kandang_id: number | null; + approval_status: OptionType | null; + project_flock_category: OptionType | null; };