From 8c6a87c011c2747af7818eee1f34ce9393e14b2a Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 15 Jan 2026 20:08:21 +0700 Subject: [PATCH] feat(FE): adding closing finance per kandang --- .../pages/closing/ClosingFinanceTable.tsx | 419 +++++------------- src/types/api/closing.d.ts | 79 ++-- 2 files changed, 139 insertions(+), 359 deletions(-) diff --git a/src/components/pages/closing/ClosingFinanceTable.tsx b/src/components/pages/closing/ClosingFinanceTable.tsx index 010bfc2f..0f574c75 100644 --- a/src/components/pages/closing/ClosingFinanceTable.tsx +++ b/src/components/pages/closing/ClosingFinanceTable.tsx @@ -3,54 +3,11 @@ import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; import { isResponseSuccess } from '@/lib/api-helper'; import { formatCurrency, formatTitleCase } from '@/lib/helper'; import { ClosingApi } from '@/services/api/closing'; -import { - DataSummarySubTotal, - HppPurchaseData, - ProfitLossDataAmount, -} from '@/types/api/closing'; +import { HppItem, ProfitLossItem } from '@/types/api/closing'; import { useSearchParams } from 'next/navigation'; +import { useMemo } from 'react'; import useSWR from 'swr'; -type HppTableRow = - | (HppPurchaseData & { - group_name: string; - group_index: number; - isGroupHeader?: boolean; - }) - | { - group_name: string; - group_index: number; - isGroupHeader: true; - type?: never; - budgeting?: never; - realization?: never; - } - | { - type: string; - group_name: string; - group_index: number; - isGroupHeader: false; - budgeting?: { rp_per_bird: number; rp_per_kg: number; amount: number }; - realization?: { rp_per_bird: number; rp_per_kg: number; amount: number }; - }; - -type ProfitLossTableRow = - | (DataSummarySubTotal & { - type: string; - group_name: string; - group_index: number; - isGroupHeader?: boolean; - }) - | { - group_name: string; - group_index: number; - isGroupHeader: true; - type?: never; - rp_per_bird?: never; - rp_per_kg?: never; - amount?: never; - }; - const ClosingFinanceTable = ({ projectFlockId, }: { @@ -68,167 +25,60 @@ const ClosingFinanceTable = ({ ) ); - const staticHppRows: Array<{ - group_name: string; - type: string; - group_index: number; - }> = [ - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian PAKAN', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian STARTER', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian DOC', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian PULLET', - group_index: 0, - }, - { - group_name: 'HPP dan Pengeluaran', - type: 'Pembelian LAYER', - group_index: 0, - }, - { - group_name: 'HPP dan Bahan Baku', - type: 'Pengeluaran Overhead', - group_index: 1, - }, - { - group_name: 'HPP dan Bahan Baku', - type: 'Beban Ekspedisi', - group_index: 1, - }, - ]; + const hppTableData: HppItem[] = useMemo(() => { + if (isResponseSuccess(finance)) { + const customItems = { + label: 'HPP dan Pengeluaran', + code: 'custom_row', + } as HppItem; + const purchases = finance.data.hpp.items.filter( + (item) => item.category === 'purchase' + ); + const totalBudgeting = { + label: 'HPP dan Bahan Baku', + code: 'custom_row', + } as HppItem; + const overheads = finance.data.hpp.items.filter( + (item) => item.category === 'overhead' + ); + return [customItems, ...purchases, totalBudgeting, ...overheads]; + } + return []; + }, []); - const hppTableData: HppTableRow[] = [ - { - group_name: 'HPP dan Pengeluaran', - group_index: 0, - isGroupHeader: true as const, - }, - ...staticHppRows - .filter((row) => row.group_index === 0) - .map((staticRow) => { - const apiData = isResponseSuccess(finance) - ? finance.data.hpp_purchases.hpp - .find((g) => g.group_name === staticRow.group_name) - ?.data.find((d) => d.type === staticRow.type) - : null; - - return { - group_name: staticRow.group_name, - group_index: staticRow.group_index, - type: staticRow.type, - budgeting: apiData?.budgeting || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - realization: apiData?.realization || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - isGroupHeader: false as const, - }; - }), - { - group_name: 'HPP dan Bahan Baku', - group_index: 1, - isGroupHeader: true as const, - }, - ...staticHppRows - .filter((row) => row.group_index === 1) - .map((staticRow) => { - const apiData = isResponseSuccess(finance) - ? finance.data.hpp_purchases.hpp - .find((g) => g.group_name === staticRow.group_name) - ?.data.find((d) => d.type === staticRow.type) - : null; - - return { - group_name: staticRow.group_name, - group_index: staticRow.group_index, - type: staticRow.type, - budgeting: apiData?.budgeting || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - realization: apiData?.realization || { - rp_per_bird: 0, - rp_per_kg: 0, - amount: 0, - }, - isGroupHeader: false as const, - }; - }), - { - group_name: 'HPP', - group_index: 2, - isGroupHeader: true as const, - }, - ]; - - const profitLossTableData: ProfitLossTableRow[] = isResponseSuccess(finance) - ? [ - // Pembelian group - ...finance.data.profit_loss.data.pembelian.map((item) => ({ - label: 'Pembelian', - group_name: 'Pembelian', - group_index: 1, - type: item.type, - rp_per_bird: item.rp_per_bird, - rp_per_kg: item.rp_per_kg, - amount: item.amount, - isGroupHeader: false as const, - })), - { - label: finance.data.profit_loss.data.summary.gross_profit.label, - group_name: 'Penjualan', - group_index: 0, - isGroupHeader: true as const, - type: finance.data.profit_loss.data.summary.gross_profit.label, - rp_per_bird: - finance.data.profit_loss.data.summary.gross_profit.rp_per_bird, - rp_per_kg: - finance.data.profit_loss.data.summary.gross_profit.rp_per_kg, - amount: finance.data.profit_loss.data.summary.gross_profit.amount, - }, - // Penjualan group - ...finance.data.profit_loss.data.penjualan.map((item) => ({ - label: 'Penjualan', - group_name: 'Penjualan', - group_index: 0, - type: item.type, - rp_per_bird: item.rp_per_bird, - rp_per_kg: item.rp_per_kg, - amount: item.amount, - isGroupHeader: false as const, - })), - { - label: finance.data.profit_loss.data.summary.sub_total.label, - group_name: 'Pembelian', - group_index: 1, - isGroupHeader: true as const, - type: finance.data.profit_loss.data.summary.sub_total.label, - rp_per_bird: - finance.data.profit_loss.data.summary.sub_total.rp_per_bird, - rp_per_kg: finance.data.profit_loss.data.summary.sub_total.rp_per_kg, - amount: finance.data.profit_loss.data.summary.sub_total.amount, - }, - ] - : []; + const profitLossTableData: ProfitLossItem[] = useMemo(() => { + if (isResponseSuccess(finance)) { + const incomes = finance.data.profit_loss.items.filter( + (item) => item.type === 'income' + ); + const purchases = finance.data.profit_loss.items.filter( + (item) => item.type === 'purchase' + ); + const overheads = finance.data.profit_loss.items.filter( + (item) => item.type === 'overhead' + ); + const grossProfit = { + label: 'LABA RUGI BRUTO', + code: 'custom_row', + type: 'gross_profit', + rp_per_bird: + finance.data.profit_loss.summary.gross_profit.rp_per_bird ?? 0, + rp_per_kg: finance.data.profit_loss.summary.gross_profit.rp_per_kg ?? 0, + amount: finance.data.profit_loss.summary.gross_profit.amount ?? 0, + } as ProfitLossItem; + const subtotal = { + label: 'Subtotal', + code: 'custom_row', + type: 'subtotal', + rp_per_bird: + finance.data.profit_loss.summary.sub_total.rp_per_bird ?? 0, + rp_per_kg: finance.data.profit_loss.summary.sub_total.rp_per_kg ?? 0, + amount: finance.data.profit_loss.summary.sub_total.amount ?? 0, + } as ProfitLossItem; + return [...incomes, ...purchases, grossProfit, ...overheads, subtotal]; + } + return []; + }, []); return (
@@ -241,47 +91,30 @@ const ClosingFinanceTable = ({ >
-
- {isResponseSuccess(finance) - ? formatTitleCase( - finance.data.profit_loss.data.summary.gross_profit - .label || '-' - ) - : 'Laba Rugi Brutto'} -
+
Laba Rugi Brutto
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.gross_profit.amount + finance.data.profit_loss.summary.gross_profit.amount ) : '-'}
-
- {isResponseSuccess(finance) - ? formatTitleCase( - finance.data.profit_loss.data.summary.net_profit.label || - '-' - ) - : 'Laba Rugi Netto'} -
+
Laba Rugi Netto
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit.amount + finance.data.profit_loss.summary.net_profit.amount ) : '-'}
+ {JSON.stringify(finance)}
- + data={hppTableData} isLoading={isLoading} columns={[ @@ -297,10 +130,10 @@ const ClosingFinanceTable = ({ header: 'No.', enableSorting: false, accessorFn: (item, index) => { - if (item.isGroupHeader) return '-'; + if (item.code === 'custom_row') return '-'; const dataRowsBefore = hppTableData .slice(0, index) - .filter((row) => !row.isGroupHeader).length; + .filter((row) => row.code !== 'custom_row').length; return dataRowsBefore + 1; }, footer: (props) => { @@ -310,7 +143,7 @@ const ClosingFinanceTable = ({ { header: 'Jenis', enableSorting: false, - accessorFn: (item) => formatTitleCase(item.type || '-'), + accessorFn: (item) => formatTitleCase(item.label || '-'), }, { header: 'Budgeting', @@ -326,7 +159,7 @@ const ClosingFinanceTable = ({ return props.column.id === 'budgeting_rp_per_bird' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp?.budgeting + finance.data.hpp.summary?.budgeting ?.rp_per_bird || 0 ) : '-'; @@ -342,8 +175,8 @@ const ClosingFinanceTable = ({ return props.column.id === 'budgeting_rp_per_kg' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp?.budgeting - ?.rp_per_kg || 0 + finance.data.hpp.summary?.budgeting?.rp_per_kg || + 0 ) : '-'; }, @@ -358,8 +191,7 @@ const ClosingFinanceTable = ({ return props.column.id === 'budgeting_amount' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp?.budgeting - ?.amount || 0 + finance.data.hpp.summary?.budgeting?.amount || 0 ) : '-'; }, @@ -380,8 +212,8 @@ const ClosingFinanceTable = ({ return props.column.id === 'realization_rp_per_bird' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp - ?.realization?.rp_per_bird || 0 + finance.data.hpp.summary?.realization + ?.rp_per_bird || 0 ) : '-'; }, @@ -396,8 +228,8 @@ const ClosingFinanceTable = ({ return props.column.id === 'realization_rp_per_kg' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp - ?.realization?.rp_per_kg || 0 + finance.data.hpp.summary?.realization + ?.rp_per_kg || 0 ) : '-'; }, @@ -412,8 +244,7 @@ const ClosingFinanceTable = ({ return props.column.id === 'realization_amount' && isResponseSuccess(finance) ? formatCurrency( - finance.data.hpp_purchases.summary_hpp - ?.realization?.amount || 0 + finance.data.hpp.summary?.realization?.amount || 0 ) : '-'; }, @@ -423,7 +254,7 @@ const ClosingFinanceTable = ({ ]} renderCustomRow={(row) => { const rowData = row.original; - if (rowData.isGroupHeader) { + if (rowData.code === 'custom_row') { return (
- {formatTitleCase(rowData.group_name ?? '-')} + {formatTitleCase(rowData.label ?? '-')}
@@ -450,11 +281,7 @@ const ClosingFinanceTable = ({
- + data={profitLossTableData} isLoading={isLoading} columns={[ { header: 'Jenis', enableSorting: false, - accessorFn: (item) => item.type, + accessorFn: (item) => item.label, cell: (item) => (
- {formatTitleCase(item.row.original.type || '-')} + {formatTitleCase(item.row.original.label || '-')}
), - footer: (item) => ( -
- {isResponseSuccess(finance) - ? formatTitleCase( - finance.data.profit_loss.data.summary.net_profit - .label || '-' - ) - : '-'} -
+ footer: () => ( +
LABA RUGI NETTO
), }, { header: 'Rp/Ekor', enableSorting: false, accessorFn: (item) => formatCurrency(item.rp_per_bird || 0), - footer: (item) => ( + footer: () => (
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit + finance.data.profit_loss.summary.net_profit .rp_per_bird || 0 ) : formatCurrency(0)} @@ -505,11 +325,11 @@ const ClosingFinanceTable = ({ header: 'Rp/Kg', enableSorting: false, accessorFn: (item) => formatCurrency(item.rp_per_kg || 0), - footer: (item) => ( + footer: () => (
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit + finance.data.profit_loss.summary.net_profit .rp_per_kg || 0 ) : formatCurrency(0)} @@ -520,11 +340,11 @@ const ClosingFinanceTable = ({ header: 'Jumlah (Rp)', enableSorting: false, accessorFn: (item) => formatCurrency(item.amount || 0), - footer: (item) => ( + footer: () => (
{isResponseSuccess(finance) ? formatCurrency( - finance.data.profit_loss.data.summary.net_profit + finance.data.profit_loss.summary.net_profit .amount || 0 ) : formatCurrency(0)} @@ -534,55 +354,30 @@ const ClosingFinanceTable = ({ ]} renderCustomRow={(row) => { const rowData = row.original; - if (rowData.isGroupHeader) { - if (rowData.amount) { - return ( - - -
- {formatTitleCase(rowData.label ?? '-')} -
- - -
- {formatCurrency(rowData.rp_per_bird ?? 0)} -
- - -
- {formatCurrency(rowData.rp_per_kg ?? 0)} -
- - -
- {formatCurrency(rowData.amount ?? 0)} -
- - - ); - } + if (rowData.code === 'custom_row') { return ( - + +
+ {formatTitleCase(rowData.label ?? '-')} +
+ +
- {formatTitleCase(rowData.group_name ?? '-')} + {formatCurrency(rowData.rp_per_bird ?? 0)} +
+ + +
+ {formatCurrency(rowData.rp_per_kg ?? 0)} +
+ + +
+ {formatCurrency(rowData.amount ?? 0)}
diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index 56406ada..ff35fd28 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -219,64 +219,30 @@ export type ClosingSales = BaseMetadata & BaseClosingSales; // ====== FINANCE ====== export interface ClosingFinance { - project_flock_id: number; - period: number; - project_type: string; - volume_base: ClosingFinanceVolumeBase; - hpp_purchases: ClosingFinanceHppPurchases; + hpp: ClosingFinanceHpp; profit_loss: ClosingFinanceProfitLoss; } -export interface ClosingFinanceProfitLoss { - title: string; - data: ProfitLossData; +export interface ClosingFinanceHpp { + items: HppItem[]; + summary: HppSummary; } -export interface ClosingFinanceHppPurchases { - title: string; - hpp: GroupHppPurchase[]; - summary_hpp: HppPurchasesSummary; -} - -export interface ClosingFinanceVolumeBase { - total_birds: number; - total_weight_kg: number; -} - -export interface ProfitLossData { - penjualan: ProfitLossDataAmount[]; - pembelian: ProfitLossDataAmount[]; - summary: ProfitLossDataSummary; -} - -export interface GroupHppPurchase { - group_name: string; - data: HppPurchaseData[]; -} - -export interface ProfitLossDataSummary { - gross_profit: DataSummarySubTotal; - sub_total: DataSummarySubTotal; - net_profit: DataSummarySubTotal; -} - -export interface ProfitLossDataAmount { - type: string; - rp_per_bird: number; - rp_per_kg: number; - amount: number; -} - -export interface HppPurchasesSummary { +export interface HppItem { + id: number; + category: string; + code: string; label: string; budgeting: HppPurchaseDataAmount; realization: HppPurchaseDataAmount; } -export interface HppPurchaseData { - type: string; +export interface HppSummary { + label: string; budgeting: HppPurchaseDataAmount; realization: HppPurchaseDataAmount; + egg_budgeting: HppPurchaseDataAmount; + egg_realization: HppPurchaseDataAmount; } export interface HppPurchaseDataAmount { @@ -285,8 +251,27 @@ export interface HppPurchaseDataAmount { amount: number; } -export interface DataSummarySubTotal { +export interface ClosingFinanceProfitLoss { + items: ProfitLossItem[]; + summary: ProfitLossSummary; +} + +export interface ProfitLossItem { + code: string; label: string; + type: string; + rp_per_bird: number; + rp_per_kg: number; + amount: number; +} + +export interface ProfitLossSummary { + gross_profit: ProfitLossAmount; + sub_total: ProfitLossAmount; + net_profit: ProfitLossAmount; +} + +export interface ProfitLossAmount { rp_per_bird: number; rp_per_kg: number; amount: number;