From c0ee2013f3092e2df24da7dbe24592e431aaa71b Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 17 Apr 2026 13:24:26 +0700 Subject: [PATCH 1/9] feat: add Laporan Depresiasi tab --- src/components/pages/report/expense/ReportExpenseTabs.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/pages/report/expense/ReportExpenseTabs.tsx b/src/components/pages/report/expense/ReportExpenseTabs.tsx index 0bde153d..045e7a99 100644 --- a/src/components/pages/report/expense/ReportExpenseTabs.tsx +++ b/src/components/pages/report/expense/ReportExpenseTabs.tsx @@ -4,7 +4,8 @@ import { useState } from 'react'; import Tabs from '@/components/Tabs'; import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; -import ReportExpenseTab from './tab/ReportExpenseTab'; +import ReportExpenseTab from '@/components/pages/report/expense/tab/ReportExpenseTab'; +import ReportDepreciationTab from '@/components/pages/report/expense/tab/ReportDepreciationTab'; const ReportExpenseTabs = () => { const [activeTabId, setActiveTabId] = useState('1'); @@ -16,6 +17,11 @@ const ReportExpenseTabs = () => { label: 'Laporan Biaya Operasional', content: , }, + { + id: '2', + label: 'Laporan Depresiasi', + content: , + }, ]; return ( From 8333b5138ae84b1ce93c053ee86d827ec565422a Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 17 Apr 2026 13:25:18 +0700 Subject: [PATCH 2/9] fix: adjust ReportExpenseSkeleton and ReportSkeletonColumn type --- .../report/expense/skeleton/ReportExpenseSkeleton.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/pages/report/expense/skeleton/ReportExpenseSkeleton.tsx b/src/components/pages/report/expense/skeleton/ReportExpenseSkeleton.tsx index 3e13c539..4f0ab52e 100644 --- a/src/components/pages/report/expense/skeleton/ReportExpenseSkeleton.tsx +++ b/src/components/pages/report/expense/skeleton/ReportExpenseSkeleton.tsx @@ -1,27 +1,26 @@ import React from 'react'; import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; import Table from '@/components/Table'; -import { ReportExpense } from '@/types/api/report/report-expense'; import { ColumnDef } from '@tanstack/react-table'; -type ReportExpenseColumn = - | ColumnDef +type ReportSkeletonColumn = + | ColumnDef | { header: string; columns: Array<{ header: string; accessorKey?: string; - cell?: (props: { row: { original: ReportExpense } }) => React.ReactNode; + cell?: (props: { row: { original: TData } }) => React.ReactNode; }>; }; -const ReportExpenseSkeleton = ({ +const ReportExpenseSkeleton = ({ columns, icon, title, subtitle, }: { - columns: ReportExpenseColumn[]; + columns: ReportSkeletonColumn[]; icon: React.ReactNode; title: string; subtitle: string; From 93083c7d2a60e0e49e350bd72644a14218055fa4 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 17 Apr 2026 13:27:05 +0700 Subject: [PATCH 3/9] fix: create ReportDepreciationTab component --- .../expense/tab/ReportDepreciationTab.tsx | 467 ++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 src/components/pages/report/expense/tab/ReportDepreciationTab.tsx diff --git a/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx b/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx new file mode 100644 index 00000000..403bc63b --- /dev/null +++ b/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx @@ -0,0 +1,467 @@ +'use client'; + +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { Icon } from '@iconify/react'; +import { ColumnDef } from '@tanstack/react-table'; +import Button from '@/components/Button'; +import Dropdown from '@/components/dropdown/Dropdown'; +import Modal, { useModal } from '@/components/Modal'; +import Pagination from '@/components/Pagination'; +import Table from '@/components/Table'; +import ButtonFilter from '@/components/helper/ButtonFilter'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import ReportExpenseSkeleton from '@/components/pages/report/expense/skeleton/ReportExpenseSkeleton'; +import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; +import { formatCurrency } from '@/lib/helper'; +import { + ReportDepreciation, + ReportDepreciationSearchParams, +} from '@/types/api/report/report-expense'; +import toast from 'react-hot-toast'; + +interface ReportDepreciationTabProps { + tabId: string; +} + +const DUMMY_DEPRECIATION_DATA: ReportDepreciation[] = [ + { + id: 'DEP-001', + flock: 'Flock A-01', + totalCostPullet: 185000000, + totalDepresiasi: 38500000, + periode: 'Periode 1', + farm: 'Farm Sukamaju', + jumlahKandang: 4, + }, + { + id: 'DEP-002', + flock: 'Flock A-02', + totalCostPullet: 192500000, + totalDepresiasi: 40125000, + periode: 'Periode 1', + farm: 'Farm Sukamaju', + jumlahKandang: 5, + }, + { + id: 'DEP-003', + flock: 'Flock B-01', + totalCostPullet: 176800000, + totalDepresiasi: 36200000, + periode: 'Periode 2', + farm: 'Farm Cibitung', + jumlahKandang: 3, + }, + { + id: 'DEP-004', + flock: 'Flock B-02', + totalCostPullet: 201400000, + totalDepresiasi: 42250000, + periode: 'Periode 2', + farm: 'Farm Cibitung', + jumlahKandang: 4, + }, + { + id: 'DEP-005', + flock: 'Flock C-01', + totalCostPullet: 210900000, + totalDepresiasi: 43950000, + periode: 'Periode 3', + farm: 'Farm Karawang', + jumlahKandang: 6, + }, + { + id: 'DEP-006', + flock: 'Flock C-02', + totalCostPullet: 205750000, + totalDepresiasi: 43100000, + periode: 'Periode 3', + farm: 'Farm Karawang', + jumlahKandang: 5, + }, + { + id: 'DEP-007', + flock: 'Flock D-01', + totalCostPullet: 188300000, + totalDepresiasi: 39000000, + periode: 'Periode 4', + farm: 'Farm Subang', + jumlahKandang: 4, + }, + { + id: 'DEP-008', + flock: 'Flock D-02', + totalCostPullet: 197600000, + totalDepresiasi: 40875000, + periode: 'Periode 4', + farm: 'Farm Subang', + jumlahKandang: 5, + }, +]; + +const INITIAL_FILTERS: ReportDepreciationSearchParams = { + farm: null, + period: null, +}; + +const ReportDepreciationTab = ({ tabId }: ReportDepreciationTabProps) => { + const [filterParams, setFilterParams] = + useState(INITIAL_FILTERS); + const [draftFilters, setDraftFilters] = + useState(INITIAL_FILTERS); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + + const handleFilterModalOpenRef = useRef(() => {}); + const filterModal = useModal(); + const setTabActions = useTabActionsStore((state) => state.setTabActions); + const clearTabActions = useTabActionsStore((state) => state.clearTabActions); + + const farmOptions = useMemo( + () => + Array.from(new Set(DUMMY_DEPRECIATION_DATA.map((item) => item.farm))).map( + (farm) => ({ + value: farm, + label: farm, + }) + ), + [] + ); + + const periodOptions = useMemo( + () => + Array.from( + new Set(DUMMY_DEPRECIATION_DATA.map((item) => item.periode)) + ).map((period) => ({ + value: period, + label: period, + })), + [] + ); + + const draftFarmValue = useMemo( + () => filterToOption(farmOptions, draftFilters.farm), + [draftFilters.farm, farmOptions] + ); + + const draftPeriodValue = useMemo( + () => filterToOption(periodOptions, draftFilters.period), + [draftFilters.period, periodOptions] + ); + + const filteredData = useMemo(() => { + return DUMMY_DEPRECIATION_DATA.filter((item) => { + const matchFarm = filterParams.farm + ? item.farm === filterParams.farm + : true; + const matchPeriod = filterParams.period + ? item.periode === filterParams.period + : true; + + return matchFarm && matchPeriod; + }); + }, [filterParams]); + + const totalPages = useMemo( + () => Math.max(1, Math.ceil(filteredData.length / pageSize)), + [filteredData.length, pageSize] + ); + + useEffect(() => { + if (page > totalPages) { + setPage(totalPages); + } + }, [page, totalPages]); + + const paginatedData = useMemo(() => { + const startIndex = (page - 1) * pageSize; + return filteredData.slice(startIndex, startIndex + pageSize); + }, [filteredData, page, pageSize]); + + handleFilterModalOpenRef.current = () => { + setDraftFilters(filterParams); + filterModal.openModal(); + }; + + const handleApplyFilters = (e: React.FormEvent) => { + e.preventDefault(); + setFilterParams(draftFilters); + setPage(1); + filterModal.closeModal(); + }; + + const handleResetFilters = () => { + setDraftFilters(INITIAL_FILTERS); + setFilterParams(INITIAL_FILTERS); + setPage(1); + filterModal.closeModal(); + }; + + const handleExport = useCallback((type: 'excel' | 'pdf') => { + toast.success( + `Export ${type.toUpperCase()} belum terhubung API. Saat ini tabel memakai dummy data.` + ); + }, []); + + const tabActionsElement = useMemo( + () => ( +
+ handleFilterModalOpenRef.current()} + variant='outline' + className='px-3 py-2.5' + /> + + +
+ + Export +
+ +
+ + } + > + + + +
+ ), + [filterParams, handleExport] + ); + + useEffect(() => { + setTabActions(tabId, tabActionsElement); + }, [setTabActions, tabActionsElement, tabId]); + + useEffect(() => { + return () => { + clearTabActions(tabId); + }; + }, [clearTabActions, tabId]); + + const columns = useMemo((): ColumnDef[] => { + return [ + { + header: 'Flock', + accessorKey: 'flock', + }, + { + header: 'Total Cost Pullet', + accessorKey: 'totalCostPullet', + cell: ({ row }) => formatCurrency(row.original.totalCostPullet), + }, + { + header: 'Total Depresiasi', + accessorKey: 'totalDepresiasi', + cell: ({ row }) => formatCurrency(row.original.totalDepresiasi), + }, + { + header: 'Periode', + accessorKey: 'periode', + }, + { + header: 'Farm', + accessorKey: 'farm', + }, + { + header: 'Jumlah Kandang', + accessorKey: 'jumlahKandang', + cell: ({ row }) => row.original.jumlahKandang.toLocaleString('id-ID'), + }, + ]; + }, []); + + return ( + <> +
+ {filteredData.length === 0 && ( + + } + title='Data Not Yet Available' + subtitle='Please change your filters to get the data.' + /> + )} + + {filteredData.length > 0 && ( + <> + + +
+ + setPage((currentPage) => + currentPage > 1 ? currentPage - 1 : currentPage + ) + } + onNextPage={() => + setPage((currentPage) => + currentPage < totalPages ? currentPage + 1 : currentPage + ) + } + onPageChange={setPage} + rowOptions={[10, 20, 50, 100]} + onRowChange={(value) => { + setPageSize(value); + setPage(1); + }} + /> +
+ + )} + + + +
+
+ +

Filter Data

+
+ +
+ +
+
+ + setDraftFilters((current) => ({ + ...current, + farm: (val as OptionType | null)?.value?.toString() || null, + })) + } + isClearable + className={{ wrapper: 'w-full' }} + /> + + + setDraftFilters((current) => ({ + ...current, + period: (val as OptionType | null)?.value?.toString() || null, + })) + } + isClearable + className={{ wrapper: 'w-full' }} + /> +
+ +
+ + +
+ +
+ + ); +}; + +const filterToOption = ( + options: OptionType[], + value: string | null +): OptionType | null => { + if (!value) return null; + return options.find((option) => option.value === value) || null; +}; + +export default ReportDepreciationTab; From 16741aaa46ace4ffcdd17d3f820b25dfa780fb27 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 17 Apr 2026 13:27:21 +0700 Subject: [PATCH 4/9] feat: create ReportDepreciation and ReportDepreciationSearchParams type --- src/types/api/report/report-expense.d.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/types/api/report/report-expense.d.ts b/src/types/api/report/report-expense.d.ts index bf9b94eb..f513b249 100644 --- a/src/types/api/report/report-expense.d.ts +++ b/src/types/api/report/report-expense.d.ts @@ -52,3 +52,18 @@ export type ReportExpenseSearchParams = { category: string | null; search: string; }; + +export type ReportDepreciation = { + id: string; + flock: string; + totalCostPullet: number; + totalDepresiasi: number; + periode: string; + farm: string; + jumlahKandang: number; +}; + +export type ReportDepreciationSearchParams = { + farm: string | null; + period: string | null; +}; From 8afc1a63818fcd2bbdd01983017331894e94e612 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 20 Apr 2026 00:28:41 +0700 Subject: [PATCH 5/9] feat: create ReportDepreciationFilterModal component --- .../tab/ReportDepreciationFilterModal.tsx | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx diff --git a/src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx b/src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx new file mode 100644 index 00000000..91e5f536 --- /dev/null +++ b/src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx @@ -0,0 +1,270 @@ +'use client'; + +import { RefObject, useMemo, useState } from 'react'; +import { useFormik } from 'formik'; +import * as yup from 'yup'; + +import { Icon } from '@iconify/react'; +import Modal from '@/components/Modal'; +import Button from '@/components/Button'; +import DateInput from '@/components/input/DateInput'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; + +import { AreaApi, LocationApi } from '@/services/api/master-data'; +import { ProjectFlockApi } from '@/services/api/production'; +import { Area } from '@/types/api/master-data/area'; +import { Location } from '@/types/api/master-data/location'; +import { ProjectFlock } from '@/types/api/production/project-flock'; + +export type ReportDepreciationFilterValues = { + area_id: string | null; + location_id: string | null; + project_flock_id: string | null; + period: string | null; +}; + +export const ReportDepreciationFilterSchema = yup.object({ + area_id: yup.string().nullable(), + location_id: yup.string().nullable(), + project_flock_id: yup.string().nullable(), + period: yup.string().nullable().required('Periode wajib dipilih'), +}) as yup.ObjectSchema; + +interface ReportDepreciationFilterModalProps { + ref: RefObject; + initialValues?: ReportDepreciationFilterValues; + onSubmit?: (values: Partial) => void; + onReset?: () => void; +} + +const defaultInitialValues: ReportDepreciationFilterValues = { + area_id: null, + location_id: null, + project_flock_id: null, + period: null, +}; + +const ReportDepreciationFilterModal = ({ + ref, + initialValues, + onSubmit, + onReset, +}: ReportDepreciationFilterModalProps) => { + const [selectedAreaId, setSelectedAreaId] = useState( + initialValues?.area_id || undefined + ); + const [selectedLocationId, setSelectedLocationId] = useState< + string | undefined + >(initialValues?.location_id || undefined); + + const closeModalHandler = () => { + ref.current?.close(); + }; + + const { + setInputValue: setAreaInputValue, + options: areaOptions, + isLoadingOptions: isLoadingAreaOptions, + loadMore: loadMoreAreas, + } = useSelect(AreaApi.basePath, 'id', 'name', 'search'); + + const { + setInputValue: setLocationInputValue, + options: locationOptions, + isLoadingOptions: isLoadingLocationOptions, + loadMore: loadMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search', { + area_id: selectedAreaId || '', + }); + + const { + setInputValue: setProjectFlockInputValue, + options: projectFlockOptions, + isLoadingOptions: isLoadingProjectFlockOptions, + loadMore: loadMoreProjectFlocks, + } = useSelect( + ProjectFlockApi.basePath, + 'id', + 'flock_name', + 'search', + { + location_id: selectedLocationId || '', + } + ); + + const formik = useFormik({ + initialValues: initialValues || defaultInitialValues, + validationSchema: ReportDepreciationFilterSchema, + onSubmit: async (values) => { + onSubmit?.(values); + closeModalHandler(); + }, + onReset: (_) => { + onReset?.(); + closeModalHandler(); + }, + }); + + const areaValue = useMemo(() => { + if (!formik.values.area_id) return null; + return ( + areaOptions.find((opt) => String(opt.value) === formik.values.area_id) || + null + ); + }, [formik.values.area_id, areaOptions]); + + const locationValue = useMemo(() => { + if (!formik.values.location_id) return null; + return ( + locationOptions.find( + (opt) => String(opt.value) === formik.values.location_id + ) || null + ); + }, [formik.values.location_id, locationOptions]); + + const projectFlockValue = useMemo(() => { + if (!formik.values.project_flock_id) return null; + return ( + projectFlockOptions.find( + (opt) => String(opt.value) === formik.values.project_flock_id + ) || null + ); + }, [formik.values.project_flock_id, projectFlockOptions]); + + const areaChangeHandler = (val: OptionType | OptionType[] | null) => { + const areaId = val && !Array.isArray(val) ? String(val.value) : null; + + setSelectedAreaId(areaId || undefined); + formik.setFieldValue('area_id', areaId); + formik.setFieldValue('location_id', null); + formik.setFieldValue('project_flock_id', null); + setSelectedLocationId(undefined); + }; + + const locationChangeHandler = (val: OptionType | OptionType[] | null) => { + const locationId = val && !Array.isArray(val) ? String(val.value) : null; + + setSelectedLocationId(locationId || undefined); + formik.setFieldValue('location_id', locationId); + formik.setFieldValue('project_flock_id', null); + }; + + const projectFlockChangeHandler = (val: OptionType | OptionType[] | null) => { + const projectFlockId = + val && !Array.isArray(val) ? String(val.value) : null; + + formik.setFieldValue('project_flock_id', projectFlockId); + }; + + return ( + +
+
+
+ +

Filter Data

+
+ + +
+ +
+ + + + + + + +
+ +
+ + + +
+ +
+ ); +}; + +export default ReportDepreciationFilterModal; From 2ca733de97b93968ea4dadd4befd5ed44b152db2 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 20 Apr 2026 00:29:15 +0700 Subject: [PATCH 6/9] fix: adjust ReportDepreciationTab content --- .../expense/tab/ReportDepreciationTab.tsx | 584 ++++++------------ 1 file changed, 187 insertions(+), 397 deletions(-) diff --git a/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx b/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx index 403bc63b..8c6caaa3 100644 --- a/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx +++ b/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx @@ -1,271 +1,121 @@ 'use client'; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { Icon } from '@iconify/react'; +import React, { useEffect, useMemo } from 'react'; +import useSWR from 'swr'; import { ColumnDef } from '@tanstack/react-table'; -import Button from '@/components/Button'; -import Dropdown from '@/components/dropdown/Dropdown'; -import Modal, { useModal } from '@/components/Modal'; + +import { Icon } from '@iconify/react'; +import Card from '@/components/Card'; import Pagination from '@/components/Pagination'; import Table from '@/components/Table'; import ButtonFilter from '@/components/helper/ButtonFilter'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; import ReportExpenseSkeleton from '@/components/pages/report/expense/skeleton/ReportExpenseSkeleton'; +import { useModal } from '@/components/Modal'; +import ReportDepreciationFilterModal from '@/components/pages/report/expense/tab/ReportDepreciationFilterModal'; + import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; -import { formatCurrency } from '@/lib/helper'; -import { - ReportDepreciation, - ReportDepreciationSearchParams, -} from '@/types/api/report/report-expense'; -import toast from 'react-hot-toast'; +import { ReportDepreciation } from '@/types/api/report/report-expense'; +import { DepreciationReportApi } from '@/services/api/report/expense-report'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { formatCurrency, formatDate, formatNumber } from '@/lib/helper'; interface ReportDepreciationTabProps { tabId: string; } -const DUMMY_DEPRECIATION_DATA: ReportDepreciation[] = [ - { - id: 'DEP-001', - flock: 'Flock A-01', - totalCostPullet: 185000000, - totalDepresiasi: 38500000, - periode: 'Periode 1', - farm: 'Farm Sukamaju', - jumlahKandang: 4, - }, - { - id: 'DEP-002', - flock: 'Flock A-02', - totalCostPullet: 192500000, - totalDepresiasi: 40125000, - periode: 'Periode 1', - farm: 'Farm Sukamaju', - jumlahKandang: 5, - }, - { - id: 'DEP-003', - flock: 'Flock B-01', - totalCostPullet: 176800000, - totalDepresiasi: 36200000, - periode: 'Periode 2', - farm: 'Farm Cibitung', - jumlahKandang: 3, - }, - { - id: 'DEP-004', - flock: 'Flock B-02', - totalCostPullet: 201400000, - totalDepresiasi: 42250000, - periode: 'Periode 2', - farm: 'Farm Cibitung', - jumlahKandang: 4, - }, - { - id: 'DEP-005', - flock: 'Flock C-01', - totalCostPullet: 210900000, - totalDepresiasi: 43950000, - periode: 'Periode 3', - farm: 'Farm Karawang', - jumlahKandang: 6, - }, - { - id: 'DEP-006', - flock: 'Flock C-02', - totalCostPullet: 205750000, - totalDepresiasi: 43100000, - periode: 'Periode 3', - farm: 'Farm Karawang', - jumlahKandang: 5, - }, - { - id: 'DEP-007', - flock: 'Flock D-01', - totalCostPullet: 188300000, - totalDepresiasi: 39000000, - periode: 'Periode 4', - farm: 'Farm Subang', - jumlahKandang: 4, - }, - { - id: 'DEP-008', - flock: 'Flock D-02', - totalCostPullet: 197600000, - totalDepresiasi: 40875000, - periode: 'Periode 4', - farm: 'Farm Subang', - jumlahKandang: 5, - }, -]; - -const INITIAL_FILTERS: ReportDepreciationSearchParams = { - farm: null, - period: null, -}; - const ReportDepreciationTab = ({ tabId }: ReportDepreciationTabProps) => { - const [filterParams, setFilterParams] = - useState(INITIAL_FILTERS); - const [draftFilters, setDraftFilters] = - useState(INITIAL_FILTERS); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(10); + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + reset: resetFilter, + } = useTableFilter({ + initial: { + area_id: '', + location_id: '', + project_flock_id: '', + period: formatDate(Date.now(), 'YYYY-MM-DD'), + }, + paramMap: { + pageSize: 'limit', + area_id: 'area_id', + location_id: 'location_id', + project_flock_id: 'project_flock_id', + period: 'period', + }, + }); + + const { data: depreciationsResponse, isLoading: isLoadingDepreciations } = + useSWR( + `${DepreciationReportApi.basePath}${getTableFilterQueryString()}`, + DepreciationReportApi.getAllFetcher + ); + + const depreciations = isResponseSuccess(depreciationsResponse) + ? depreciationsResponse.data + : []; - const handleFilterModalOpenRef = useRef(() => {}); const filterModal = useModal(); + const { ref: filterModalRef } = filterModal; + const setTabActions = useTabActionsStore((state) => state.setTabActions); const clearTabActions = useTabActionsStore((state) => state.clearTabActions); - const farmOptions = useMemo( - () => - Array.from(new Set(DUMMY_DEPRECIATION_DATA.map((item) => item.farm))).map( - (farm) => ({ - value: farm, - label: farm, - }) - ), - [] - ); - - const periodOptions = useMemo( - () => - Array.from( - new Set(DUMMY_DEPRECIATION_DATA.map((item) => item.periode)) - ).map((period) => ({ - value: period, - label: period, - })), - [] - ); - - const draftFarmValue = useMemo( - () => filterToOption(farmOptions, draftFilters.farm), - [draftFilters.farm, farmOptions] - ); - - const draftPeriodValue = useMemo( - () => filterToOption(periodOptions, draftFilters.period), - [draftFilters.period, periodOptions] - ); - - const filteredData = useMemo(() => { - return DUMMY_DEPRECIATION_DATA.filter((item) => { - const matchFarm = filterParams.farm - ? item.farm === filterParams.farm - : true; - const matchPeriod = filterParams.period - ? item.periode === filterParams.period - : true; - - return matchFarm && matchPeriod; - }); - }, [filterParams]); - - const totalPages = useMemo( - () => Math.max(1, Math.ceil(filteredData.length / pageSize)), - [filteredData.length, pageSize] - ); - - useEffect(() => { - if (page > totalPages) { - setPage(totalPages); - } - }, [page, totalPages]); - - const paginatedData = useMemo(() => { - const startIndex = (page - 1) * pageSize; - return filteredData.slice(startIndex, startIndex + pageSize); - }, [filteredData, page, pageSize]); - - handleFilterModalOpenRef.current = () => { - setDraftFilters(filterParams); - filterModal.openModal(); - }; - - const handleApplyFilters = (e: React.FormEvent) => { - e.preventDefault(); - setFilterParams(draftFilters); - setPage(1); - filterModal.closeModal(); - }; - - const handleResetFilters = () => { - setDraftFilters(INITIAL_FILTERS); - setFilterParams(INITIAL_FILTERS); - setPage(1); - filterModal.closeModal(); - }; - - const handleExport = useCallback((type: 'excel' | 'pdf') => { - toast.success( - `Export ${type.toUpperCase()} belum terhubung API. Saat ini tabel memakai dummy data.` - ); - }, []); + const depreciationKandangColumns: ColumnDef< + ReportDepreciation['components']['kandang'][0] + >[] = [ + { + accessorKey: 'kandang_name', + header: 'Kandang', + }, + { + accessorKey: 'house_type', + header: 'Tipe Kandang', + cell: ({ row }) => row.original.house_type.toUpperCase(), + }, + { + accessorKey: 'depreciation_percent', + header: 'Persentase Depresiasi', + cell: ({ row }) => row.original.depreciation_percent + '%', + }, + { + accessorKey: 'depreciation_value', + header: 'Nilai Depresiasi', + cell: ({ row }) => formatCurrency(row.original.depreciation_value), + }, + { + accessorKey: 'depreciation_source', + header: 'Asal Depresiasi', + cell: ({ row }) => row.original.depreciation_source.toUpperCase(), + }, + { + accessorKey: 'cutover_date', + header: 'Tanggal Cutover', + cell: ({ row }) => formatDate(row.original.cutover_date, 'DD MMM YYYY'), + }, + { + accessorKey: 'origin_date', + header: 'Tanggal Origin', + cell: ({ row }) => formatDate(row.original.origin_date, 'DD MMM YYYY'), + }, + ]; const tabActionsElement = useMemo( () => (
handleFilterModalOpenRef.current()} + values={tableFilterState} + excludeFields={['page', 'pageSize']} + onClick={() => filterModal.openModal()} variant='outline' className='px-3 py-2.5' /> - - -
- - Export -
- -
- - } - > - - -
), - [filterParams, handleExport] + [tableFilterState] ); useEffect(() => { @@ -278,44 +128,18 @@ const ReportDepreciationTab = ({ tabId }: ReportDepreciationTabProps) => { }; }, [clearTabActions, tabId]); - const columns = useMemo((): ColumnDef[] => { - return [ - { - header: 'Flock', - accessorKey: 'flock', - }, - { - header: 'Total Cost Pullet', - accessorKey: 'totalCostPullet', - cell: ({ row }) => formatCurrency(row.original.totalCostPullet), - }, - { - header: 'Total Depresiasi', - accessorKey: 'totalDepresiasi', - cell: ({ row }) => formatCurrency(row.original.totalDepresiasi), - }, - { - header: 'Periode', - accessorKey: 'periode', - }, - { - header: 'Farm', - accessorKey: 'farm', - }, - { - header: 'Jumlah Kandang', - accessorKey: 'jumlahKandang', - cell: ({ row }) => row.original.jumlahKandang.toLocaleString('id-ID'), - }, - ]; - }, []); - return ( <>
- {filteredData.length === 0 && ( + {isLoadingDepreciations && ( +
+ +
+ )} + + {!isLoadingDepreciations && depreciations.length === 0 && ( { /> )} - {filteredData.length > 0 && ( + {!isLoadingDepreciations && depreciations.length > 0 && ( <> -
- -
- - setPage((currentPage) => - currentPage > 1 ? currentPage - 1 : currentPage - ) - } - onNextPage={() => - setPage((currentPage) => - currentPage < totalPages ? currentPage + 1 : currentPage - ) - } - onPageChange={setPage} - rowOptions={[10, 20, 50, 100]} - onRowChange={(value) => { - setPageSize(value); - setPage(1); + {depreciations.map((depreciationItem, idx) => ( + -
+ variant='bordered' + collapsible={true} + > +
+ + ))} + + setPage(tableFilterState.page - 1)} + onNextPage={() => setPage(tableFilterState.page + 1)} + onPageChange={setPage} + rowOptions={[10, 20, 50, 100]} + onRowChange={setPageSize} + /> )} - { + updateFilter('area_id', values.area_id ?? ''); + updateFilter('location_id', values.location_id ?? ''); + updateFilter('project_flock_id', values.project_flock_id ?? ''); + updateFilter( + 'period', + values.period ? formatDate(values.period, 'YYYY-MM-DD') : '' + ); + + console.log({ values }); }} - > -
-
- -

Filter Data

-
- -
- -
-
- - setDraftFilters((current) => ({ - ...current, - farm: (val as OptionType | null)?.value?.toString() || null, - })) - } - isClearable - className={{ wrapper: 'w-full' }} - /> - - - setDraftFilters((current) => ({ - ...current, - period: (val as OptionType | null)?.value?.toString() || null, - })) - } - isClearable - className={{ wrapper: 'w-full' }} - /> -
- -
- - -
- -
+ /> ); }; -const filterToOption = ( - options: OptionType[], - value: string | null -): OptionType | null => { - if (!value) return null; - return options.find((option) => option.value === value) || null; -}; - export default ReportDepreciationTab; From 5a668c469f84872f7c49d0430b08e2e9686994d7 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 20 Apr 2026 00:29:34 +0700 Subject: [PATCH 7/9] chore: update ReportExpenseApi import path --- src/components/pages/report/expense/tab/ReportExpenseTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/report/expense/tab/ReportExpenseTab.tsx b/src/components/pages/report/expense/tab/ReportExpenseTab.tsx index e439c09a..811d9c2c 100644 --- a/src/components/pages/report/expense/tab/ReportExpenseTab.tsx +++ b/src/components/pages/report/expense/tab/ReportExpenseTab.tsx @@ -23,7 +23,7 @@ import RealizationStatusBadge from '@/components/pages/expense/RealizationStatus import Table from '@/components/Table'; import { formatCurrency, formatDate } from '@/lib/helper'; import { ReportExpense } from '@/types/api/report/report-expense'; -import { ReportExpenseApi } from '@/services/api/report'; +import { ReportExpenseApi } from '@/services/api/report/expense-report'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; import Modal, { useModal } from '@/components/Modal'; From aa4da686c63473c32f999321028626e5c5c2277c Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 20 Apr 2026 00:29:55 +0700 Subject: [PATCH 8/9] fix: move and rename report to expense-report.ts --- .../api/{report.ts => report/expense-report.ts} | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) rename src/services/api/{report.ts => report/expense-report.ts} (72%) diff --git a/src/services/api/report.ts b/src/services/api/report/expense-report.ts similarity index 72% rename from src/services/api/report.ts rename to src/services/api/report/expense-report.ts index 5a5e06c8..e6faf8ac 100644 --- a/src/services/api/report.ts +++ b/src/services/api/report/expense-report.ts @@ -1,7 +1,10 @@ import { BaseApiService } from '@/services/api/base'; import { httpClientFetcher } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; -import { ReportExpense } from '@/types/api/report/report-expense'; +import { + ReportDepreciation, + ReportExpense, +} from '@/types/api/report/report-expense'; export class ReportExpenseApiService extends BaseApiService< ReportExpense, @@ -20,3 +23,9 @@ export class ReportExpenseApiService extends BaseApiService< } export const ReportExpenseApi = new ReportExpenseApiService('/reports/expense'); + +export const DepreciationReportApi = new BaseApiService< + ReportDepreciation, + unknown, + unknown +>('/reports/expense/depreciation'); From f49822d03de7f5c31406cba1269b0b4c1341c7b6 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 20 Apr 2026 00:30:07 +0700 Subject: [PATCH 9/9] fix: adjust ReportDepreciation type --- src/types/api/report/report-expense.d.ts | 37 +++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/types/api/report/report-expense.d.ts b/src/types/api/report/report-expense.d.ts index f513b249..cf0d3ad2 100644 --- a/src/types/api/report/report-expense.d.ts +++ b/src/types/api/report/report-expense.d.ts @@ -54,13 +54,36 @@ export type ReportExpenseSearchParams = { }; export type ReportDepreciation = { - id: string; - flock: string; - totalCostPullet: number; - totalDepresiasi: number; - periode: string; - farm: string; - jumlahKandang: number; + project_flock_id: number; + farm_name: string; + period: string; + depreciation_percent_effective: number; + depreciation_value: number; + pullet_cost_day_n_total: number; + hpp?: number; + components: { + kandang: { + kandang_id: number; + hpp?: number; + transfer_id: number; + cutover_date: string; + kandang_name: string; + manual_input_id: number; + depreciation_value: number; + start_schedule_day: number; + depreciation_percent: number; + source_project_flock_id: number; + day_n: number; + transfer_date: string; + pullet_cost_day_n: number; + depreciation_source: string; + project_flock_kandang_id: number; + transfer_qty: number; + house_type: string; + origin_date: string; + }[]; + kandang_count: number; + }; }; export type ReportDepreciationSearchParams = {