Files
lti-web-client/src/components/pages/report/expense/tab/ReportDepreciationTab.tsx
T
ValdiANS ab6ad7d7b1 feat: migrate depreciation report to V2 API with daily breakdown view
- Add V2 types (ReportDepreciationV2Item, DepreciationV2Meta, DepreciationV2Response) for the new per-day response shape
- Add DepreciationReportV2Api service pointing to /reports/expense/v2/depreciation
- Require projectFlock in filter (was optional); auto-open filter modal on first load when none is selected
- Replace multi-card farm loop with a single project flock card showing farm_name and period only in the header
- Replace kandang sub-table with daily depreciation rows: date, day_n, chickin_date, depreciation_value, pullet_cost_day_n_total, multiplication_percentage, total_value_pullet_after_depreciation
- Add Total Hari (limit) NumberInput field (default 10) to filter modal; remove pagination
- Switch storeName to report-depreciation-v2-table to avoid loading stale localStorage state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 16:14:57 +07:00

290 lines
9.1 KiB
TypeScript

'use client';
import React, { useEffect, useMemo, useRef } from 'react';
import useSWR from 'swr';
import { ColumnDef } from '@tanstack/react-table';
import { Icon } from '@iconify/react';
import Card from '@/components/Card';
import Table from '@/components/Table';
import ButtonFilter from '@/components/helper/ButtonFilter';
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 {
DepreciationV2Response,
ReportDepreciationV2Item,
} from '@/types/api/report/report-expense';
import { DepreciationReportV2Api } from '@/services/api/report/expense-report';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { OptionType } from '@/components/input/SelectInput';
import { httpClientFetcher } from '@/services/http/client';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
interface ReportDepreciationTabProps {
tabId: string;
}
const ReportDepreciationTab = ({ tabId }: ReportDepreciationTabProps) => {
const {
state: tableFilterState,
updateFilter,
toQueryString: getTableFilterQueryString,
reset: resetFilter,
} = useTableFilter<{
area?: OptionType<string>;
location?: OptionType<string>;
projectFlock?: OptionType<string>;
period: string;
totalDays: number;
}>({
initial: {
area: undefined,
location: undefined,
projectFlock: undefined,
period: formatDate(Date.now(), 'YYYY-MM-DD'),
totalDays: 10,
},
paramMap: {
area: 'area_id',
location: 'location_id',
projectFlock: 'project_flock_id',
period: 'period',
totalDays: 'limit',
},
persist: true,
storeName: 'report-depreciation-v2-table',
});
const swrKey = tableFilterState.projectFlock
? `${DepreciationReportV2Api.basePath}${getTableFilterQueryString()}`
: null;
const { data: depreciationsResponse, isLoading: isLoadingDepreciations } =
useSWR<DepreciationV2Response>(swrKey, httpClientFetcher);
const depreciationMeta =
depreciationsResponse?.status === 'success'
? depreciationsResponse.meta
: null;
const depreciationData =
depreciationsResponse?.status === 'success'
? depreciationsResponse.data
: [];
const filterModal = useModal();
const { ref: filterModalRef } = filterModal;
const initialOpenRef = useRef(false);
useEffect(() => {
if (!initialOpenRef.current) {
initialOpenRef.current = true;
if (!tableFilterState.projectFlock) {
filterModal.openModal();
}
}
}, []);
const setTabActions = useTabActionsStore((state) => state.setTabActions);
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
const depreciationColumns: ColumnDef<ReportDepreciationV2Item>[] = useMemo(
() => [
{
accessorKey: 'date',
header: 'Tanggal',
cell: ({ row }) => formatDate(row.original.date, 'DD MMM YYYY'),
},
{
accessorKey: 'day_n',
header: 'Hari ke-',
},
{
accessorKey: 'chickin_date',
header: 'Tanggal Chick-in',
cell: ({ row }) => formatDate(row.original.chickin_date, 'DD MMM YYYY'),
},
{
accessorKey: 'depreciation_value',
header: 'Nilai Depresiasi',
cell: ({ row }) => formatCurrency(row.original.depreciation_value),
},
{
accessorKey: 'pullet_cost_day_n_total',
header: 'Total Harga Pullet Hari ke-N',
cell: ({ row }) =>
formatCurrency(
row.original.pullet_cost_day_n_total,
'IDR',
'id-ID',
0,
10
),
},
{
accessorKey: 'multiplication_percentage',
header: 'Persentase Multiplikasi',
cell: ({ row }) =>
formatNumber(
row.original.multiplication_percentage * 100,
'en-US',
0,
4
) + '%',
},
{
accessorKey: 'total_value_pullet_after_depreciation',
header: 'Total Nilai Pullet Setelah Depresiasi',
cell: ({ row }) =>
formatCurrency(
row.original.total_value_pullet_after_depreciation,
'IDR',
'id-ID',
0,
10
),
},
],
[]
);
const tabActionsElement = useMemo(
() => (
<div className='flex flex-row gap-3'>
<ButtonFilter
values={tableFilterState}
excludeFields={['page', 'pageSize']}
onClick={filterModal.openModal}
variant='outline'
className='px-3 py-2.5'
/>
</div>
),
[tableFilterState]
);
useEffect(() => {
setTabActions(tabId, tabActionsElement);
}, [setTabActions, tabActionsElement, tabId]);
useEffect(() => {
return () => {
clearTabActions(tabId);
};
}, [clearTabActions, tabId]);
return (
<>
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
{isLoadingDepreciations && (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
)}
{!isLoadingDepreciations && !tableFilterState.projectFlock && (
<ReportExpenseSkeleton
columns={depreciationColumns}
icon={
<Icon
icon='heroicons:chart-bar'
className='text-white'
width={20}
height={20}
/>
}
title='Pilih Project Flock'
subtitle='Silakan pilih Project Flock pada filter untuk melihat data depresiasi.'
/>
)}
{!isLoadingDepreciations &&
tableFilterState.projectFlock &&
depreciationData.length === 0 && (
<ReportExpenseSkeleton
columns={depreciationColumns}
icon={
<Icon
icon='heroicons:chart-bar'
className='text-white'
width={20}
height={20}
/>
}
title='Data Not Yet Available'
subtitle='Please change your filters to get the data.'
/>
)}
{!isLoadingDepreciations &&
depreciationData.length > 0 &&
depreciationMeta && (
<Card
title={depreciationMeta.farm_name}
subtitle={`Periode: ${formatDate(depreciationMeta.period, 'DD MMM YYYY')}`}
className={{
wrapper: 'w-full rounded-lg border-none',
body: 'p-0',
title: 'px-2 py-1.5 font-normal text-sm bg-primary text-white',
subtitle:
'px-2 pb-1.5 bg-primary text-white text-xs font-normal',
collapsible: 'rounded-lg',
}}
variant='bordered'
collapsible={true}
>
<Table
data={depreciationData}
columns={depreciationColumns}
pageSize={depreciationData.length}
page={1}
totalItems={depreciationData.length}
isLoading={isLoadingDepreciations}
className={{
containerClassName: 'w-full mb-0!',
tableWrapperClassName:
'overflow-x-auto rounded-tr-none rounded-tl-none',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
paginationClassName: 'hidden',
}}
/>
</Card>
)}
</div>
<ReportDepreciationFilterModal
ref={filterModalRef}
initialValues={tableFilterState}
onReset={resetFilter}
onSubmit={(values) => {
updateFilter('area', values.area, true);
updateFilter('location', values.location, true);
updateFilter('projectFlock', values.projectFlock, true);
updateFilter(
'period',
values.period ? formatDate(values.period, 'YYYY-MM-DD') : '',
true
);
updateFilter('totalDays', values.totalDays ?? 10, true);
}}
/>
</>
);
};
export default ReportDepreciationTab;