'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, ProjectFlockKandangApi, } from '@/services/api/production'; import { 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 { 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'; import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; // 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 [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, } = useSWR( [ 'dashboard-production', filterValues.startDate ?? '', filterValues.endDate ?? '', filterValues.analysisMode ?? 'OVERVIEW', normalizeToArray(filterValues.location).toString(), normalizeToArray(filterValues.flock).toString(), normalizeToArray(filterValues.kandang).toString(), filterValues.comparisonType ?? '', ], () => DashboardApi.getDashboardProductionFetcher({ start_date: filterValues.startDate || '', end_date: filterValues.endDate || '', analysis_mode: (filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON') || 'OVERVIEW', location_ids: normalizeToArray(filterValues.location), flock_ids: normalizeToArray(filterValues.flock), kandang_ids: normalizeToArray(filterValues.kandang), comparison_type: filterValues.comparisonType || '', }) ); const dashboardProductionData = isResponseSuccess(dashboardProductionResponse) ? dashboardProductionResponse.data : undefined; // ===== SELECT ===== const { setInputValue: setInputValueFlock, options: flockOptions, isLoadingOptions: isLoadingFlockOptions, loadMore: loadMoreFlock, } = useSelect( ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { location_id: selectedLocationIds ? selectedLocationIds.toString() : '', } ); const { setInputValue: setInputValueLocation, options: locationOptions, isLoadingOptions: isLoadingLocationOptions, loadMore: loadMoreLocation, } = useSelect(LocationApi.basePath, 'id', 'name'); 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) => { setFilterValues(values); filterModal.closeModal(); }, }); const { resetForm } = formik; const selectedLocationValues = normalizeToArray(formik.values.location); const selectedFlockValues = normalizeToArray(formik.values.flock); const { setInputValue: setInputValueKandang, options: kandangOptions, isLoadingOptions: isLoadingKandangOptions, loadMore: loadMoreKandang, } = useSelect( ProjectFlockKandangApi.basePath, 'id', 'name_with_period', 'search', { location_id: selectedLocationValues.length > 0 ? selectedLocationValues.toString() : '', project_flock_id: selectedFlockValues.length > 0 ? selectedFlockValues.toString() : '', } ); const handleResetFilter = useCallback(() => { resetForm(); resetFilterValues(); // Clear stored filter values setAnalysisMode('OVERVIEW'); setSelectedLocationIds([]); filterModal.closeModal(); }, [filterModal, resetForm, resetFilterValues]); // ===== 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]); return ( <>
openFilterModalRef.current()} /> Export
} className={{ content: 'w-full mt-1 p-0 shadow-button-soft border border-base-content/10 rounded-lg', }} >
{/* Dashboard Stats */}
{isLoadingDashboardProductionData ? (
) : ( )}
{/* 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) */} {exporting && 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', }} isClearable={true} /> )} {/* 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', }} isClearable={true} /> ) : ( { 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', }} isClearable={true} /> )} {/* 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', }} isClearable={true} /> ) : ( 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', }} isClearable={true} /> )} )} {/* 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', }} isClearable={true} /> ) : ( 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', }} isClearable={true} /> )} )} {formErrorList.length > 0 && (
)}
{/* Action Buttons */}
); }; export default DashboardProduction;