From 49e9e958fa106fb8da125d5c456252d0db8dccb9 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 22 Jan 2026 14:20:16 +0700 Subject: [PATCH 1/4] refactor(FE): Use local state for record date and disable reinitialization --- .../recording/form/RecordingForm.tsx | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 7365c6ca..65f72925 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -197,6 +197,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const router = useRouter(); // ===== STATE MANAGEMENT ===== + const [selectedRecordDate, setSelectedRecordDate] = useState( + initialValues?.record_datetime + ? new Date(initialValues.record_datetime).toISOString().split('T')[0] + : new Date().toISOString().split('T')[0] + ); const [selectedStocks, setSelectedStocks] = useState([]); const [selectedDepletions, setSelectedDepletions] = useState([]); const [selectedEggs, setSelectedEggs] = useState([]); @@ -911,6 +916,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { baseValues = getRecordingGrowingFormInitialValues(initialValues); } + baseValues.record_date = selectedRecordDate; + if (type === 'add') { baseValues.location = selectedLocation ? { @@ -967,13 +974,22 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return baseValues; - }, [initialValues, isLayingCategory, projectFlockKandangDetail, type]); + }, [ + initialValues, + isLayingCategory, + projectFlockKandangDetail, + type, + selectedRecordDate, + selectedLocation, + selectedProjectFlock, + selectedKandang, + ]); const formik = useFormik< RecordingGrowingFormValues | RecordingLayingFormValues >({ initialValues: formikInitialValues, - enableReinitialize: true, + enableReinitialize: false, validationSchema: (() => { let schema; if (isLayingCategory) { @@ -1333,6 +1349,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { (e: React.ChangeEvent) => { const newDate = e.target.value; formik.setFieldValue('record_date', newDate); + setSelectedRecordDate(newDate); setCurrentRecordDate(newDate); if (duplicateErrorShown) { toast.dismiss(); From 9d6148e8778fbf07cc0dfd89699b153bbf2e0dbd Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 22 Jan 2026 14:46:38 +0700 Subject: [PATCH 2/4] refactor(FE): Add tooltip and allow 3-decimal weight input --- .../recording/form/RecordingForm.tsx | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 65f72925..a000f4a3 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -23,6 +23,7 @@ import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWith import Modal, { useModal } from '@/components/Modal'; import AlertErrorList from '@/components/helper/form/FormErrors'; import Table from '@/components/Table'; +import Tooltip from '@/components/Tooltip'; import { type ColumnDef } from '@tanstack/react-table'; import { @@ -2816,7 +2817,23 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { )} Kondisi Telur Jumlah - Total Berat (Kilogram) + + Total Berat (Kilogram) + + + + {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( Action )} @@ -2922,7 +2939,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { value={egg.weight ?? ''} onChange={handleEggWeightChangeWrapper(idx)} onBlur={formik.handleBlur} - decimalScale={0} + decimalScale={3} allowNegative={false} thousandSeparator=',' decimalSeparator='.' From 756701722a55687bdd50d365d92202a081e3b8a6 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 22 Jan 2026 15:24:04 +0700 Subject: [PATCH 3/4] feat(FE): Persist Recording search across navigation --- .../production/recording/RecordingTable.tsx | 39 +++++++++++++++++-- src/stores/table/slices/table.slice.ts | 25 ++++++++++++ src/stores/table/table.store.ts | 27 +++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/stores/table/slices/table.slice.ts create mode 100644 src/stores/table/table.store.ts diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 8d8caad1..4c83cb02 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -1,6 +1,12 @@ 'use client'; -import React, { useCallback, useState, useMemo, useEffect } from 'react'; +import React, { + useCallback, + useState, + useMemo, + useEffect, + useRef, +} from 'react'; import { RefObject } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; @@ -28,6 +34,7 @@ import { useTableFilter } from '@/services/hooks/useTableFilter'; import toast from 'react-hot-toast'; import Badge from '@/components/Badge'; import CheckboxInput from '@/components/input/CheckboxInput'; +import { useTableStore } from '@/stores/table/table.store'; import { BaseApproval, BaseApiResponse } from '@/types/api/api-general'; const RowOptionsMenu = ({ @@ -344,6 +351,9 @@ const ApprovalHistoryModal = ({ }; const RecordingTable = () => { + const { searchValue, setSearchValue, resetSearchValue } = useTableStore(); + const previousPathRef = useRef(null); + const { state: tableFilterState, updateFilter, @@ -352,7 +362,7 @@ const RecordingTable = () => { toQueryString: getTableFilterQueryString, } = useTableFilter({ initial: { - search: '', + search: searchValue, areaFilter: '', locationFilter: '', kandangFilter: '', @@ -403,12 +413,35 @@ const RecordingTable = () => { ); }, []); + useEffect(() => { + // Store current path on mount + previousPathRef.current = window.location.pathname; + + return () => { + const currentPath = window.location.pathname; + + // if both paths are within /production/recording module + const isCurrentPathRecording = currentPath.includes( + '/production/recording' + ); + const isPreviousPathRecording = previousPathRef.current?.includes( + '/production/recording' + ); + + // reset if we outside recording module entirely + if (isPreviousPathRecording && !isCurrentPathRecording) { + resetSearchValue(); + } + }; + }, [resetSearchValue]); + const searchChangeHandler = useCallback( (e: React.ChangeEvent) => { updateFilter('search', e.target.value); + setSearchValue(e.target.value); setPage(1); }, - [updateFilter, setPage] + [updateFilter, setSearchValue, setPage] ); const pageSizeChangeHandler = useCallback( diff --git a/src/stores/table/slices/table.slice.ts b/src/stores/table/slices/table.slice.ts new file mode 100644 index 00000000..4333e157 --- /dev/null +++ b/src/stores/table/slices/table.slice.ts @@ -0,0 +1,25 @@ +import { StateCreator } from 'zustand'; + +export interface TableState { + searchValue: string; +} + +export interface TableSlice { + searchValue: string; + setSearchValue: (value: string) => void; + resetSearchValue: () => void; +} + +export const createTableSlice: StateCreator = ( + set +) => ({ + // Initial state + searchValue: '', + + // Actions + setSearchValue: (value) => set({ searchValue: value }), + + resetSearchValue: () => { + return set({ searchValue: '' }); + }, +}); diff --git a/src/stores/table/table.store.ts b/src/stores/table/table.store.ts new file mode 100644 index 00000000..54b5d1c4 --- /dev/null +++ b/src/stores/table/table.store.ts @@ -0,0 +1,27 @@ +'use client'; + +import { create } from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; +import { createTableSlice } from '@/stores/table/slices/table.slice'; +import type { TableSlice } from '@/stores/table/slices/table.slice'; + +export type TableStore = TableSlice; + +export const useTableStore = create()( + devtools( + persist( + (...args) => ({ + ...createTableSlice(...args), + }), + { + name: 'table-cache', + partialize: (state) => ({ + searchValue: state.searchValue, + }), + } + ), + { + name: 'TableStore', + } + ) +); From 1f6ce3697684d0a1e5e060ab8623387714635d7e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 22 Jan 2026 15:58:56 +0700 Subject: [PATCH 4/4] refactor(FE): Move table slice into UI store and persist search --- .../production/recording/RecordingTable.tsx | 4 +-- src/stores/table/table.store.ts | 27 ------------------- .../{table => ui}/slices/table.slice.ts | 11 +++++--- src/stores/ui/ui.store.ts | 20 ++++++++++---- src/types/stores.d.ts | 8 +++++- 5 files changed, 31 insertions(+), 39 deletions(-) delete mode 100644 src/stores/table/table.store.ts rename src/stores/{table => ui}/slices/table.slice.ts (73%) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 4c83cb02..cd98b597 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -34,7 +34,7 @@ import { useTableFilter } from '@/services/hooks/useTableFilter'; import toast from 'react-hot-toast'; import Badge from '@/components/Badge'; import CheckboxInput from '@/components/input/CheckboxInput'; -import { useTableStore } from '@/stores/table/table.store'; +import { useUiStore } from '@/stores/ui/ui.store'; import { BaseApproval, BaseApiResponse } from '@/types/api/api-general'; const RowOptionsMenu = ({ @@ -351,7 +351,7 @@ const ApprovalHistoryModal = ({ }; const RecordingTable = () => { - const { searchValue, setSearchValue, resetSearchValue } = useTableStore(); + const { searchValue, setSearchValue, resetSearchValue } = useUiStore(); const previousPathRef = useRef(null); const { diff --git a/src/stores/table/table.store.ts b/src/stores/table/table.store.ts deleted file mode 100644 index 54b5d1c4..00000000 --- a/src/stores/table/table.store.ts +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; - -import { create } from 'zustand'; -import { devtools, persist } from 'zustand/middleware'; -import { createTableSlice } from '@/stores/table/slices/table.slice'; -import type { TableSlice } from '@/stores/table/slices/table.slice'; - -export type TableStore = TableSlice; - -export const useTableStore = create()( - devtools( - persist( - (...args) => ({ - ...createTableSlice(...args), - }), - { - name: 'table-cache', - partialize: (state) => ({ - searchValue: state.searchValue, - }), - } - ), - { - name: 'TableStore', - } - ) -); diff --git a/src/stores/table/slices/table.slice.ts b/src/stores/ui/slices/table.slice.ts similarity index 73% rename from src/stores/table/slices/table.slice.ts rename to src/stores/ui/slices/table.slice.ts index 4333e157..eb6e7cc2 100644 --- a/src/stores/table/slices/table.slice.ts +++ b/src/stores/ui/slices/table.slice.ts @@ -4,15 +4,18 @@ export interface TableState { searchValue: string; } -export interface TableSlice { +export interface TableUISlice { searchValue: string; setSearchValue: (value: string) => void; resetSearchValue: () => void; } -export const createTableSlice: StateCreator = ( - set -) => ({ +export const createTableUISlice: StateCreator< + TableUISlice, + [], + [], + TableUISlice +> = (set) => ({ // Initial state searchValue: '', diff --git a/src/stores/ui/ui.store.ts b/src/stores/ui/ui.store.ts index cbc5785d..05adbb9b 100644 --- a/src/stores/ui/ui.store.ts +++ b/src/stores/ui/ui.store.ts @@ -1,18 +1,28 @@ 'use client'; import { create } from 'zustand'; -import { devtools } from 'zustand/middleware'; +import { devtools, persist } from 'zustand/middleware'; import { UIStore } from '@/types/stores'; import { createMainUiSlice } from '@/stores/ui/slices/main.slice'; import { createDrawerUISlice } from '@/stores/ui/slices/drawer.slice'; +import { createTableUISlice } from '@/stores/ui/slices/table.slice'; export const useUiStore = create()( devtools( - (...args) => ({ - ...createMainUiSlice(...args), - ...createDrawerUISlice(...args), - }), + persist( + (...args) => ({ + ...createMainUiSlice(...args), + ...createDrawerUISlice(...args), + ...createTableUISlice(...args), + }), + { + name: 'ui-cache', + partialize: (state) => ({ + searchValue: state.searchValue, + }), + } + ), { name: 'UIStore', } diff --git a/src/types/stores.d.ts b/src/types/stores.d.ts index 528309c7..5b0be6f3 100644 --- a/src/types/stores.d.ts +++ b/src/types/stores.d.ts @@ -26,7 +26,13 @@ type DrawerUISlice = { setIsNextStep: (v: boolean) => void; }; -export type UIStore = MainUiSlice & DrawerUISlice; +type TableUISlice = { + searchValue: string; + setSearchValue: (value: string) => void; + resetSearchValue: () => void; +}; + +export type UIStore = MainUiSlice & DrawerUISlice & TableUISlice; type ProductionStandardFormSlice = { formData: {