'use client'; import Button from '@/components/Button'; import { Icon } from '@iconify/react'; import Modal, { useModal } from '@/components/Modal'; import DateInput from '@/components/input/DateInput'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import { useState, useEffect, useRef, useCallback } from 'react'; import useSWR from 'swr'; import { DashboardApi } from '@/services/api/dashboard'; import { useFormik } from 'formik'; import { ProjectFlockApi } from '@/services/api/production'; import { KandangApi, LocationApi } from '@/services/api/master-data'; import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF'; import { DashboardFilterType, getDashboardFilterSchema, } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; import DashboardLineChart from '@/components/pages/dashboard/chart/DashboardLineChart'; import DashboardLineChartSkeleton from '@/components/pages/dashboard/skeleton/DashboardLineChartSkeleton'; import DashboardExportCharts, { DashboardExportChartsRef, } from '@/components/pages/dashboard/export/DashboardExportCharts'; import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput'; import { DashboardFilter, DashboardMeta, } from '@/types/api/dashboard/dashboard'; import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats'; import { isResponseSuccess } from '@/lib/api-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; import ButtonFilter from '@/components/helper/ButtonFilter'; import Dropdown from '@/components/Dropdown'; import Menu from '@/components/menu/Menu'; import MenuItem from '@/components/menu/MenuItem'; import { useDashboardStore } from '@/stores/dashboard'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import { useUiStore } from '@/stores/ui/ui.store'; import { cn } from '@/lib/helper'; import DashboardExportStats, { DashboardExportStatsRef, } from '@/components/pages/dashboard/export/DashboardExportStats'; // Helper function to normalize values to array const normalizeToArray = ( value: OptionType | OptionType[] | null | undefined ): number[] => { if (!value) return []; if (Array.isArray(value)) { return value.map((v) => Number(v.value)); } return [Number(value.value)]; }; const DashboardProduction = () => { const filterModal = useModal(); // ===== DASHBOARD STORE ===== const { filterValues, setFilterValues, resetFilterValues } = useDashboardStore(); // ===== UI STORE (for navbar actions) ===== const setNavbarActions = useUiStore((state) => state.setNavbarActions); const clearNavbarActions = useUiStore((state) => state.clearNavbarActions); const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>( (filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON') || 'OVERVIEW' ); const [endpointUrl, setEndpointUrl] = useState('/dashboards'); const [selectedLocationIds, setSelectedLocationIds] = useState( normalizeToArray(filterValues.location) ); const [exporting, setExporting] = useState(false); const allChartsRef = useRef(null); const allStatsRef = useRef(null); // ===== FETCH DATA ===== const { data: dashboardProductionResponse, isLoading: isLoadingDashboardProductionData, mutate: refreshDashboardProductionData, } = useSWR(endpointUrl, () => DashboardApi.getDashboardProductionFetcher(endpointUrl) ); const dashboardProductionData = isResponseSuccess(dashboardProductionResponse) ? dashboardProductionResponse.data : undefined; // ===== SELECT ===== const { setInputValue: setInputValueFlock, options: flockOptions, isLoadingOptions: isLoadingFlockOptions, loadMore: loadMoreFlock, } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', '', { location_id: selectedLocationIds ? selectedLocationIds.toString() : '', }); const { setInputValue: setInputValueLocation, options: locationOptions, isLoadingOptions: isLoadingLocationOptions, loadMore: loadMoreLocation, } = useSelect(LocationApi.basePath, 'id', 'name'); const { setInputValue: setInputValueKandang, options: kandangOptions, isLoadingOptions: isLoadingKandangOptions, loadMore: loadMoreKandang, } = useSelect(KandangApi.basePath, 'id', 'name', '', { location_id: selectedLocationIds ? selectedLocationIds.toString() : '', }); const comparisonTypeOptions = [ { value: 'FARM', label: 'Farm' }, { value: 'FLOCK', label: 'Flock' }, { value: 'KANDANG', label: 'Kandang' }, ]; // ===== FORMIK ===== const formik = useFormik({ initialValues: { startDate: filterValues.startDate ?? '', endDate: filterValues.endDate ?? '', flock: filterValues.flock ?? ([] as OptionType[]), location: filterValues.location ?? ([] as OptionType[]), kandang: filterValues.kandang ?? ([] as OptionType[]), analysisMode: filterValues.analysisMode ?? analysisMode, comparisonType: filterValues.comparisonType ?? '', locationIds: filterValues.locationIds ?? [], flockIds: filterValues.flockIds ?? [], kandangIds: filterValues.kandangIds ?? [], } as DashboardFilterType, enableReinitialize: true, validationSchema: getDashboardFilterSchema(analysisMode), onSubmit: (values) => { // Save filter values to store setFilterValues(values); handleApplyFilter({ start_date: values.startDate || '', end_date: values.endDate || '', analysis_mode: values.analysisMode as 'OVERVIEW' | 'COMPARISON', location_ids: normalizeToArray(values.location), flock_ids: normalizeToArray(values.flock), kandang_ids: normalizeToArray(values.kandang), comparison_type: values.comparisonType, }); }, }); const { resetForm } = formik; const handleResetFilter = useCallback(() => { resetForm(); resetFilterValues(); // Clear stored filter values setAnalysisMode('OVERVIEW'); setEndpointUrl('/dashboards'); setSelectedLocationIds([]); }, [resetForm, resetFilterValues]); const handleApplyFilter = useCallback( (values: DashboardFilter) => { // Build query params object, only include non-empty values const params: Record = {}; if (values.start_date) params.start_date = values.start_date; if (values.end_date) params.end_date = values.end_date; if (values.analysis_mode) params.analysis_mode = values.analysis_mode; if (values.location_ids.length > 0) params.location_ids = values.location_ids.toString(); if (values.flock_ids.length > 0) params.flock_ids = values.flock_ids.toString(); if (values.kandang_ids.length > 0) params.kandang_ids = values.kandang_ids.toString(); if (values.comparison_type) params.comparison_type = values.comparison_type; setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`); filterModal.closeModal(); refreshDashboardProductionData(); }, [filterModal, 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, handleApplyFilter]); // ===== Formik Error List ===== const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); // ===== Export PDF ===== const handleExportPDF = useCallback(async () => { await generateDashboardPDF({ filterValues: formik.values, allStatsRef, allChartsRef, setExporting, }); }, [formik.values]); // ===== Register Navbar Actions ===== const openFilterModalRef = useRef(filterModal.openModal); openFilterModalRef.current = filterModal.openModal; useEffect(() => { setNavbarActions(
openFilterModalRef.current()} /> Export
} className={{ content: 'w-full mt-1 p-0', }} >
); }, [formik.values, exporting, setNavbarActions, handleExportPDF]); // Cleanup only on unmount useEffect(() => { return () => { clearNavbarActions(); }; }, [clearNavbarActions]); if (isLoadingDashboardProductionData) { return (
); } return ( <>
openFilterModalRef.current()} /> Export
} className={{ content: 'w-full mt-1 p-0 shadow-button-soft border border-base-content/10 rounded-lg', }} >
{/* Dashboard Stats */}
{/* Use DashboardLineChart component or skeleton */}
{isLoadingDashboardProductionData ? ( ) : dashboardProductionData && dashboardProductionData.charts && Object.keys(dashboardProductionData.charts).length > 0 ? ( ) : ( )}
{/* Hidden container for all charts (used for PDF export in OVERVIEW mode) */} {dashboardProductionData && ( <> {/* Export Stats Charts */}
{/* Export ALL Charts */}
)}
{/* Modal Header */}

