From 43d26b4833d16d99c857c54e33735e4410dde72c Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 12 Feb 2026 11:16:26 +0700 Subject: [PATCH] refactor(FE): Refactor marketing report components and add HPP filter --- src/app/report/marketing/page.tsx | 6 +- .../marketing/MarketingReportContent.tsx | 59 +- .../marketing/filter/HppPerKandangFilter.ts | 40 + .../skeleton/HppPerKandangSkeleton.tsx | 37 + .../report/marketing/tab/HppPerKandangTab.tsx | 919 +++++++++++------- 5 files changed, 658 insertions(+), 403 deletions(-) create mode 100644 src/components/pages/report/marketing/filter/HppPerKandangFilter.ts create mode 100644 src/components/pages/report/marketing/skeleton/HppPerKandangSkeleton.tsx diff --git a/src/app/report/marketing/page.tsx b/src/app/report/marketing/page.tsx index 87ed7a1a..57035844 100644 --- a/src/app/report/marketing/page.tsx +++ b/src/app/report/marketing/page.tsx @@ -1,11 +1,7 @@ import MarketingReportContent from '@/components/pages/report/marketing/MarketingReportContent'; const MarketingReportPage = () => { - return ( -
- -
- ); + return ; }; export default MarketingReportPage; diff --git a/src/components/pages/report/marketing/MarketingReportContent.tsx b/src/components/pages/report/marketing/MarketingReportContent.tsx index e38a39d4..9277311f 100644 --- a/src/components/pages/report/marketing/MarketingReportContent.tsx +++ b/src/components/pages/report/marketing/MarketingReportContent.tsx @@ -1,47 +1,42 @@ 'use client'; -import { JSX, useState } from 'react'; - +import { useState } from 'react'; import Tabs from '@/components/Tabs'; import DailyMarketingReportContent from '@/components/pages/report/marketing/tab/DailyMarketingReportContent'; import HppPerKandangTab from '@/components/pages/report/marketing/tab/HppPerKandangTab'; - -type MarketingReportTabType = - | 'daily' - | 'transaction' - | 'hpp-comparison' - | 'daily-hpp'; - -const marketingReportTabs: { - id: MarketingReportTabType; - label: string; - content: JSX.Element; -}[] = [ - { - id: 'daily', - label: 'Penjualan Harian', - content: , - }, - { - id: 'daily-hpp', - label: 'HPP Harian Kandang', - content: , - }, -]; +import { useMarketingTabStore } from '@/stores/marketing-tab/marketing-tab.store'; const MarketingReportContent = () => { - const [activeTab, setActiveTab] = useState('daily'); + const [activeTabId, setActiveTabId] = useState('1'); + const tabActions = useMarketingTabStore((state) => state.tabActions); + + const tabs = [ + { + id: '1', + label: 'Penjualan Harian', + content: , + }, + { + id: '2', + label: 'HPP Harian Kandang', + content: , + }, + ]; return ( -
+
); diff --git a/src/components/pages/report/marketing/filter/HppPerKandangFilter.ts b/src/components/pages/report/marketing/filter/HppPerKandangFilter.ts new file mode 100644 index 00000000..57d2dcd2 --- /dev/null +++ b/src/components/pages/report/marketing/filter/HppPerKandangFilter.ts @@ -0,0 +1,40 @@ +import * as yup from 'yup'; + +export type HppPerKandangFilterType = { + area_id: string | null; + location_id: string | null; + kandang_id: string | null; + weight_min: string | null; + weight_max: string | null; + period: string | null; + sort_by: string | null; + show_unrecorded: boolean | null; +}; + +export const HppPerKandangFilterSchema = yup.object({ + area_id: yup.string().nullable(), + location_id: yup.string().nullable(), + kandang_id: yup.string().nullable(), + weight_min: yup.string().nullable(), + weight_max: yup + .string() + .nullable() + .test( + 'is-greater-than-min', + 'Rentang bobot max tidak boleh lebih kecil dari min', + function (value) { + const { weight_min } = this.parent; + if (!weight_min || !value) return true; + const weightMinNum = parseFloat(weight_min) || 0; + const weightMaxNum = parseFloat(value) || 0; + return weightMaxNum >= weightMinNum; + } + ), + period: yup.string().required('Periode wajib diisi'), + sort_by: yup.string().nullable(), + show_unrecorded: yup.boolean().nullable(), +}); + +export type HppPerKandangFilterValues = yup.InferType< + typeof HppPerKandangFilterSchema +>; diff --git a/src/components/pages/report/marketing/skeleton/HppPerKandangSkeleton.tsx b/src/components/pages/report/marketing/skeleton/HppPerKandangSkeleton.tsx new file mode 100644 index 00000000..42a6cf56 --- /dev/null +++ b/src/components/pages/report/marketing/skeleton/HppPerKandangSkeleton.tsx @@ -0,0 +1,37 @@ +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; +import Table from '@/components/Table'; +import { HppPerKandangReport } from '@/types/api/report/hpp-per-kandang'; +import { ColumnDef } from '@tanstack/react-table'; + +const HppPerKandangSkeleton = ({ + columns, + icon, + title, + subtitle, +}: { + columns: ColumnDef[]; + icon: React.ReactNode; + title: string; + subtitle: string; +}) => { + return ( +
+ +
+ +
+ + ); +}; + +export default HppPerKandangSkeleton; diff --git a/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx b/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx index c0371abf..514edcb9 100644 --- a/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx @@ -1,11 +1,6 @@ import { useState, useMemo, useCallback } from 'react'; -import { ChangeEventHandler } from 'react'; import useSWR from 'swr'; -import Card from '@/components/Card'; -import SelectInput, { - useSelect, - OptionType, -} from '@/components/input/SelectInput'; +import { useSelect } from '@/components/input/SelectInput'; import DateInput from '@/components/input/DateInput'; import NumberInput from '@/components/input/NumberInput'; import { AreaApi } from '@/services/api/master-data'; @@ -21,7 +16,6 @@ import { HppPerKandangPerWeightRange, } from '@/types/api/report/hpp-per-kandang'; import { isResponseSuccess } from '@/lib/api-helper'; -import { useTableFilter } from '@/services/hooks/useTableFilter'; import Button from '@/components/Button'; import Dropdown from '@/components/Dropdown'; import MenuItem from '@/components/menu/MenuItem'; @@ -30,8 +24,35 @@ import { generateHppPerKandangPDF } from '@/components/pages/report/marketing/ex import { generateHppPerKandangExcel } from '@/components/pages/report/marketing/export/HppPerkandangExportXLSX'; import toast from 'react-hot-toast'; import { Icon } from '@iconify/react'; +import { useFormik } from 'formik'; +import { + HppPerKandangFilterSchema, + HppPerKandangFilterType, +} from '@/components/pages/report/marketing/filter/HppPerKandangFilter'; +import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; +import SelectInputRadio from '@/components/input/SelectInputRadio'; +import Modal, { useModal } from '@/components/Modal'; +import { cn } from '@/lib/helper'; +import { useMarketingTabStore } from '@/stores/marketing-tab/marketing-tab.store'; +import HppPerKandangSkeleton from '@/components/pages/report/marketing/skeleton/HppPerKandangSkeleton'; +import { useEffect as useEffectHook } from 'react'; -const HppPerKandangTab = () => { +interface HppPerKandangTabProps { + tabId: string; +} + +interface FilterParams { + area_id?: string; + location_id?: string; + kandang_id?: string; + weight_min?: string; + weight_max?: string; + period?: string; + sort_by?: string; + show_unrecorded?: boolean; +} + +const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => { // ===== STATE MANAGEMENT ===== const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); @@ -42,190 +63,229 @@ const HppPerKandangTab = () => { // ===== VALIDATION STATE ===== const [weightMaxError, setWeightMaxError] = useState(''); + const [dateErrorShown, setDateErrorShown] = useState(false); - // ===== TABLE FILTER STATE ===== - const { state: tableFilterState, updateFilter } = useTableFilter({ - initial: { - area_id: [] as string[], - location_id: [] as string[], - kandang_id: [] as string[], - weight_min: '', - weight_max: '', - period: '', - sort_by: '', - show_unrecorded: false, - }, - paramMap: { - page: 'page', - pageSize: 'limit', - }, - }); + // ===== FILTER STATE ===== + const [filterParams, setFilterParams] = useState({}); - const { - setInputValue: setAreaInputValue, - options: areaOptions, - isLoadingOptions: isLoadingAreas, - loadMore: loadMoreAreas, - } = useSelect(AreaApi.basePath, 'id', 'name', 'search'); + const filterModal = useModal(); - const { - setInputValue: setLocationInputValue, - options: locationOptions, - isLoadingOptions: isLoadingLocations, - loadMore: loadMoreLocations, - } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); - - const { - setInputValue: setKandangInputValue, - options: kandangOptions, - isLoadingOptions: isLoadingKandangs, - loadMore: loadMoreKandangs, - } = useSelect( - ProjectFlockKandangApi.basePath, + // ===== OPTIONS ===== + const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect( + AreaApi.basePath, 'id', - 'name_with_period', + 'name', 'search' ); - const showUnrecordedOptions: OptionType[] = [ - { value: 'false', label: 'Sembunyikan' }, - { value: 'true', label: 'Tampilkan' }, - ]; + const { options: locationOptions, isLoadingOptions: isLoadingLocations } = + useSelect(LocationApi.basePath, 'id', 'name', 'search'); - const areaChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'area_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); - }, - [updateFilter] + const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } = + useSelect( + ProjectFlockKandangApi.basePath, + 'id', + 'name_with_period', + 'search' + ); + + const showUnrecordedOptions = useMemo( + () => [ + { value: 'false', label: 'Sembunyikan' }, + { value: 'true', label: 'Tampilkan' }, + ], + [] ); - const locationChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'location_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); + const handleFilterModalOpen = () => { + filterModal.openModal(); + formik.validateForm(); + }; + + // ===== FORMIK SETUP ===== + const formik = useFormik({ + initialValues: { + area_id: null, + location_id: null, + kandang_id: null, + weight_min: null, + weight_max: null, + period: null, + sort_by: null, + show_unrecorded: null, }, - [updateFilter] - ); - - const kandangChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'kandang_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); + validationSchema: HppPerKandangFilterSchema, + onSubmit: (values, { setSubmitting }) => { + setFilterParams({ + area_id: values.area_id || undefined, + location_id: values.location_id || undefined, + kandang_id: values.kandang_id || undefined, + weight_min: values.weight_min || undefined, + weight_max: values.weight_max || undefined, + period: values.period || undefined, + sort_by: values.sort_by || undefined, + show_unrecorded: + values.show_unrecorded !== null ? values.show_unrecorded : undefined, + }); + filterModal.closeModal(); + setIsSubmitted(true); + setSubmitting(false); }, - [updateFilter] - ); - - const weightMinChangeHandler = useCallback< - ChangeEventHandler - >( - (e) => { - const val = e.target.value; - updateFilter('weight_min', val ? String(parseFloat(val) || 0) : ''); + onReset: () => { + setFilterParams({}); setIsSubmitted(false); + setWeightMaxError(''); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + }, + }); - if (weightMaxError) { + // ===== WEIGHT CHANGE HANDLERS ===== + const handleWeightMinChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + formik.setFieldValue('weight_min', value || null); + + if (value && formik.values.weight_max) { + const weightMin = parseFloat(value) || 0; + const weightMax = parseFloat(formik.values.weight_max) || 0; + + if (weightMax < weightMin) { + setWeightMaxError( + 'Rentang bobot max tidak boleh lebih kecil dari min' + ); + if (!dateErrorShown) { + toast.error('Rentang bobot max tidak boleh lebih kecil dari min', { + duration: Infinity, + }); + setDateErrorShown(true); + } + } else { + setWeightMaxError(''); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + } + } else { setWeightMaxError(''); } }, - [updateFilter, weightMaxError] + [formik, dateErrorShown] ); - const weightMaxChangeHandler = useCallback< - ChangeEventHandler - >( - (e) => { - const val = e.target.value; - const weightMax = val ? parseFloat(val) || 0 : 0; - const weightMin = tableFilterState.weight_min - ? parseFloat(tableFilterState.weight_min) - : 0; + const handleWeightMaxChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + formik.setFieldValue('weight_max', value || null); - if (weightMax < weightMin) { - setWeightMaxError('Rentang bobot max tidak boleh lebih kecil dari min'); - toast.error('Rentang bobot max tidak boleh lebih kecil dari min'); - return; + if (value && formik.values.weight_min) { + const weightMin = parseFloat(formik.values.weight_min) || 0; + const weightMax = parseFloat(value) || 0; + + if (weightMax < weightMin) { + setWeightMaxError( + 'Rentang bobot max tidak boleh lebih kecil dari min' + ); + if (!dateErrorShown) { + toast.error('Rentang bobot max tidak boleh lebih kecil dari min', { + duration: Infinity, + }); + setDateErrorShown(true); + } + return; + } } setWeightMaxError(''); - updateFilter('weight_max', val ? String(weightMax) : ''); - setIsSubmitted(false); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } }, - [updateFilter, tableFilterState.weight_min] + [formik, dateErrorShown] ); - const periodChangeHandler = useCallback>( - (e) => { - const val = e.target.value; - updateFilter('period', val || ''); - setIsSubmitted(false); - }, - [updateFilter] - ); + // ===== DERIVED VALUES ===== + const areaIdsValue = useMemo(() => { + if (!formik.values.area_id) return []; + const ids = formik.values.area_id.split(','); + return areaOptions.filter((opt) => ids.includes(String(opt.value))); + }, [formik.values.area_id, areaOptions]); - const showUnrecordedChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - updateFilter('show_unrecorded', newVal?.value === 'true'); - setIsSubmitted(false); - }, - [updateFilter] - ); + const locationIdsValue = useMemo(() => { + if (!formik.values.location_id) return []; + const ids = formik.values.location_id.split(','); + return locationOptions.filter((opt) => ids.includes(String(opt.value))); + }, [formik.values.location_id, locationOptions]); - const resetFilters = useCallback(() => { - updateFilter('area_id', []); - updateFilter('location_id', []); - updateFilter('kandang_id', []); - updateFilter('weight_min', ''); - updateFilter('weight_max', ''); - updateFilter('period', ''); - updateFilter('sort_by', ''); - updateFilter('show_unrecorded', false); - setIsSubmitted(false); - }, [updateFilter]); + const kandangIdsValue = useMemo(() => { + if (!formik.values.kandang_id) return []; + const ids = formik.values.kandang_id.split(','); + return kandangOptions.filter((opt) => ids.includes(String(opt.value))); + }, [formik.values.kandang_id, kandangOptions]); - const handleSubmit = useCallback(() => { - if (!tableFilterState.period) { - toast.error('Periode wajib diisi'); - return; + const showUnrecordedValue = useMemo(() => { + if (formik.values.show_unrecorded === null) return null; + return ( + showUnrecordedOptions.find( + (opt) => opt.value === String(formik.values.show_unrecorded) + ) || null + ); + }, [formik.values.show_unrecorded, showUnrecordedOptions]); + + // ===== ACTIVE FILTERS COUNT ===== + const activeFiltersCount = useMemo(() => { + let count = 0; + + if (filterParams.period) { + count += 1; } - setIsSubmitted(true); - }, [tableFilterState.period]); + + if (filterParams.area_id) { + count += 1; + } + + if (filterParams.location_id) { + count += 1; + } + + if (filterParams.kandang_id) { + count += 1; + } + + if (filterParams.weight_min || filterParams.weight_max) { + count += 1; + } + + if (filterParams.show_unrecorded !== undefined) { + count += 1; + } + + if (filterParams.sort_by) { + count += 1; + } + + return count; + }, [filterParams]); + + const hasFilters = activeFiltersCount > 0; // ===== DATA FETCHING ===== const { data: hppPerKandang, isLoading } = useSWR( isSubmitted ? () => { const params = { - area_id: - tableFilterState.area_id.length > 0 - ? tableFilterState.area_id.join(',') - : undefined, - location_id: - tableFilterState.location_id.length > 0 - ? tableFilterState.location_id.join(',') - : undefined, - kandang_id: - tableFilterState.kandang_id.length > 0 - ? tableFilterState.kandang_id.join(',') - : undefined, - weight_min: tableFilterState.weight_min || undefined, - weight_max: tableFilterState.weight_max || undefined, - period: tableFilterState.period || undefined, - sort_by: tableFilterState.sort_by || undefined, - show_unrecorded: tableFilterState.show_unrecorded, + area_id: filterParams.area_id, + location_id: filterParams.location_id, + kandang_id: filterParams.kandang_id, + weight_min: filterParams.weight_min, + weight_max: filterParams.weight_max, + period: filterParams.period, + sort_by: filterParams.sort_by, + show_unrecorded: filterParams.show_unrecorded, }; return ['hpp-per-kandang-report', params]; @@ -275,23 +335,14 @@ const HppPerKandangTab = () => { const hppPerKandangExport = useCallback(async (): Promise => { const params = { - area_id: - tableFilterState.area_id.length > 0 - ? tableFilterState.area_id.join(',') - : undefined, - location_id: - tableFilterState.location_id.length > 0 - ? tableFilterState.location_id.join(',') - : undefined, - kandang_id: - tableFilterState.kandang_id.length > 0 - ? tableFilterState.kandang_id.join(',') - : undefined, - weight_min: tableFilterState.weight_min || undefined, - weight_max: tableFilterState.weight_max || undefined, - period: tableFilterState.period || undefined, - sort_by: tableFilterState.sort_by || undefined, - show_unrecorded: tableFilterState.show_unrecorded, + area_id: filterParams.area_id, + location_id: filterParams.location_id, + kandang_id: filterParams.kandang_id, + weight_min: filterParams.weight_min, + weight_max: filterParams.weight_max, + period: filterParams.period, + sort_by: filterParams.sort_by, + show_unrecorded: filterParams.show_unrecorded, limit: 10000, page: 1, }; @@ -308,7 +359,7 @@ const HppPerKandangTab = () => { ); return isResponseSuccess(response) ? response.data : null; - }, [tableFilterState]); + }, [filterParams]); // ===== TABLE COLUMNS DEFINITION ===== const allFeedSuppliers = useMemo(() => { @@ -373,38 +424,32 @@ const HppPerKandangTab = () => { return; } - const areaName = - tableFilterState.area_id.length > 0 - ? tableFilterState.area_id - .map( - (id) => - areaOptions.find((opt) => opt.value === Number(id))?.label - ) - .filter(Boolean) - .join(', ') || 'Semua Area' - : 'Semua Area'; + const areaName = filterParams.area_id + ? areaOptions + .filter((opt) => + filterParams.area_id?.split(',').includes(String(opt.value)) + ) + .map((opt) => opt.label) + .join(', ') || 'Semua Area' + : 'Semua Area'; - const locationName = - tableFilterState.location_id.length > 0 - ? tableFilterState.location_id - .map( - (id) => - locationOptions.find((opt) => opt.value === Number(id))?.label - ) - .filter(Boolean) - .join(', ') || 'Semua Lokasi' - : 'Semua Lokasi'; + const locationName = filterParams.location_id + ? locationOptions + .filter((opt) => + filterParams.location_id?.split(',').includes(String(opt.value)) + ) + .map((opt) => opt.label) + .join(', ') || 'Semua Lokasi' + : 'Semua Lokasi'; - const kandangName = - tableFilterState.kandang_id.length > 0 - ? tableFilterState.kandang_id - .map( - (id) => - kandangOptions.find((opt) => opt.value === Number(id))?.label - ) - .filter(Boolean) - .join(', ') || 'Semua Kandang' - : 'Semua Kandang'; + const kandangName = filterParams.kandang_id + ? kandangOptions + .filter((opt) => + filterParams.kandang_id?.split(',').includes(String(opt.value)) + ) + .map((opt) => opt.label) + .join(', ') || 'Semua Kandang' + : 'Semua Kandang'; await generateHppPerKandangPDF( { @@ -413,11 +458,12 @@ const HppPerKandangTab = () => { area_name: areaName, location_name: locationName, kandang_name: kandangName, - period: tableFilterState.period, - weight_min: tableFilterState.weight_min, - weight_max: tableFilterState.weight_max, - show_unrecorded: tableFilterState.show_unrecorded.toString(), - sort_by: tableFilterState.sort_by, + period: filterParams.period, + weight_min: filterParams.weight_min, + weight_max: filterParams.weight_max, + show_unrecorded: + filterParams.show_unrecorded?.toString() || 'false', + sort_by: filterParams.sort_by, }, }, allFeedSuppliers, @@ -432,7 +478,7 @@ const HppPerKandangTab = () => { } }, [ hppPerKandangExport, - tableFilterState, + filterParams, areaOptions, locationOptions, kandangOptions, @@ -440,6 +486,91 @@ const HppPerKandangTab = () => { allDocSuppliers, ]); + // ===== REGISTER TAB ACTIONS TO STORE ===== + const setTabActions = useMarketingTabStore((state) => state.setTabActions); + const clearTabActions = useMarketingTabStore( + (state) => state.clearTabActions + ); + + useEffectHook(() => { + setTabActions( + tabId, +
+ + + + + Export +
+ +
+ + } + align='end' + className={{ + content: + 'mt-1 p-0 w-full shadow-button-soft border border-base-content/10 rounded-lg', + }} + > + + + + +
+
+ ); + }, [ + tabId, + hasFilters, + activeFiltersCount, + isAnyExportLoading, + filterModal.open, + setTabActions, + ]); + + useEffectHook(() => { + return () => { + clearTabActions(tabId); + }; + }, [tabId, clearTabActions]); + const getTableColumns = (): ColumnDef[] => { const tableColumns: ColumnDef[] = [ { @@ -682,161 +813,50 @@ const HppPerKandangTab = () => { ); return ( -
- HPP Harian Kandang (${period})` - : 'Laporan > HPP Harian Kandang' - } - className={{ wrapper: 'w-full', body: 'p-1!' }} - > -
- - (tableFilterState.area_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={areaChangeHandler} - onInputChange={setAreaInputValue} - onMenuScrollToBottom={loadMoreAreas} - isLoading={isLoadingAreas} - closeMenuOnSelect={false} - hideSelectedOptions={false} - isClearable - /> - - (tableFilterState.location_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={locationChangeHandler} - onInputChange={setLocationInputValue} - onMenuScrollToBottom={loadMoreLocations} - isLoading={isLoadingLocations} - closeMenuOnSelect={false} - hideSelectedOptions={false} - isClearable - /> - - (tableFilterState.kandang_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={kandangChangeHandler} - onInputChange={setKandangInputValue} - onMenuScrollToBottom={loadMoreKandangs} - isLoading={isLoadingKandangs} - closeMenuOnSelect={false} - hideSelectedOptions={false} - isClearable - /> -
- -
-
- - -
- - opt.value === 'true') || - null - : showUnrecordedOptions.find((opt) => opt.value === 'false') || - null - } - onChange={showUnrecordedChangeHandler} - /> -
- -
- - - - Export - - - } - align='end' - > - - - - - -
- -
- + <> +
{!isSubmitted ? ( -
- Silakan pilih filter dan klik tombol Cari untuk menampilkan data. -
+ + } + title='No Filters Selected' + subtitle='Please choose filters to narrow down your results and make your search easier.' + /> ) : isLoading ? ( -
- -
+ + } + title='Memuat Data HPP Per Kandang' + subtitle='Silakan tunggu sebentar...' + /> ) : data.length === 0 ? ( -
- Tidak ada data yang dapat ditampilkan... -
+ + } + title='Data Not Yet Available' + subtitle='Please change your filters to get the data.' + /> ) : (
{ renderFooter={data.length > 0} renderCustomRow={renderCustomRow} className={{ - containerClassName: 'w-full mt-6', - tableWrapperClassName: 'overflow-x-auto mt-4', + containerClassName: 'w-full mb-0!', + tableWrapperClassName: 'overflow-x-auto', tableClassName: 'w-full table-auto text-sm', headerRowClassName: 'border-b border-b-gray-200 bg-gray-50', headerColumnClassName: @@ -862,8 +882,175 @@ const HppPerKandangTab = () => { }} /> )} - - + + + {/* Filter Modal */} + + {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+
+
+ {/* Period Filter */} +
+ { + formik.setFieldValue('period', e.target.value || null); + }} + className={{ wrapper: 'w-full' }} + isNestedModal + required={true} + isError={!!formik.errors.period && formik.touched.period} + /> + {formik.errors.period && formik.touched.period && ( +
+ {formik.errors.period} +
+ )} +
+ + {/* Area Filter */} + { + formik.setFieldValue( + 'area_id', + Array.isArray(val) && val.length > 0 + ? val.map((v) => String(v.value)).join(',') + : null + ); + }} + isLoading={isLoadingAreas} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Location Filter */} + { + formik.setFieldValue( + 'location_id', + Array.isArray(val) && val.length > 0 + ? val.map((v) => String(v.value)).join(',') + : null + ); + }} + isLoading={isLoadingLocations} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Kandang Filter */} + { + formik.setFieldValue( + 'kandang_id', + Array.isArray(val) && val.length > 0 + ? val.map((v) => String(v.value)).join(',') + : null + ); + }} + isLoading={isLoadingKandangs} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Weight Range Filter */} +
+ +
+ +
+ +
+
+ + {/* Show Unrecorded Filter */} + { + if (!Array.isArray(val)) { + formik.setFieldValue( + 'show_unrecorded', + val?.value === 'true' || null + ); + } + }} + className={{ wrapper: 'w-full' }} + isClearable={true} + /> +
+ + {/* Modal Footer */} +
+ + +
+ +
+ ); };