feat(FE): add state filter dashboard with store

This commit is contained in:
randy-ar
2026-01-18 19:30:54 +07:00
parent a9c22d778b
commit 55b50d4184
8 changed files with 132 additions and 21 deletions
@@ -15,7 +15,6 @@ import { useFormik } from 'formik';
import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlockApi } from '@/services/api/production';
import { KandangApi, LocationApi } from '@/services/api/master-data'; import { KandangApi, LocationApi } from '@/services/api/master-data';
import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF'; import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF';
import { import {
DashboardFilterType, DashboardFilterType,
getDashboardFilterSchema, getDashboardFilterSchema,
@@ -38,6 +37,7 @@ import ButtonFilter from '@/components/helper/ButtonFilter';
import Dropdown from '@/components/Dropdown'; import Dropdown from '@/components/Dropdown';
import Menu from '@/components/menu/Menu'; import Menu from '@/components/menu/Menu';
import MenuItem from '@/components/menu/MenuItem'; import MenuItem from '@/components/menu/MenuItem';
import { useDashboardStore } from '@/stores/dashboard';
// Helper function to normalize values to array // Helper function to normalize values to array
const normalizeToArray = ( const normalizeToArray = (
@@ -52,11 +52,18 @@ const normalizeToArray = (
const DashboardProduction = () => { const DashboardProduction = () => {
const filterModal = useModal(); const filterModal = useModal();
// ===== DASHBOARD STORE =====
const { filterValues, setFilterValues, resetFilterValues } =
useDashboardStore();
const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>( const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>(
'OVERVIEW' (filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON') || 'OVERVIEW'
); );
const [endpointUrl, setEndpointUrl] = useState('/dashboards'); const [endpointUrl, setEndpointUrl] = useState('/dashboards');
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>([]); const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>(
normalizeToArray(filterValues.location)
);
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const statsRef = useRef<HTMLDivElement>(null); const statsRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<HTMLDivElement>(null); const chartRef = useRef<HTMLDivElement>(null);
@@ -111,19 +118,22 @@ const DashboardProduction = () => {
// ===== FORMIK ===== // ===== FORMIK =====
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
startDate: '', startDate: filterValues.startDate || '',
endDate: '', endDate: filterValues.endDate || '',
flock: [] as OptionType[], flock: filterValues.flock || ([] as OptionType[]),
location: [] as OptionType[], location: filterValues.location || ([] as OptionType[]),
kandang: [] as OptionType[], kandang: filterValues.kandang || ([] as OptionType[]),
analysisMode: analysisMode, analysisMode: filterValues.analysisMode || analysisMode,
comparisonType: '', comparisonType: filterValues.comparisonType || '',
lokasiIds: [], locationIds: filterValues.locationIds || [],
flockIds: [], flockIds: filterValues.flockIds || [],
kandangIds: [], kandangIds: filterValues.kandangIds || [],
} as DashboardFilterType, } as DashboardFilterType,
validationSchema: getDashboardFilterSchema(analysisMode), validationSchema: getDashboardFilterSchema(analysisMode),
onSubmit: (values) => { onSubmit: (values) => {
// Save filter values to store
setFilterValues(values);
handleApplyFilter({ handleApplyFilter({
start_date: values.startDate || '', start_date: values.startDate || '',
end_date: values.endDate || '', end_date: values.endDate || '',
@@ -138,8 +148,10 @@ const DashboardProduction = () => {
const handleResetFilter = () => { const handleResetFilter = () => {
formik.resetForm(); formik.resetForm();
resetFilterValues(); // Clear stored filter values
setAnalysisMode('OVERVIEW'); setAnalysisMode('OVERVIEW');
setEndpointUrl('/dashboards'); setEndpointUrl('/dashboards');
setSelectedLocationIds([]);
}; };
const handleApplyFilter = (values: DashboardFilter) => { const handleApplyFilter = (values: DashboardFilter) => {
@@ -162,6 +174,20 @@ const DashboardProduction = () => {
refreshDashboardProductionData(); refreshDashboardProductionData();
}; };
// ===== Load filter from store on mount =====
useEffect(() => {
if (!filterValues) return;
handleApplyFilter({
start_date: filterValues.startDate,
end_date: filterValues.endDate,
analysis_mode: filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON',
location_ids: normalizeToArray(filterValues.location),
flock_ids: normalizeToArray(filterValues.flock),
kandang_ids: normalizeToArray(filterValues.kandang),
comparison_type: filterValues.comparisonType,
});
}, [filterValues]);
// ===== Formik Error List ===== // ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
@@ -512,7 +538,6 @@ const DashboardProduction = () => {
type='reset' type='reset'
variant='soft' variant='soft'
className='ms-4 min-w-36 rounded-lg' className='ms-4 min-w-36 rounded-lg'
onClick={handleResetFilter}
> >
Reset Filter Reset Filter
</Button> </Button>
@@ -32,8 +32,9 @@ export type DashboardAllChartsRef = {
// Type guard to check if charts is DashboardOverviewCharts // Type guard to check if charts is DashboardOverviewCharts
function isOverviewCharts( function isOverviewCharts(
charts: DashboardOverviewCharts | DashboardComparisonCharts charts: DashboardOverviewCharts | DashboardComparisonCharts | undefined
): charts is DashboardOverviewCharts { ): charts is DashboardOverviewCharts {
if (!charts) return false;
return ( return (
'deplesi' in charts || 'deplesi' in charts ||
'body_weight' in charts || 'body_weight' in charts ||
@@ -45,8 +46,9 @@ function isOverviewCharts(
// Type guard to check if charts is DashboardComparisonCharts // Type guard to check if charts is DashboardComparisonCharts
function isComparisonCharts( function isComparisonCharts(
charts: DashboardOverviewCharts | DashboardComparisonCharts charts: DashboardOverviewCharts | DashboardComparisonCharts | undefined
): charts is DashboardComparisonCharts { ): charts is DashboardComparisonCharts {
if (!charts) return false;
return 'farm' in charts || 'flock' in charts || 'kandang' in charts; return 'farm' in charts || 'flock' in charts || 'kandang' in charts;
} }
@@ -32,8 +32,9 @@ type DashboardLineChartProps = {
// Type guard to check if charts is DashboardOverviewCharts // Type guard to check if charts is DashboardOverviewCharts
function isOverviewCharts( function isOverviewCharts(
charts: DashboardOverviewCharts | DashboardComparisonCharts charts: DashboardOverviewCharts | DashboardComparisonCharts | undefined
): charts is DashboardOverviewCharts { ): charts is DashboardOverviewCharts {
if (!charts) return false;
return ( return (
'deplesi' in charts || 'deplesi' in charts ||
'body_weight' in charts || 'body_weight' in charts ||
@@ -45,8 +46,9 @@ function isOverviewCharts(
// Type guard to check if charts is DashboardComparisonCharts // Type guard to check if charts is DashboardComparisonCharts
function isComparisonCharts( function isComparisonCharts(
charts: DashboardOverviewCharts | DashboardComparisonCharts charts: DashboardOverviewCharts | DashboardComparisonCharts | undefined
): charts is DashboardComparisonCharts { ): charts is DashboardComparisonCharts {
if (!charts) return false;
return 'farm' in charts || 'flock' in charts || 'kandang' in charts; return 'farm' in charts || 'flock' in charts || 'kandang' in charts;
} }
@@ -7,7 +7,7 @@ export type DashboardFilterType = {
analysisMode: string; analysisMode: string;
comparisonType: string | undefined; comparisonType: string | undefined;
location: OptionType | OptionType[]; location: OptionType | OptionType[];
lokasiIds: number[] | undefined; locationIds: number[] | undefined;
flock: OptionType | OptionType[] | undefined; flock: OptionType | OptionType[] | undefined;
flockIds: number[] | undefined; flockIds: number[] | undefined;
kandang: OptionType | OptionType[] | undefined; kandang: OptionType | OptionType[] | undefined;
@@ -25,7 +25,7 @@ export const DashboardFilterOverviewSchema: yup.ObjectSchema<DashboardFilterType
then: (schema) => schema.required('Compared by is required'), then: (schema) => schema.required('Compared by is required'),
otherwise: (schema) => schema.optional(), otherwise: (schema) => schema.optional(),
}), }),
lokasiIds: yup.array().optional(), locationIds: yup.array().optional(),
flockIds: yup.array().optional(), flockIds: yup.array().optional(),
kandangIds: yup.array().optional(), kandangIds: yup.array().optional(),
location: yup location: yup
@@ -68,7 +68,7 @@ export const DashboardFilterComparisonSchema: yup.ObjectSchema<DashboardFilterTy
then: (schema) => schema.required('Compared by is required'), then: (schema) => schema.required('Compared by is required'),
otherwise: (schema) => schema.optional(), otherwise: (schema) => schema.optional(),
}), }),
lokasiIds: yup.array().optional(), locationIds: yup.array().optional(),
flockIds: yup.array().optional(), flockIds: yup.array().optional(),
kandangIds: yup.array().optional(), kandangIds: yup.array().optional(),
location: yup location: yup
+24
View File
@@ -0,0 +1,24 @@
'use client';
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { createDashboardFilterSlice } from '@/stores/dashboard/slices/dashboard-filter.slice';
import { DashboardFilterSlice } from '@/types/stores';
export type DashboardStore = DashboardFilterSlice;
export const useDashboardStore = create<DashboardStore>()(
devtools(
persist(
(...args) => ({
...createDashboardFilterSlice(...args),
}),
{
name: 'dashboard-filter-cache',
}
),
{
name: 'DashboardStore',
}
)
);
+2
View File
@@ -0,0 +1,2 @@
export { useDashboardStore } from './dashboard.store';
export type { DashboardStore } from './dashboard.store';
@@ -0,0 +1,45 @@
import { DashboardFilterSlice } from '@/types/stores';
import { StateCreator } from 'zustand';
export const createDashboardFilterSlice: StateCreator<
DashboardFilterSlice,
[],
[],
DashboardFilterSlice
> = (set) => ({
// Initial state
filterValues: {
startDate: '',
endDate: '',
analysisMode: 'OVERVIEW',
comparisonType: undefined,
location: [],
locationIds: undefined,
flock: undefined,
flockIds: undefined,
kandang: undefined,
kandangIds: undefined,
},
// Actions
setFilterValues: (values) => set({ filterValues: values }),
resetFilterValues: () => {
alert('reset filter values');
return set({
filterValues: {
startDate: '',
endDate: '',
analysisMode: 'OVERVIEW',
comparisonType: undefined,
location: [],
locationIds: undefined,
flock: undefined,
flockIds: undefined,
kandang: undefined,
kandangIds: undefined,
},
});
},
});
+11
View File
@@ -1,3 +1,4 @@
import { DashboardFilterType } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema';
import type { ProductionStandardRepeaterFormSchemaValues } from '@/components/pages/master-data/production-standard/form/ProductionStandardForm.schema'; import type { ProductionStandardRepeaterFormSchemaValues } from '@/components/pages/master-data/production-standard/form/ProductionStandardForm.schema';
import type { import type {
UniformityFormData, UniformityFormData,
@@ -70,3 +71,13 @@ export type UniformitySlice = {
setCreatedUniformity: (data: UniformityDetail | null) => void; setCreatedUniformity: (data: UniformityDetail | null) => void;
resetUniformity: () => void; resetUniformity: () => void;
}; };
// Dashboard Filter Slice
export type DashboardFilterSlice = {
// State
filterValues: DashboardFilterType;
// Actions
setFilterValues: (values: DashboardFilterType) => void;
resetFilterValues: () => void;
};