Filter Data

{/* Rentang Waktu */}

{/* Analysis Mode */}
{ formik.handleChange(e); setAnalysisMode( e.target.value as 'OVERVIEW' | 'COMPARISON' ); // Reset all dependent fields when analysis mode changes formik.setFieldValue('location', []); formik.setFieldValue('flock', []); formik.setFieldValue('kandang', []); formik.setFieldValue('comparisonType', ''); setSelectedLocationIds([]); }} color='primary' className={{ wrapper: 'w-full flex flex-row items-center font-medium text-base-content/50', radioWrapper: 'w-full grid grid-cols-2 gap-0 p-0', }} >
{formik.values.analysisMode === 'COMPARISON' && ( option.value === formik.values.comparisonType )} onChange={(selected) => formik.setFieldValue( 'comparisonType', selected ? (selected as OptionType).value : '' ) } errorMessage={formik.errors.comparisonType as string} options={comparisonTypeOptions} isLoading={isLoadingLocationOptions} isError={ Boolean(formik.errors.comparisonType) && Boolean(formik.touched.comparisonType) } className={{ select: 'rounded-lg text-sm border-base-content/10', }} /> )} {/* Location */} {comparisonTypeOptions.find( (option) => option.value === formik.values.comparisonType )?.value === 'FARM' ? ( { formik.setFieldValue('location', selected); // Update selectedLocationIds for kandang filter setSelectedLocationIds(normalizeToArray(selected)); // Reset dependent fields when location changes formik.setFieldValue('flock', []); formik.setFieldValue('kandang', []); }} errorMessage={formik.errors.location as string} options={locationOptions} isLoading={isLoadingLocationOptions} isError={ Boolean(formik.errors.location) && Boolean(formik.touched.location) } className={{ select: 'rounded-lg text-sm border-base-content/10', }} /> ) : ( { formik.setFieldValue('location', selected); // Update selectedLocationIds for kandang filter setSelectedLocationIds(normalizeToArray(selected)); // Reset dependent fields when location changes formik.setFieldValue('flock', []); formik.setFieldValue('kandang', []); }} errorMessage={formik.errors.location as string} options={locationOptions} isLoading={isLoadingLocationOptions} isError={ Boolean(formik.errors.location) && Boolean(formik.touched.location) } className={{ select: 'rounded-lg text-sm border-base-content/10', }} /> )} {/* Flock */} {!( formik.values.analysisMode === 'COMPARISON' && !( formik.values.comparisonType === 'FLOCK' || formik.values.comparisonType === 'KANDANG' ) ) && ( <> {comparisonTypeOptions.find( (option) => option.value === formik.values.comparisonType )?.value === 'FLOCK' ? ( formik.setFieldValue('flock', selected) } errorMessage={formik.errors.flock as string} onInputChange={setInputValueFlock} onMenuScrollToBottom={loadMoreFlock} options={flockOptions} isLoading={isLoadingFlockOptions} isError={ Boolean(formik.errors.flock) && Boolean(formik.touched.flock) } className={{ select: 'rounded-lg text-sm border-base-content/10', }} /> ) : ( formik.setFieldValue('flock', selected) } errorMessage={formik.errors.flock as string} onInputChange={setInputValueFlock} onMenuScrollToBottom={loadMoreFlock} options={flockOptions} isLoading={isLoadingFlockOptions} isError={ Boolean(formik.errors.flock) && Boolean(formik.touched.flock) } className={{ select: 'rounded-lg text-sm border-base-content/10', }} /> )} )} {/* Kandang */} {!( formik.values.analysisMode === 'COMPARISON' && !(formik.values.comparisonType === 'KANDANG') ) && ( <> {comparisonTypeOptions.find( (option) => option.value === formik.values.comparisonType )?.value === 'KANDANG' ? ( formik.setFieldValue('kandang', selected) } errorMessage={formik.errors.kandang as string} onInputChange={setInputValueKandang} onMenuScrollToBottom={loadMoreKandang} options={kandangOptions} isLoading={isLoadingKandangOptions} isError={ Boolean(formik.errors.kandang) && Boolean(formik.touched.kandang) } className={{ select: 'rounded-lg text-sm border-base-content/10', }} /> ) : ( formik.setFieldValue('kandang', selected) } errorMessage={formik.errors.kandang as string} onInputChange={setInputValueKandang} onMenuScrollToBottom={loadMoreKandang} options={kandangOptions} isLoading={isLoadingKandangOptions} isError={ Boolean(formik.errors.kandang) && Boolean(formik.touched.kandang) } className={{ select: 'rounded-lg text-sm border-base-content/10', }} /> )} )} {formErrorList.length > 0 && (
)}
{/* Action Buttons */}
); }; export default DashboardProduction;