diff --git a/src/app/finance/detail/page.tsx b/src/app/finance/detail/page.tsx index 1d20e9f5..b80e8acb 100644 --- a/src/app/finance/detail/page.tsx +++ b/src/app/finance/detail/page.tsx @@ -24,8 +24,6 @@ const FinanceDetailPage = () => { ); } - console.log(finance); - // if (!finance || isResponseError(finance)) { // router.replace('/404'); // return; diff --git a/src/components/pages/closing/ClosingFinanceTable.tsx b/src/components/pages/closing/ClosingFinanceTable.tsx index 010bfc2f..6225f5e7 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 []; + }, [finance]); - 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 []; + }, [finance]); return (
@@ -241,35 +91,21 @@ 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 ) : '-'}
@@ -277,11 +113,7 @@ const ClosingFinanceTable = ({
- + data={hppTableData} isLoading={isLoading} columns={[ @@ -297,10 +129,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 +142,7 @@ const ClosingFinanceTable = ({ { header: 'Jenis', enableSorting: false, - accessorFn: (item) => formatTitleCase(item.type || '-'), + accessorFn: (item) => formatTitleCase(item.label || '-'), }, { header: 'Budgeting', @@ -326,7 +158,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 +174,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 +190,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 +211,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 +227,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 +243,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 +253,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 +280,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 +324,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 +339,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 +353,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/components/pages/closing/ClosingOverheadTable.tsx b/src/components/pages/closing/ClosingOverheadTable.tsx index ed74ca66..9ef6694f 100644 --- a/src/components/pages/closing/ClosingOverheadTable.tsx +++ b/src/components/pages/closing/ClosingOverheadTable.tsx @@ -32,101 +32,160 @@ const ClosingOverheadTable = ({ ); // Helper function to create columns with footer support - const createColumns = (total?: OverheadTotal): ColumnDef[] => [ - // Group untuk kolom tanpa footer - { - header: 'Nama Item', - accessorFn: (props) => props.item_name, - footer: 'Total Pengeluaran Overhead', - }, - { - header: 'Satuan', - accessorFn: (props) => props.uom_name, - }, - { - header: 'Budget Pengajuan', - footer: '', - columns: [ - { - id: 'budget_quantity', - header: 'Jumlah', - accessorFn: (props) => - props.budget_quantity ? formatNumber(props.budget_quantity) : '-', - footer: total ? () => formatNumber(total.budget_quantity) : '', - }, - { - id: 'budget_unit_price', - header: 'Harga Satuan', - accessorFn: (props) => - props.budget_unit_price - ? formatCurrency(props.budget_unit_price) - : '-', - footer: '', - }, - { - id: 'budget_total_amount', - header: 'Total', - accessorFn: (props) => - props.budget_total_amount - ? formatCurrency(props.budget_total_amount) - : '-', - footer: total ? () => formatCurrency(total.budget_total_amount) : '', - }, - ], - }, - { - header: 'Realisasi', - footer: '', - columns: [ - { - id: 'actual_date', - header: 'Tanggal', - accessorFn: (props) => - props.actual_date - ? formatDate(props.actual_date, 'DD MMM, YYYY') - : '-', - footer: '', - }, - { - id: 'actual_quantity', - header: 'Jumlah', - accessorFn: (props) => - props.actual_quantity ? formatNumber(props.actual_quantity) : '-', - footer: total ? () => formatNumber(total.actual_quantity) : '', - }, - { - id: 'actual_unit_price', - header: 'Harga Satuan', - accessorFn: (props) => - props.actual_unit_price - ? formatCurrency(props.actual_unit_price) - : '-', - footer: '', - }, - { - id: 'actual_total_amount', - header: 'Total', - accessorFn: (props) => - props.actual_total_amount - ? formatCurrency(props.actual_total_amount) - : '-', - footer: total ? () => formatCurrency(total.actual_total_amount) : '', - }, - ], - }, - { - id: 'cost_per_bird', - header: 'Rp/Ekor', - accessorFn: (props) => - props.cost_per_bird ? formatCurrency(props.cost_per_bird) : '-', - footer: total ? () => formatCurrency(total.cost_per_bird) : '', - }, - ]; + const createColumns = ( + total?: OverheadTotal, + kandangId?: number + ): ColumnDef[] => { + const flockColumn: ColumnDef[] = [ + { + header: 'Budget Pengajuan', + footer: '', + columns: [ + { + id: 'budget_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.budget_quantity ? formatNumber(props.budget_quantity) : '-', + footer: total ? () => formatNumber(total.budget_quantity) : '', + }, + { + id: 'budget_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.budget_unit_price + ? formatCurrency(props.budget_unit_price) + : '-', + footer: '', + }, + { + id: 'budget_total_amount', + header: 'Total', + accessorFn: (props) => + props.budget_total_amount + ? formatCurrency(props.budget_total_amount) + : '-', + footer: total + ? () => formatCurrency(total.budget_total_amount) + : '', + }, + ], + }, + { + header: 'Realisasi', + footer: '', + columns: [ + { + id: 'actual_date', + header: 'Tanggal', + accessorFn: (props) => + props.actual_date + ? formatDate(props.actual_date, 'DD MMM, YYYY') + : '-', + footer: '', + }, + { + id: 'actual_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.actual_quantity ? formatNumber(props.actual_quantity) : '-', + footer: total ? () => formatNumber(total.actual_quantity) : '', + }, + { + id: 'actual_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.actual_unit_price + ? formatCurrency(props.actual_unit_price) + : '-', + footer: '', + }, + { + id: 'actual_total_amount', + header: 'Total', + accessorFn: (props) => + props.actual_total_amount + ? formatCurrency(props.actual_total_amount) + : '-', + footer: total + ? () => formatCurrency(total.actual_total_amount) + : '', + }, + ], + }, + ]; + + const kandangColumn: ColumnDef[] = [ + { + id: 'actual_date', + header: 'Tanggal', + accessorFn: (props) => + props.actual_date + ? formatDate(props.actual_date, 'DD MMM, YYYY') + : '-', + footer: '', + }, + { + id: 'actual_quantity', + header: 'Jumlah', + accessorFn: (props) => + props.actual_quantity ? formatNumber(props.actual_quantity) : '-', + footer: total ? () => formatNumber(total.actual_quantity) : '', + }, + { + id: 'actual_unit_price', + header: 'Harga Satuan', + accessorFn: (props) => + props.actual_unit_price + ? formatCurrency(props.actual_unit_price) + : '-', + footer: '', + }, + { + id: 'actual_total_amount', + header: 'Total', + accessorFn: (props) => + props.actual_total_amount + ? formatCurrency(props.actual_total_amount) + : '-', + footer: total ? () => formatCurrency(total.actual_total_amount) : '', + }, + ]; + + const finalColumns: ColumnDef[] = [ + // Group untuk kolom tanpa footer + { + header: 'No', + accessorFn: (_, index) => index, + cell: (props) => props.row.index + 1, + }, + { + header: 'Nama Item', + accessorFn: (props) => props.item_name, + footer: 'Total Pengeluaran Overhead', + }, + { + header: 'Satuan', + accessorFn: (props) => props.uom_name, + }, + ...(kandangId ? kandangColumn : flockColumn), + { + id: 'cost_per_bird', + header: 'Rp/Ekor', + accessorFn: (props) => + props.cost_per_bird ? formatCurrency(props.cost_per_bird) : '-', + footer: total ? () => formatCurrency(total.cost_per_bird) : '', + }, + ]; + return finalColumns; + }; const columns = useMemo( () => isResponseSuccess(overhead) - ? createColumns(overhead.data?.total) + ? createColumns( + overhead.data?.total, + kandangId ? Number(kandangId) : undefined + ) : createColumns(), [overhead] ); diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index cf5eeaa2..0175a8be 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -118,8 +118,6 @@ const DashboardProduction = () => { } as DashboardFilterType, validationSchema: getDashboardFilterSchema(analysisMode), onSubmit: (values) => { - console.log(values); - handleApplyFilter({ start_date: values.startDate || '', end_date: values.endDate || '', @@ -139,8 +137,6 @@ const DashboardProduction = () => { }; const handleApplyFilter = (values: DashboardFilter) => { - console.log(values); - // Build query params object, only include non-empty values const params: Record = {}; @@ -156,7 +152,6 @@ const DashboardProduction = () => { if (values.comparison_type) params.comparison_type = values.comparison_type; setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`); - console.log(endpointUrl); filterModal.closeModal(); refreshDashboardProductionData(); }; diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index 2d43d273..227c3731 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -1,21 +1,17 @@ import { ChangeEventHandler, useMemo, useState } from 'react'; -import { CellContext, Row } from '@tanstack/react-table'; +import { CellContext } from '@tanstack/react-table'; import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; import Button from '@/components/Button'; import Card from '@/components/Card'; -import Dropdown from '@/components/dropdown/Dropdown'; import DateInput from '@/components/input/DateInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType, useSelect, } from '@/components/input/SelectInput'; -import Menu from '@/components/menu/Menu'; -import MenuItem from '@/components/menu/MenuItem'; import Table from '@/components/Table'; -import Tooltip from '@/components/Tooltip'; import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { Finance } from '@/types/api/finance/finance'; @@ -23,11 +19,10 @@ import { FINANCE_INITIAL_BALANCE_STATUS, FINANCE_INJECTION_STATUS, FINANCE_TRANSACTION_STATUS, - ROWS_OPTIONS, } from '@/config/constant'; import { FinanceApi } from '@/services/api/finance'; import { isResponseSuccess } from '@/lib/api-helper'; -import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data'; +import { BankApi } from '@/services/api/master-data'; import { Bank } from '@/types/api/master-data/bank'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; @@ -219,15 +214,12 @@ const FinanceTable = () => { { label: 'Tanggal Dibuat', value: 'created_at' }, ]; }, []); - const { options: bankOptions, rawData: bankRawData } = useSelect( - BankApi.basePath, - 'id', - 'alias', - '', - { - limit: 'limit', - } - ); + const { + options: bankOptions, + rawData: bankRawData, + setInputValue: bankInputValue, + loadMore: bankLoadMore, + } = useSelect(BankApi.basePath, 'id', 'alias'); // ===== Handler ===== const searchChangeHandler: ChangeEventHandler = (e) => { @@ -501,6 +493,8 @@ const FinanceTable = () => { label='Bank' value={selectedBank} onChange={bankChangeHandler} + onInputChange={bankInputValue} + onMenuScrollToBottom={bankLoadMore} isClearable /> ( formik.values.party_type_option?.value === 'CUSTOMER' ? CustomerApi.basePath : SupplierApi.basePath, 'id', - 'name', - '', - { limit: 'limit' } + 'name' ); const { options: bankOptions, rawData: bankRawData, isLoadingOptions: isLoadingBankOptions, - } = useSelect(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); + setInputValue: setBankInputValue, + loadMore: loadMoreBankOptions, + } = useSelect(BankApi.basePath, 'id', 'name'); // ===== Helper Functions ===== const transformFormValuesToPayload = ( @@ -219,6 +221,8 @@ const FormFinanceAdd = ({ placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis transaksi dahulu'}`} options={partyOptions} value={formik.values.party_id_option} + onInputChange={setPartyInputValue} + onMenuScrollToBottom={loadMorePartyOptions} onChange={(value) => { formik.setFieldValue('party_id_option', value); if (isResponseSuccess(partyRawData) && value) { @@ -304,6 +308,8 @@ const FormFinanceAdd = ({ : [] } value={formik.values.bank_id_option} + onInputChange={setBankInputValue} + onMenuScrollToBottom={loadMoreBankOptions} onChange={(value) => { formik.setFieldValue('bank_id_option', value); }} diff --git a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx index 85b63ac3..7bcdbccf 100644 --- a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx +++ b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.tsx @@ -104,21 +104,25 @@ const FormFinanceAddInitialBalance = ({ }); // ===== Options ===== - const { options: partyOptions, isLoadingOptions: isLoadingPartyOptions } = - useSelect( - formik.values.party_type_option?.value === 'CUSTOMER' - ? CustomerApi.basePath - : SupplierApi.basePath, - 'id', - 'name', - '', - { limit: 'limit' } - ); + const { + options: partyOptions, + isLoadingOptions: isLoadingPartyOptions, + setInputValue: setPartyInputValue, + loadMore: loadMorePartyOptions, + } = useSelect( + formik.values.party_type_option?.value === 'CUSTOMER' + ? CustomerApi.basePath + : SupplierApi.basePath, + 'id', + 'name' + ); const { options: bankOptions, rawData: bankRawData, isLoadingOptions: isLoadingBankOptions, - } = useSelect(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); + setInputValue: setBankInputValue, + loadMore: loadMoreBankOptions, + } = useSelect(BankApi.basePath, 'id', 'name'); // ===== Helper Functions ===== const transformFormValuesToPayload = ( @@ -189,6 +193,8 @@ const FormFinanceAddInitialBalance = ({ placeholder='Pilih jenis pihak' options={FINANCE_PARTY_TYPE_OPTIONS} value={formik.values.party_type_option} + onInputChange={setPartyInputValue} + onMenuScrollToBottom={loadMorePartyOptions} onChange={(value) => { formik.setFieldValue('party_type_option', value); formik.setFieldValue('party_id_option', null); @@ -218,6 +224,8 @@ const FormFinanceAddInitialBalance = ({ placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis pihak dahulu'}`} options={partyOptions} value={formik.values.party_id_option} + onInputChange={setPartyInputValue} + onMenuScrollToBottom={loadMorePartyOptions} onChange={(value) => { formik.setFieldValue('party_id_option', value); }} diff --git a/src/components/pages/finance/add/injection/FormFinanceInjection.tsx b/src/components/pages/finance/add/injection/FormFinanceInjection.tsx index 4ddd282c..22663f75 100644 --- a/src/components/pages/finance/add/injection/FormFinanceInjection.tsx +++ b/src/components/pages/finance/add/injection/FormFinanceInjection.tsx @@ -80,7 +80,9 @@ const FormFinanceInjection = ({ options: bankOptions, rawData: bankRawData, isLoadingOptions: isLoadingBankOptions, - } = useSelect(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); + setInputValue: setBankInputValue, + loadMore: loadMoreBankOptions, + } = useSelect(BankApi.basePath, 'id', 'name'); // ===== Helper Functions ===== const transformFormValuesToPayload = ( @@ -162,6 +164,8 @@ const FormFinanceInjection = ({ : [] } value={formik.values.bank_id_option} + onInputChange={setBankInputValue} + onMenuScrollToBottom={loadMoreBankOptions} onChange={(value) => { formik.setFieldValue('bank_id_option', value); }} diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index 1c37dbbb..467c2e00 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -184,12 +184,16 @@ const MarketingTable = () => { const { options: productsOptions, isLoadingOptions: isLoadingProductsOptions, + setInputValue: setProductsInputValue, + loadMore: loadMoreProducts, } = useSelect(ProductApi.basePath, 'id', 'name', '', { limit: 'limit', }); const { options: customersOptions, isLoadingOptions: isLoadingCustomersOptions, + setInputValue: setCustomersInputValue, + loadMore: loadMoreCustomers, } = useSelect(CustomerApi.basePath, 'id', 'name', '', { limit: 'limit', }); @@ -400,6 +404,8 @@ const MarketingTable = () => { .join(',') || '' ) } + onInputChange={setProductsInputValue} + onMenuScrollToBottom={loadMoreProducts} isMulti /> {/* select status */} @@ -444,6 +450,8 @@ const MarketingTable = () => { (value as OptionType)?.value.toString() || '' ) } + onInputChange={setCustomersInputValue} + onMenuScrollToBottom={loadMoreCustomers} />
diff --git a/src/components/pages/master-data/area/AreasTable.tsx b/src/components/pages/master-data/area/AreasTable.tsx index 45c4fdff..d92c7840 100644 --- a/src/components/pages/master-data/area/AreasTable.tsx +++ b/src/components/pages/master-data/area/AreasTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Area } from '@/types/api/master-data/area'; import { AreaApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -164,7 +164,14 @@ const AreasTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await AreaApi.delete(selectedArea?.id as number); + const deleteResponse = await AreaApi.delete(selectedArea?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshAreas(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/bank/BanksTable.tsx b/src/components/pages/master-data/bank/BanksTable.tsx index f28f4bd0..c5a564fe 100644 --- a/src/components/pages/master-data/bank/BanksTable.tsx +++ b/src/components/pages/master-data/bank/BanksTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Bank } from '@/types/api/master-data/bank'; import { BankApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -177,7 +177,14 @@ const BanksTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await BankApi.delete(selectedBank?.id as number); + const deleteResponse = await BankApi.delete(selectedBank?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshBanks(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/customer/CustomersTable.tsx b/src/components/pages/master-data/customer/CustomersTable.tsx index 3e442620..e605d9f7 100644 --- a/src/components/pages/master-data/customer/CustomersTable.tsx +++ b/src/components/pages/master-data/customer/CustomersTable.tsx @@ -11,7 +11,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RequirePermission from '@/components/helper/RequirePermission'; import { ROWS_OPTIONS } from '@/config/constant'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn } from '@/lib/helper'; import { CustomerApi } from '@/services/api/master-data'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -186,7 +186,16 @@ const CustomersTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await CustomerApi.delete(selectedCustomer?.id as number); + const deleteResponse = await CustomerApi.delete( + selectedCustomer?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshCustomers(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/fcr/FcrsTable.tsx b/src/components/pages/master-data/fcr/FcrsTable.tsx index 2d65a406..2eb8d8da 100644 --- a/src/components/pages/master-data/fcr/FcrsTable.tsx +++ b/src/components/pages/master-data/fcr/FcrsTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Fcr } from '@/types/api/master-data/fcr'; import { FcrApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -164,7 +164,14 @@ const FcrsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await FcrApi.delete(selectedFcr?.id as number); + const deleteResponse = await FcrApi.delete(selectedFcr?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshFcrs(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx index ce8f701a..baeaa69e 100644 --- a/src/components/pages/master-data/flock/FlocksTable.tsx +++ b/src/components/pages/master-data/flock/FlocksTable.tsx @@ -19,7 +19,7 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput'; import { ROWS_OPTIONS } from '@/config/constant'; import Table from '@/components/Table'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; const RowsOptions = ({ @@ -182,7 +182,14 @@ const FlockTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await FlockApi.delete(selectedFlock?.id as number); + const deleteResponse = await FlockApi.delete(selectedFlock?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshFlocks(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/kandang/KandangsTable.tsx b/src/components/pages/master-data/kandang/KandangsTable.tsx index 1bd7badb..7d79d456 100644 --- a/src/components/pages/master-data/kandang/KandangsTable.tsx +++ b/src/components/pages/master-data/kandang/KandangsTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Kandang } from '@/types/api/master-data/kandang'; import { KandangApi } from '@/services/api/master-data'; import { cn, formatNumber } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -199,7 +199,16 @@ const KandangsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await KandangApi.delete(selectedKandang?.id as number); + const deleteResponse = await KandangApi.delete( + selectedKandang?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshKandangs(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/location/LocationsTable.tsx b/src/components/pages/master-data/location/LocationsTable.tsx index 10fe46c9..a35ffd09 100644 --- a/src/components/pages/master-data/location/LocationsTable.tsx +++ b/src/components/pages/master-data/location/LocationsTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Location } from '@/types/api/master-data/location'; import { LocationApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -186,7 +186,16 @@ const LocationsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await LocationApi.delete(selectedLocation?.id as number); + const deleteResponse = await LocationApi.delete( + selectedLocation?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshLocations(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/nonstock/NonstocksTable.tsx b/src/components/pages/master-data/nonstock/NonstocksTable.tsx index 7066c19a..6aeb3f99 100644 --- a/src/components/pages/master-data/nonstock/NonstocksTable.tsx +++ b/src/components/pages/master-data/nonstock/NonstocksTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Nonstock } from '@/types/api/master-data/nonstock'; import { NonstockApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -198,7 +198,16 @@ const NonstocksTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await NonstockApi.delete(selectedNonstock?.id as number); + const deleteResponse = await NonstockApi.delete( + selectedNonstock?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshNonstocks(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/product-category/ProductCategoryTable.tsx b/src/components/pages/master-data/product-category/ProductCategoryTable.tsx index a9b98bcb..e25dfd56 100644 --- a/src/components/pages/master-data/product-category/ProductCategoryTable.tsx +++ b/src/components/pages/master-data/product-category/ProductCategoryTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { ProductCategory } from '@/types/api/master-data/product-category'; import { ProductCategoryApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -170,7 +170,16 @@ const ProductCategoryTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await ProductCategoryApi.delete(selectedProductCategory?.id as number); + const deleteResponse = await ProductCategoryApi.delete( + selectedProductCategory?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshProductCategories(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/product/ProductTable.tsx b/src/components/pages/master-data/product/ProductTable.tsx index 957d0551..74137a14 100644 --- a/src/components/pages/master-data/product/ProductTable.tsx +++ b/src/components/pages/master-data/product/ProductTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Product } from '@/types/api/master-data/product'; import { ProductApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -230,8 +230,19 @@ const ProductsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await ProductApi.delete(selectedProduct?.id as number); + + const deleteResponse = await ProductApi.delete( + selectedProduct?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshProducts(); + deleteModal.closeModal(); toast.success('Successfully delete Product!'); setIsDeleteLoading(false); diff --git a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx index afa41295..b56e31bd 100644 --- a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx +++ b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx @@ -7,7 +7,7 @@ import { ProductionStandard } from '@/types/api/master-data/production-standard' import { Icon } from '@iconify/react'; import useSWR from 'swr'; import { ProductionStandardApi } from '@/services/api/master-data'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import { CellContext } from '@tanstack/react-table'; import { useModal } from '@/components/Modal'; @@ -94,9 +94,16 @@ const ProductionStandardTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await ProductionStandardApi.delete( + const deleteResponse = await ProductionStandardApi.delete( selectedProductionStandard?.id as number ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshProductionStandards(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/supplier/SupplierTable.tsx b/src/components/pages/master-data/supplier/SupplierTable.tsx index 3e10c9c8..2620c9e6 100644 --- a/src/components/pages/master-data/supplier/SupplierTable.tsx +++ b/src/components/pages/master-data/supplier/SupplierTable.tsx @@ -11,7 +11,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RequirePermission from '@/components/helper/RequirePermission'; import { ROWS_OPTIONS } from '@/config/constant'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn } from '@/lib/helper'; import { SupplierApi } from '@/services/api/master-data'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -205,7 +205,16 @@ const SuppliersTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await SupplierApi.delete(selectedSupplier?.id as number); + const deleteResponse = await SupplierApi.delete( + selectedSupplier?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshSuppliers(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/uom/UomsTable.tsx b/src/components/pages/master-data/uom/UomsTable.tsx index 851647b9..51e95661 100644 --- a/src/components/pages/master-data/uom/UomsTable.tsx +++ b/src/components/pages/master-data/uom/UomsTable.tsx @@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Uom } from '@/types/api/master-data/uom'; import { UomApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -164,7 +164,14 @@ const UomsTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await UomApi.delete(selectedUom?.id as number); + const deleteResponse = await UomApi.delete(selectedUom?.id as number); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshUoms(); deleteModal.closeModal(); diff --git a/src/components/pages/master-data/warehouse/WarehousesTable.tsx b/src/components/pages/master-data/warehouse/WarehousesTable.tsx index fe694322..62c39574 100644 --- a/src/components/pages/master-data/warehouse/WarehousesTable.tsx +++ b/src/components/pages/master-data/warehouse/WarehousesTable.tsx @@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission'; import { Warehouse } from '@/types/api/master-data/warehouse'; import { WarehouseApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { ROWS_OPTIONS } from '@/config/constant'; @@ -220,7 +220,16 @@ const WarehousesTable = () => { const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await WarehouseApi.delete(selectedWarehouse?.id as number); + const deleteResponse = await WarehouseApi.delete( + selectedWarehouse?.id as number + ); + + if (isResponseError(deleteResponse)) { + toast.error(deleteResponse.message); + setIsDeleteLoading(false); + return; + } + refreshWarehouses(); deleteModal.closeModal(); diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index 5ca0e789..6b935812 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -5,7 +5,10 @@ import Button from '@/components/Button'; import FloatingActionsButton from '@/components/FloatingActionsButton'; import CheckboxInput from '@/components/input/CheckboxInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; @@ -59,9 +62,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const selectedRowIds = Object.keys(rowSelection) .filter((id) => rowSelection[id]) .map((id) => parseInt(id)); - const [locationSelectInputValue, setLocationSelectInputValue] = useState(''); - const [areaSelectInputValue, setAreaSelectInputValue] = useState(''); - const [kandangSelectInputValue, setKandangSelectInputValue] = useState(''); const [selectedArea, setSelectedArea] = useState(null); const [selectedLocation, setSelectedLocation] = useState( null @@ -90,55 +90,25 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { { revalidateOnMount: true } ); - const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({ - search: areaSelectInputValue, - limit: '100', - }).toString()}`; - const { data: areas, isLoading: isLoadingAreas } = useSWR( - areaUrl, - AreaApi.getAllFetcher - ); - - const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ - search: locationSelectInputValue, - area_id: selectedArea != null ? selectedArea.value.toString() : '', - limit: '100', - }).toString()}`; - const { data: locations, isLoading: isLoadingLocations } = useSWR( - locationUrl, - LocationApi.getAllFetcher - ); - - const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ - search: kandangSelectInputValue, - location_id: - selectedLocation != null ? selectedLocation.value.toString() : '', - limit: '100', - }).toString()}`; - const { data: kandangs, isLoading: isLoadingKandang } = useSWR( - kandangUrl, - KandangApi.getAllFetcher - ); - - // ===== Data to Options Mapping ====== - const optionsArea = isResponseSuccess(areas) - ? areas?.data.map((area) => ({ - value: area.id, - label: area.name, - })) - : []; - const optionsKandang = isResponseSuccess(kandangs) - ? kandangs?.data.map((kandang) => ({ - value: kandang.id, - label: kandang.name, - })) - : []; - const optionsLocation = isResponseSuccess(locations) - ? locations?.data.map((location) => ({ - value: location.id, - label: location.name, - })) - : []; + // ===== Fetch Data Select ===== + const { + options: optionsArea, + isLoadingOptions: isLoadingArea, + setInputValue: setAreaSelectInputValue, + loadMore: loadMoreArea, + } = useSelect(AreaApi.basePath, 'id', 'name'); + const { + options: optionsLocation, + isLoadingOptions: isLoadingLocation, + setInputValue: setLocationSelectInputValue, + loadMore: loadMoreLocation, + } = useSelect(LocationApi.basePath, 'id', 'name'); + const { + options: optionsKandang, + isLoadingOptions: isLoadingKandang, + setInputValue: setKandangSelectInputValue, + loadMore: loadMoreKandang, + } = useSelect(KandangApi.basePath, 'id', 'name'); // ====== HANDLER ====== const confirmationModalDeleteClickHandler = async () => { @@ -385,7 +355,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { { setSelectedArea(val as OptionType); @@ -395,12 +365,13 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { ); }} onInputChange={setAreaSelectInputValue} + onMenuScrollToBottom={loadMoreArea} isClearable /> { setSelectedLocation(val as OptionType); @@ -410,6 +381,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { ); }} onInputChange={setLocationSelectInputValue} + onMenuScrollToBottom={loadMoreLocation} isClearable /> void }) => { ); }} onInputChange={setKandangSelectInputValue} + onMenuScrollToBottom={loadMoreKandang} isClearable /> { - console.log(`nonstock_id: ${nonstock_id}, index: ${index}`); if (!nonstock_id) { const updatedBudgets = formik.values.project_budgets .map((budget, i) => { if (i == index) { - console.log(`buget: ${null}, index: ${index}, i: ${i}`); return null; } else { - console.log(`buget: ${budget}, index: ${index}, i: ${i}`); return budget; } }) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 6b10b26e..b854cd59 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -5,7 +5,7 @@ import { RefObject } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import { SortingState, CellContext } from '@tanstack/react-table'; -import { cn, formatDate } from '@/lib/helper'; +import { cn, formatDate, formatNumber } from '@/lib/helper'; import RequirePermission from '@/components/helper/RequirePermission'; import { useModal } from '@/components/Modal'; import Modal from '@/components/Modal'; @@ -656,20 +656,30 @@ const RecordingTable = () => { ); }, cell: ({ row }) => { + const recording = row.original; + const isDisabled = isRecordingApproved(recording); + + const handleToggleSelection = (e: unknown) => { + if (!isDisabled) { + row.getToggleSelectedHandler()(e); + } + }; + return ( -
+
); }, }, { - header: '#', + header: 'No', cell: (props) => tableFilterState.pageSize * (tableFilterState.page - 1) + props.row.index + @@ -680,6 +690,10 @@ const RecordingTable = () => { cell: (props) => props.row.original.project_flock?.flock_name || '-', }, + { + header: 'Periode', + cell: (props) => props.row.original.project_flock?.period || '-', + }, { header: 'Kategori', cell: (props) => { @@ -696,7 +710,21 @@ const RecordingTable = () => { }, { header: 'Umur (hari)', - cell: (props) => props.row.original.day, + cell: (props) => { + return ( + <> + + {props.row.original.day} (Minggu ke- + {props.row.original.project_flock.production_standart.week}) + + + ); + }, + }, + { + accessorKey: 'warehouse.name', + header: 'Gudang', + cell: (props) => props.row.original.warehouse?.name, }, { accessorKey: 'record_date', @@ -705,10 +733,263 @@ const RecordingTable = () => { formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'), }, { - header: 'Populasi Awal', + header: 'Populasi Akhir', cell: (props) => - props.row.original.project_flock?.total_chick_qty?.toLocaleString() || - '-', + props.row.original.project_flock?.total_chick_qty != null + ? formatNumber(props.row.original.project_flock.total_chick_qty) + : '-', + }, + { + id: 'fcr', + header: 'FCR', + columns: [ + { + id: 'fcr_actual', + header: 'Actual', + cell: (props) => { + const value = props.row.original.fcr_value; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'fcr_standard', + header: 'Standard', + cell: (props) => { + const value = props.row.original.project_flock?.fcr?.fcr_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'feed_intake', + header: 'Feed Intake (KG)', + columns: [ + { + id: 'feed_intake_actual', + header: 'Actual', + cell: (props) => { + const value = props.row.original.feed_intake; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'feed_intake_standard', + header: 'Standard', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.feed_intake_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'mortality', + header: 'Mortality', + columns: [ + { + id: 'cum_depletion_rate_actual', + header: 'Cum Depletion Rate', + cell: (props) => { + const value = props.row.original.cum_depletion_rate; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'max_depletion_std', + header: 'Max Depletion Std', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.max_depletion_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'total_depletion', + header: 'Total Depletion', + cell: (props) => { + const value = props.row.original.total_depletion_qty; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'egg_production', + header: 'Egg Production', + columns: [ + { + id: 'egg_mass_actual', + header: 'Egg Mass Actual', + cell: (props) => { + const value = props.row.original.egg_mass; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_mass_standard', + header: 'Egg Mass Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.egg_mass_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_weight_actual', + header: 'Egg Weight Actual', + cell: (props) => { + const value = props.row.original.egg_weight; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_weight_standard', + header: 'Egg Weight Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.egg_weight_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'hen_performance', + header: 'Hen Performance', + columns: [ + { + id: 'hen_day_actual', + header: 'Hen Day Actual', + cell: (props) => { + const value = props.row.original.hen_day; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_day_standard', + header: 'Hen Day Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.hen_day_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_house_actual', + header: 'Hen House Actual', + cell: (props) => { + const value = props.row.original.hen_house; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_house_standard', + header: 'Hen House Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.hen_house_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + ], }, { header: 'Status Approval', @@ -874,14 +1155,15 @@ const RecordingTable = () => { 'mb-20': isResponseSuccess(recordings) && recordings?.data?.length === 0, }), - tableWrapperClassName: 'overflow-x-auto min-h-full!', - tableClassName: 'font-inter w-full table-auto min-h-full!', + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', headerRowClassName: 'border-b border-b-gray-200', headerColumnClassName: - 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', - bodyRowClassName: 'border-b border-b-gray-200', + 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200', bodyColumnClassName: - 'px-6 py-3 last:flex last:flex-row last:justify-end', + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', }} /> diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index 4901b349..8ebd4aa2 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -7,6 +7,7 @@ import { } from '@/types/api/production/recording'; type RecordingGrowingFormSchemaType = { + record_date: string; project_flock_kandang: { value: number; label: string; @@ -85,6 +86,9 @@ const EggObjectSchema: Yup.ObjectSchema = Yup.object({ export const RecordingGrowingFormSchema: Yup.ObjectSchema = Yup.object({ + record_date: Yup.string() + .required('Tanggal recording wajib diisi!') + .typeError('Tanggal recording wajib diisi!'), project_flock_kandang: Yup.object({ value: Yup.number().min(1).required(), label: Yup.string().required(), @@ -179,6 +183,9 @@ type RecordingFormData = Partial & { export const getRecordingGrowingFormInitialValues = ( initialValues?: RecordingFormData ): RecordingGrowingFormValues => ({ + record_date: initialValues?.record_datetime + ? new Date(initialValues.record_datetime).toISOString().split('T')[0] + : new Date().toISOString().split('T')[0], project_flock_kandang: initialValues?.project_flock_kandang_id ? { value: initialValues.project_flock_kandang_id, diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 80549f4e..b45f72d0 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -12,20 +12,33 @@ import RequirePermission from '@/components/helper/RequirePermission'; import Card from '@/components/Card'; import Badge from '@/components/Badge'; import NumberInput from '@/components/input/NumberInput'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import DateInput from '@/components/input/DateInput'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; import CheckboxInput from '@/components/input/CheckboxInput'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; -import { useModal } from '@/components/Modal'; +import Modal, { useModal } from '@/components/Modal'; import AlertErrorList from '@/components/helper/form/FormErrors'; +import Table from '@/components/Table'; +import { type ColumnDef } from '@tanstack/react-table'; import { ProjectFlockKandangApi, RecordingApi, ProjectFlockApi, } from '@/services/api/production'; +import { FcrApi, ProductionStandardApi } from '@/services/api/master-data'; +import { FcrWithStandards, FcrStandard } from '@/types/api/master-data/fcr'; +import { + ProductionStandard, + StandardDetails, +} from '@/types/api/master-data/production-standard'; import { LocationApi } from '@/services/api/master-data'; import { ProductWarehouseApi } from '@/services/api/inventory'; +import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { CreateGrowingRecordingPayload, @@ -36,7 +49,10 @@ import { NextDayRecording, } from '@/types/api/production/recording'; import { type BaseApiResponse } from '@/types/api/api-general'; -import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; +import { + ProjectFlockKandangLookup, + ProjectFlock, +} from '@/types/api/production/project-flock'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { Kandang } from '@/types/api/master-data/kandang'; @@ -68,6 +84,112 @@ interface RecordingFormProps { initialValues?: Recording; } +const fcrStandardColumns: ColumnDef[] = [ + { + accessorKey: 'weight', + header: 'Weight', + cell: (props) => formatNumber(props.getValue() as number), + }, + { + accessorKey: 'fcr_number', + header: 'FCR Number', + cell: (props) => formatNumber(props.getValue() as number), + }, + { + accessorKey: 'mortality', + header: 'Mortality', + cell: (props) => formatNumber(props.getValue() as number), + }, +]; + +const productionStandardColumns: ColumnDef[] = [ + { + accessorKey: 'week', + header: 'Minggu', + cell: (props) => `Minggu ${props.getValue() as number}`, + }, + { + accessorKey: 'growth_standard_detail.target_mean_bw', + header: 'Target Mean BW (gram)', + cell: (props) => + formatNumber( + (props.row.original.growth_standard_detail?.target_mean_bw as number) || + 0 + ), + }, + { + accessorKey: 'growth_standard_detail.max_depletion', + header: 'Max Depletion (%)', + cell: (props) => + `${ + (props.row.original.growth_standard_detail?.max_depletion as number) || + 0 + }%`, + }, + { + accessorKey: 'growth_standard_detail.min_uniformity', + header: 'Min Uniformity (%)', + cell: (props) => + `${ + (props.row.original.growth_standard_detail?.min_uniformity as number) || + 0 + }%`, + }, + { + accessorKey: 'growth_standard_detail.feed_intake', + header: 'Feed Intake (gram)', + cell: (props) => + formatNumber( + (props.row.original.growth_standard_detail?.feed_intake as number) || 0 + ), + }, + { + accessorKey: 'egg_production_standard_detail.target_hen_day_production', + header: 'Target Hen Day (%)', + cell: (props) => + `${ + (props.row.original.egg_production_standard_detail + ?.target_hen_day_production as number) || 0 + }%`, + }, + { + accessorKey: 'egg_production_standard_detail.target_hen_house_production', + header: 'Target Hen House (%)', + cell: (props) => + `${ + (props.row.original.egg_production_standard_detail + ?.target_hen_house_production as number) || 0 + }%`, + }, + { + accessorKey: 'egg_production_standard_detail.target_egg_weight', + header: 'Target Egg Weight (gram)', + cell: (props) => + formatNumber( + (props.row.original.egg_production_standard_detail + ?.target_egg_weight as number) || 0 + ), + }, + { + accessorKey: 'egg_production_standard_detail.target_egg_mass', + header: 'Target Egg Mass (gram)', + cell: (props) => + formatNumber( + (props.row.original.egg_production_standard_detail + ?.target_egg_mass as number) || 0 + ), + }, + { + accessorKey: 'egg_production_standard_detail.standard_fcr', + header: 'Standard FCR', + cell: (props) => + formatNumber( + (props.row.original.egg_production_standard_detail + ?.standard_fcr as number) || 0 + ), + }, +]; + const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== HOOKS & ROUTER ===== const router = useRouter(); @@ -77,16 +199,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const [selectedDepletions, setSelectedDepletions] = useState([]); const [selectedEggs, setSelectedEggs] = useState([]); - const [locationSearchValue, setLocationSearchValue] = useState(''); const [selectedLocation, setSelectedLocation] = useState( null ); - const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); const [selectedProjectFlock, setSelectedProjectFlock] = useState(null); const [selectedKandang, setSelectedKandang] = useState( null ); + const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] = + useState(''); + const [stockProductsLocationId, setStockProductsLocationId] = + useState(''); + const [stockProductsKandangId, setStockProductsKandangId] = + useState(''); + const [depletionProductsLocationId, setDepletionProductsLocationId] = + useState(''); + const [depletionProductsKandangId, setDepletionProductsKandangId] = + useState(''); + const [eggProductsLocationId, setEggProductsLocationId] = + useState(''); + const [eggProductsKandangId, setEggProductsKandangId] = useState(''); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); @@ -102,6 +235,54 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const approveModal = useModal(); const rejectModal = useModal(); const deleteModal = useModal(); + const fcrStandardModal = useModal(); + const productionStandardModal = useModal(); + + const [fcrStandards, setFcrStandards] = useState([]); + const [productionStandards, setProductionStandards] = + useState(null); + + const [isFcrModalOpen, setIsFcrModalOpen] = useState(false); + const [isProductionStandardModalOpen, setIsProductionStandardModalOpen] = + useState(false); + + useEffect(() => { + const checkFcrModalOpen = () => { + const isOpen = fcrStandardModal.ref.current?.open || false; + setIsFcrModalOpen(isOpen); + }; + + checkFcrModalOpen(); + + const observer = new MutationObserver(checkFcrModalOpen); + if (fcrStandardModal.ref.current) { + observer.observe(fcrStandardModal.ref.current, { + attributes: true, + attributeFilter: ['open'], + }); + } + + return () => observer.disconnect(); + }, [fcrStandardModal.ref]); + + useEffect(() => { + const checkProductionStandardModalOpen = () => { + const isOpen = productionStandardModal.ref.current?.open || false; + setIsProductionStandardModalOpen(isOpen); + }; + + checkProductionStandardModalOpen(); + + const observer = new MutationObserver(checkProductionStandardModalOpen); + if (productionStandardModal.ref.current) { + observer.observe(productionStandardModal.ref.current, { + attributes: true, + attributeFilter: ['open'], + }); + } + + return () => observer.disconnect(); + }, [productionStandardModal.ref]); const isRecordingApproved = useCallback((recording?: Recording) => { return ( @@ -117,10 +298,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== PAYLOAD CREATION HELPERS ===== const createGrowingPayload = useCallback( (values: RecordingGrowingFormValues) => { - const today = new Date().toISOString().split('T')[0]; return { project_flock_kandang_id: values.project_flock_kandang_id, - record_date: today, + record_date: values.record_date, stocks: (values.stocks ?? []).map((stock) => ({ product_warehouse_id: stock.product_warehouse_id, qty: Number(stock.qty) || 0, @@ -136,10 +316,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const createLayingPayload = useCallback( (values: RecordingLayingFormValues) => { - const today = new Date().toISOString().split('T')[0]; return { project_flock_kandang_id: values.project_flock_kandang_id, - record_date: today, + record_date: values.record_date, stocks: (values.stocks ?? []).map((stock) => ({ product_warehouse_id: stock.product_warehouse_id, qty: Number(stock.qty) || 0, @@ -210,26 +389,24 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [deleteModal, initialValues?.id, router]); // ===== API DATA FETCHING ===== - const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({ - search: locationSearchValue || '', - limit: '100', - }).toString()}`; - const { data: locations, isLoading: isLoadingLocations } = useSWR( - locationsUrl, - LocationApi.getAllFetcher - ); + const { + setInputValue: setLocationSearchValue, + options: locationOptions, + isLoadingOptions: isLoadingLocations, + loadMore: loadMoreLocations, + hasMore: hasMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); - const projectFlocksUrl = `${ProjectFlockApi.basePath}?${new URLSearchParams({ - search: projectFlockSearchValue || '', - limit: '100', - ...(selectedLocation - ? { location_id: selectedLocation.value.toString() } - : {}), - }).toString()}`; - const { data: projectFlocks, isLoading: isLoadingProjectFlocks } = useSWR( - projectFlocksUrl, - ProjectFlockApi.getAllFetcher - ); + const { + setInputValue: setProjectFlockSearchValue, + options: projectFlockOptions, + rawData: projectFlocksRawData, + isLoadingOptions: isLoadingProjectFlocks, + loadMore: loadMoreProjectFlocks, + hasMore: hasMoreProjectFlocks, + } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { + location_id: selectedProjectFlockLocationId, + }); const projectFlockKandangLookupUrl = useMemo(() => { if (!selectedProjectFlock || !selectedKandang) return null; @@ -255,6 +432,47 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? projectFlockKandangLookupData.data : undefined; + const fcrId = useMemo(() => { + if (type === 'add') { + return projectFlockKandangLookup?.project_flock?.fcr?.id; + } + return initialValues?.project_flock?.fcr?.id; + }, [type, projectFlockKandangLookup, initialValues]); + + const { data: fcr, isLoading: isLoadingFcrStandards } = useSWR( + isFcrModalOpen && fcrId ? `fcr-detail-${fcrId}` : null, + () => FcrApi.getSingle(fcrId!) + ); + + useEffect(() => { + if (fcr?.status === 'success') { + setFcrStandards((fcr.data as FcrWithStandards).fcr_standards || []); + } + }, [fcr]); + + const productionStandardId = useMemo(() => { + if (type === 'add') { + return projectFlockKandangLookup?.project_flock?.production_standard_id; + } + return initialValues?.project_flock?.production_standart?.id; + }, [type, projectFlockKandangLookup, initialValues]); + + const { data: productionStandard, isLoading: isLoadingProductionStandards } = + useSWR( + isProductionStandardModalOpen && productionStandardId + ? `production-standard-detail-${productionStandardId}` + : null, + () => ProductionStandardApi.getSingle(productionStandardId!) + ); + + useEffect(() => { + if (productionStandard?.status === 'success') { + setProductionStandards( + productionStandard.data as ProductionStandard | null + ); + } + }, [productionStandard]); + const projectFlockKandangDetailUrl = useMemo(() => { if ( type === 'add' || @@ -279,46 +497,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { ? projectFlockKandangDetailData.data : undefined; - const stockProductsUrl = useMemo(() => { - if (!selectedLocation || !selectedKandang) return null; - const params = new URLSearchParams({ - flags: 'PAKAN,OVK', - search: '', - limit: '100', - location_id: selectedLocation.value.toString(), - }); + const { + options: stockProductOptions, + rawData: stockProducts, + isLoadingOptions: isLoadingStockProducts, + loadMore: loadMoreStockProducts, + hasMore: hasMoreStockProducts, + } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { + flags: 'PAKAN,OVK', + location_id: stockProductsLocationId, + kandang_id: stockProductsKandangId, + }); - if (projectFlockKandangLookup?.kandang?.id) { - params.append( - 'kandang_id', - projectFlockKandangLookup.kandang.id.toString() - ); - } else if (selectedKandang) { - params.append('kandang_id', selectedKandang.value.toString()); - } - - return `${ProductWarehouseApi.basePath}?${params.toString()}`; - }, [selectedLocation, selectedKandang, projectFlockKandangLookup]); - - const depletionProductsUrl = useMemo(() => { - if (!selectedLocation || !selectedKandang) return null; - const params = new URLSearchParams({ - search: '', - limit: '100', - location_id: selectedLocation.value.toString(), - }); - - if (projectFlockKandangLookup?.kandang?.id) { - params.append( - 'kandang_id', - projectFlockKandangLookup.kandang.id.toString() - ); - } else if (selectedKandang) { - params.append('kandang_id', selectedKandang.value.toString()); - } - - return `${ProductWarehouseApi.basePath}?${params.toString()}`; - }, [selectedLocation, selectedKandang, projectFlockKandangLookup]); + const { + options: depletionProductOptions, + rawData: depletionProductsData, + isLoadingOptions: isLoadingDepletionProducts, + loadMore: loadMoreDepletionProducts, + hasMore: hasMoreDepletionProducts, + } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', { + location_id: depletionProductsLocationId, + kandang_id: depletionProductsKandangId, + }); const today = new Date().toISOString().split('T')[0]; const existingRecordingsUrl = `${RecordingApi.basePath}`; @@ -360,38 +560,17 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } }, [nextDayRecordingData]); - const { data: stockProducts, isLoading: isLoadingStockProducts } = useSWR( - stockProductsUrl, - ProductWarehouseApi.getAllFetcher - ); - - const { data: depletionProductsData, isLoading: isLoadingDepletionProducts } = - useSWR(depletionProductsUrl, ProductWarehouseApi.getAllFetcher); - - const eggProductsUrl = useMemo(() => { - if (!selectedLocation || !selectedKandang) return null; - const params = new URLSearchParams({ - search: 'telur', - limit: '100', - location_id: selectedLocation.value.toString(), - }); - - if (projectFlockKandangLookup?.kandang?.id) { - params.append( - 'kandang_id', - projectFlockKandangLookup.kandang.id.toString() - ); - } else if (selectedKandang) { - params.append('kandang_id', selectedKandang.value.toString()); - } - - return `${ProductWarehouseApi.basePath}?${params.toString()}`; - }, [selectedLocation, selectedKandang, projectFlockKandangLookup]); - - const { data: eggProductsData, isLoading: isLoadingEggProducts } = useSWR( - eggProductsUrl, - ProductWarehouseApi.getAllFetcher - ); + const { + options: eggProductOptions, + rawData: eggProductsData, + isLoadingOptions: isLoadingEggProducts, + loadMore: loadMoreEggProducts, + hasMore: hasMoreEggProducts, + } = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', { + search: 'telur', + location_id: eggProductsLocationId, + kandang_id: eggProductsKandangId, + }); const approvedProjectFlockKandangsUrl = useMemo(() => { const params = new URLSearchParams({ @@ -448,17 +627,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }); // ===== DATA PROCESSING ===== - const locationOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(locations)) { - const locationOptionsList = - locations?.data.map((location) => ({ - value: location.id, - label: location.name || '', - })) || []; - options = options.concat(locationOptionsList); - } + const enhancedLocationOptions = useMemo(() => { + const options = [...locationOptions]; if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) { const currentLocation = projectFlockKandangDetail.project_flock.location; @@ -474,19 +644,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [locations, projectFlockKandangDetail, type]); + }, [locationOptions, projectFlockKandangDetail, type]); - const projectFlockOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(projectFlocks)) { - const flockOptions = - projectFlocks?.data.map((projectFlock) => ({ - value: projectFlock.id, - label: projectFlock.flock_name || '', - })) || []; - options = options.concat(flockOptions); - } + const enhancedProjectFlockOptions = useMemo(() => { + const options = [...projectFlockOptions]; if (projectFlockKandangDetail && (type === 'edit' || type === 'detail')) { const currentProjectFlock = projectFlockKandangDetail.project_flock; @@ -502,13 +663,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [projectFlocks, projectFlockKandangDetail, type]); + }, [projectFlockOptions, projectFlockKandangDetail, type]); const kandangOptions = useMemo(() => { let options: OptionType[] = []; - if (selectedProjectFlock && isResponseSuccess(projectFlocks)) { - const selectedProjectFlockData = projectFlocks.data.find( + if (selectedProjectFlock && isResponseSuccess(projectFlocksRawData)) { + const data = projectFlocksRawData.data as ProjectFlock[]; + const selectedProjectFlockData = data.find( (pf) => pf.id === selectedProjectFlock.value ); @@ -548,7 +710,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return options; }, [ selectedProjectFlock, - projectFlocks, + projectFlocksRawData, projectFlockKandangDetail, type, approvedProjectFlockKandangs, @@ -570,22 +732,48 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return recordedIds; }, [existingRecordings, today]); - const unifiedStockProducts = useMemo(() => { - const options: OptionType[] = []; - if (isResponseSuccess(stockProducts) && selectedKandang) { - stockProducts.data.forEach((product) => { - const hasPakanFlag = product.product.flags?.includes('PAKAN'); - const hasOvkFlag = product.product.flags?.includes('OVK'); - - if (hasPakanFlag || hasOvkFlag) { - options.push({ - value: product.id, - label: product.product.name, - }); - } - }); + const currentTotalChickQty = useMemo(() => { + if (type === 'add' && projectFlockKandangLookup) { + return projectFlockKandangLookup.available_quantity || null; } + if ((type === 'edit' || type === 'detail') && initialValues) { + return initialValues.project_flock?.total_chick_qty || null; + } + + if (!isResponseSuccess(existingRecordings) || !selectedKandang) { + return null; + } + + let projectFlockKandangId: number | undefined; + + if (projectFlockKandangLookup) { + projectFlockKandangId = + projectFlockKandangLookup.project_flock_kandang_id; + } else if (projectFlockKandangDetail) { + projectFlockKandangId = projectFlockKandangDetail.id; + } + + if (!projectFlockKandangId) return null; + + const recording = existingRecordings.data.find( + (rec) => + rec.project_flock.project_flock_kandang_id === projectFlockKandangId + ); + + return recording?.project_flock.total_chick_qty || null; + }, [ + type, + initialValues, + existingRecordings, + selectedKandang, + projectFlockKandangLookup, + projectFlockKandangDetail, + ]); + + const unifiedStockProducts = useMemo(() => { + const options = [...stockProductOptions]; + if ( initialValues && 'stocks' in initialValues && @@ -608,12 +796,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { } return options; - }, [stockProducts, initialValues, type, selectedKandang]); + }, [stockProductOptions, initialValues, type]); const depletionProducts = useMemo(() => { const options: OptionType[] = []; + if (isResponseSuccess(depletionProductsData) && selectedKandang) { - depletionProductsData.data.forEach((product) => { + const data = depletionProductsData.data as unknown as ProductWarehouse[]; + data.forEach((product) => { const productName = product.product.name; if ( @@ -653,8 +843,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { const eggProducts = useMemo(() => { const options: OptionType[] = []; + if (isResponseSuccess(eggProductsData) && selectedKandang) { - eggProductsData.data.forEach((product) => { + const data = eggProductsData.data as unknown as ProductWarehouse[]; + data.forEach((product) => { const productName = product.product.name; if ( @@ -785,33 +977,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; // ===== HELPER FUNCTIONS ===== - useCallback((): OptionType | null => { - if ( - !formik.values.project_flock_kandang || - !isResponseSuccess(projectFlocks) - ) { - return selectedLocation; - } - const projectFlockId = formik.values.project_flock_kandang.value; - const projectFlock = projectFlocks.data.find( - (pf) => pf.id === projectFlockId - ); - if (projectFlock && projectFlock.location) { - return { - value: projectFlock.location.id, - label: projectFlock.location.name, - }; - } - return selectedLocation; - }, [formik.values.project_flock_kandang, projectFlocks, selectedLocation]); - const getAvailableStock = useCallback( (productWarehouseId: number) => { if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0; if (!isResponseSuccess(stockProducts)) return 0; - const productWarehouse = stockProducts.data.find( - (pw) => pw.id === productWarehouseId - ); + const data = stockProducts.data as unknown as ProductWarehouse[]; + const productWarehouse = data.find((pw) => pw.id === productWarehouseId); return productWarehouse?.quantity ?? 0; }, [stockProducts, type] @@ -888,9 +1059,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { (productWarehouseId: number) => { if (!isResponseSuccess(stockProducts)) return null; - const productWarehouse = stockProducts.data.find( - (pw) => pw.id === productWarehouseId - ); + const data = stockProducts.data as unknown as ProductWarehouse[]; + const productWarehouse = data.find((pw) => pw.id === productWarehouseId); if (!productWarehouse) return null; const hasPakanFlag = productWarehouse.product.flags?.includes('PAKAN'); @@ -975,9 +1145,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { // ===== EVENT HANDLERS ===== const locationChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedLocation(val as OptionType); + const location = val as OptionType; + setSelectedLocation(location); setSelectedProjectFlock(null); setSelectedKandang(null); + setSelectedProjectFlockLocationId( + location ? location.value.toString() : '' + ); formik.setFieldValue('project_flock_kandang', null); formik.setFieldValue('project_flock_kandang_id', 0); }; @@ -990,11 +1164,34 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; const kandangChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedKandang(val as OptionType); + const kandang = val as OptionType; + setSelectedKandang(kandang); + if (selectedLocation && kandang) { + setStockProductsLocationId(selectedLocation.value.toString()); + setStockProductsKandangId(kandang.value.toString()); + setDepletionProductsLocationId(selectedLocation.value.toString()); + setDepletionProductsKandangId(kandang.value.toString()); + setEggProductsLocationId(selectedLocation.value.toString()); + setEggProductsKandangId(kandang.value.toString()); + } else { + setStockProductsLocationId(''); + setStockProductsKandangId(''); + setDepletionProductsLocationId(''); + setDepletionProductsKandangId(''); + setEggProductsLocationId(''); + setEggProductsKandangId(''); + } formik.setFieldTouched('project_flock_kandang', true); formik.setFieldTouched('project_flock_kandang_id', true); }; + const handleRecordDateChange = useCallback( + (e: React.ChangeEvent) => { + formik.setFieldValue('record_date', e.target.value); + }, + [formik] + ); + useEffect(() => { if (projectFlockKandangLookup?.project_flock_kandang_id) { const projectFlockKandangId = @@ -1064,6 +1261,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { label: location.name || '', }; setSelectedLocation(locationOption); + setSelectedProjectFlockLocationId(location.id.toString()); if (projectFlock) { const projectFlockOption = { @@ -1079,6 +1277,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }; setSelectedKandang(kandangOption); + setStockProductsLocationId(location.id.toString()); + setStockProductsKandangId(kandang.id.toString()); + setDepletionProductsLocationId(location.id.toString()); + setDepletionProductsKandangId(kandang.id.toString()); + setEggProductsLocationId(location.id.toString()); + setEggProductsKandangId(kandang.id.toString()); + if ( formik.values.project_flock_kandang_id !== projectFlockKandangDetail.id @@ -1099,7 +1304,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { }, [ projectFlockKandangDetail, type, - projectFlockOptions, + enhancedProjectFlockOptions, formik.values.project_flock_kandang_id, ]); @@ -1380,31 +1585,46 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { body: 'flex flex-col gap-6', }} > -
+
<> + { ? getProjectFlockBadgeAdornment() : undefined } + bottomLabel={ + currentTotalChickQty + ? `Jumlah ayam saat ini: ${formatNumber( + currentTotalChickQty + )}` + : undefined + } />
+ + {/* Additional Information Fields */} + {(projectFlockKandangLookup || projectFlockKandangDetail) && ( +
+
+ Gudang +

+ {projectFlockKandangLookup?.warehouse?.name || + initialValues?.warehouse?.name || + '-'} +

+
+
+ Umur +

+ {nextDayRecording + ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${Math.ceil(nextDayRecording.next_day / 7)})` + : initialValues?.day + ? `Hari ke-${initialValues.day} (Minggu ke-${Math.ceil(initialValues.day / 7)})` + : '-'} +

+
+
+ Standard FCR +
+ fcrStandardModal.openModal()} + > + {projectFlockKandangLookup?.project_flock?.fcr?.name || + initialValues?.project_flock?.fcr?.name || + '-'} + +
+
+
+ + Standard Produksi + +
+ productionStandardModal.openModal()} + > + {productionStandards?.name || + initialValues?.project_flock?.production_standart + ?.name || + (projectFlockKandangLookup?.project_flock + ?.production_standard_id + ? `Production Standard ${projectFlockKandangLookup.project_flock.production_standard_id}` + : '-')} + +
+
+
+ )} )} @@ -1453,7 +1750,106 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { body: 'flex flex-col gap-4', }} > -
+
+
+ Lokasi +

+ {projectFlockKandangLookup?.project_flock?.location + ?.name || + projectFlockKandangDetail?.project_flock?.location + ?.name || + '-'} +

+
+
+ Project Flock +

+ {projectFlockKandangLookup?.project_flock?.flock_name || + projectFlockKandangDetail?.project_flock?.flock_name || + '-'} +

+
+
+ Kandang +

+ {projectFlockKandangLookup?.kandang?.name || + projectFlockKandangDetail?.kandang?.name || + '-'} +

+
+
+ Kategori +

+ + {initialValues.project_flock?.project_flock_category} + +

+
+
+ Gudang +

+ {initialValues.warehouse?.name || '-'} +

+
+
+ + Jumlah Ayam Saat Ini + +

+ {initialValues.project_flock?.total_chick_qty + ? formatNumber( + initialValues.project_flock.total_chick_qty + ) + : '-'} +

+
+
+ Periode +

+ + Periode{' '} + {projectFlockKandangLookup?.project_flock?.period || + projectFlockKandangDetail?.project_flock?.period || + '-'} + +

+
+
+ + Tanggal Recording + +

+ {formatDate( + initialValues.record_datetime || '', + 'DD MMMM YYYY' + )} +

+
+
+ Hari +

+ Hari ke-{initialValues.day} (Minggu ke- + {initialValues.project_flock?.production_standart?.week || + '-'} + ) +

+
{initialValues.approval && (
@@ -1505,80 +1901,39 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)}
- Lokasi -

- {projectFlockKandangLookup?.project_flock?.location - ?.name || - projectFlockKandangDetail?.project_flock?.location - ?.name || - '-'} -

-
-
- Project Flock -

- {projectFlockKandangLookup?.project_flock?.flock_name || - projectFlockKandangDetail?.project_flock?.flock_name || - '-'} -

-
-
- Kandang -

- {projectFlockKandangLookup?.kandang?.name || - projectFlockKandangDetail?.kandang?.name || - '-'} -

+ Standard FCR +
+ fcrStandardModal.openModal()} + > + {initialValues.project_flock?.fcr?.name || '-'} + +
- Tanggal Recording + Standard Produksi -

- {formatDate( - initialValues.record_datetime || '', - 'DD MMMM YYYY' - )} -

-
-
- Hari -

Hari ke-{initialValues.day}

-
-
- Kategori -

+

- {initialValues.project_flock?.project_flock_category} - -

-
-
- Periode -

- productionStandardModal.openModal()} > - Periode{' '} - {projectFlockKandangLookup?.project_flock?.period || - projectFlockKandangDetail?.project_flock?.period || - '-'} + {initialValues.project_flock?.production_standart + ?.name || '-'} -

+
@@ -1616,15 +1971,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { FCR - {initialValues.fcr_value && - initialValues.fcr_value > 0 + {initialValues.fcr_value != null ? formatNumber(initialValues.fcr_value) : '-'} - {initialValues.project_flock?.fcr?.fcr_std && - initialValues.project_flock?.fcr?.fcr_std > 0 + {initialValues.project_flock?.fcr?.fcr_std != null ? formatNumber( initialValues.project_flock?.fcr?.fcr_std ) @@ -1635,17 +1988,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Feed Intake (KG) - {initialValues.feed_intake && - initialValues.feed_intake > 0 + {initialValues.feed_intake != null ? formatNumber(initialValues.feed_intake) : '-'} {initialValues.project_flock?.production_standart - ?.feed_intake_std && - initialValues.project_flock?.production_standart - ?.feed_intake_std > 0 + ?.feed_intake_std != null ? formatNumber( initialValues.project_flock?.production_standart ?.feed_intake_std @@ -1672,8 +2022,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Deplesi Kumulatif - {initialValues.cum_depletion_rate && - initialValues.cum_depletion_rate > 0 + {initialValues.cum_depletion_rate != null ? `${initialValues.cum_depletion_rate.toFixed(2)}%` : '-'} @@ -1682,8 +2031,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Total Depletion - {initialValues.total_depletion_qty && - initialValues.total_depletion_qty > 0 + {initialValues.total_depletion_qty != null ? formatNumber(initialValues.total_depletion_qty) : '-'} @@ -1692,8 +2040,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Total Ayam - {initialValues.project_flock?.total_chick_qty && - initialValues.project_flock?.total_chick_qty > 0 + {initialValues.project_flock?.total_chick_qty != null ? formatNumber( initialValues.project_flock?.total_chick_qty ) @@ -1735,17 +2082,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Egg Mass - {initialValues.egg_mass && - initialValues.egg_mass > 0 + {initialValues.egg_mass != null ? formatNumber(initialValues.egg_mass) : '-'} {initialValues.project_flock?.production_standart - ?.egg_mass_std && - initialValues.project_flock?.production_standart - ?.egg_mass_std > 0 + ?.egg_mass_std != null ? formatNumber( initialValues.project_flock ?.production_standart?.egg_mass_std @@ -1759,17 +2103,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { - {initialValues.egg_weight && - initialValues.egg_weight > 0 + {initialValues.egg_weight != null ? formatNumber(initialValues.egg_weight) : '-'} {initialValues.project_flock?.production_standart - ?.egg_weight_std && - initialValues.project_flock?.production_standart - ?.egg_weight_std > 0 + ?.egg_weight_std != null ? formatNumber( initialValues.project_flock ?.production_standart?.egg_weight_std @@ -1781,17 +2122,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Hen Day - {initialValues.hen_day && - initialValues.hen_day > 0 + {initialValues.hen_day != null ? formatNumber(initialValues.hen_day) : '-'} {initialValues.project_flock?.production_standart - ?.hen_day_std !== undefined && - initialValues.project_flock?.production_standart - ?.hen_day_std > 0 + ?.hen_day_std != null ? `${initialValues.project_flock?.production_standart?.hen_day_std}%` : '-'} @@ -1800,17 +2138,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Hen House - {initialValues.hen_house && - initialValues.hen_house > 0 + {initialValues.hen_house != null ? formatNumber(initialValues.hen_house) : '-'} {initialValues.project_flock?.production_standart - ?.hen_house_std !== undefined && - initialValues.project_flock?.production_standart - ?.hen_house_std > 0 + ?.hen_house_std != null ? `${initialValues.project_flock?.production_standart?.hen_house_std}%` : '-'} @@ -1931,6 +2266,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { options={unifiedStockProducts} placeholder='Pilih Produk' isLoading={isLoadingStockProducts} + onMenuScrollToBottom={loadMoreStockProducts} isError={ isRepeaterInputError( 'stocks', @@ -2152,6 +2488,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { options={depletionProducts} placeholder='Pilih Kondisi' isLoading={isLoadingDepletionProducts} + onMenuScrollToBottom={loadMoreDepletionProducts} isError={ isRepeaterInputError( 'depletions', @@ -2371,6 +2708,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { options={eggProducts} placeholder='Pilih Kondisi Telur' isLoading={isLoadingEggProducts} + onMenuScrollToBottom={loadMoreEggProducts} isError={ isRepeaterInputError( 'eggs', @@ -2656,6 +2994,119 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { )} )} + + {/* FCR Standard Modal */} + +
+ {/* Modal Header */} +
+
+ +

Detail Standard FCR

+
+ +
+
+ {isLoadingFcrStandards ? ( +
+ +
+ ) : fcrStandards.length > 0 ? ( + + data={fcrStandards} + columns={fcrStandardColumns} + pageSize={100} + className={{ + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + paginationClassName: 'hidden', + }} + /> + ) : ( +

+ Tidak ada data FCR standards +

+ )} +
+
+
+ + {/* Production Standard Modal */} + +
+ {/* Modal Header */} +
+
+ +

Detail Standard Produksi

+
+ +
+
+ {isLoadingProductionStandards ? ( +
+ +
+ ) : productionStandards?.details && + productionStandards.details.length > 0 ? ( + + data={productionStandards.details} + columns={productionStandardColumns} + pageSize={100} + className={{ + tableWrapperClassName: 'overflow-x-auto', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + paginationClassName: 'hidden', + }} + /> + ) : ( +

+ Tidak ada data Production standards +

+ )} +
+
+
); }; diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx index 18ce404d..860c4616 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx @@ -179,12 +179,16 @@ const TransferToLayingsTable = () => { setInputValue: setFlockSourceInputValue, options: flockSourceOptions, isLoadingOptions: isLoadingFlockSourceOptions, + loadMore: loadMoreFlockSource, + hasMore: hasMoreFlockSource, } = useSelect(FlockApi.basePath, 'id', 'name'); const { setInputValue: setFlockDestinationInputValue, options: flockDestinationOptions, isLoadingOptions: isLoadingFlockDestinationOptions, + loadMore: loadMoreFlockDestination, + hasMore: hasMoreFlockDestination, } = useSelect(FlockApi.basePath, 'id', 'name'); // Flocks value @@ -595,6 +599,7 @@ const TransferToLayingsTable = () => { value={selectedFlockSource} onChange={flockSourceChangeHandler} onInputChange={setFlockSourceInputValue} + onMenuScrollToBottom={loadMoreFlockSource} isClearable className={{ wrapper: 'col-span-12 sm:col-span-3', @@ -608,6 +613,7 @@ const TransferToLayingsTable = () => { value={selectedFlockDestination} onChange={flockDestinationChangeHandler} onInputChange={setFlockDestinationInputValue} + onMenuScrollToBottom={loadMoreFlockDestination} isClearable className={{ wrapper: 'col-span-12 sm:col-span-3', diff --git a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx index c5683fff..a257af0d 100644 --- a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx +++ b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx @@ -270,6 +270,8 @@ const TransferToLayingForm = ({ options: flockSourceOptions, isLoadingOptions: isLoadingFlockSourceOptions, rawData: flockSources, + loadMore: loadMoreFlockSource, + hasMore: hasMoreFlockSource, } = useSelect( '/production/project-flocks', 'id', @@ -360,6 +362,8 @@ const TransferToLayingForm = ({ options: flockDestinationOptions, isLoadingOptions: isLoadingFlockDestinationOptions, rawData: flockDestinations, + loadMore: loadMoreFlockDestination, + hasMore: hasMoreFlockDestination, } = useSelect( '/production/project-flocks', 'id', @@ -573,6 +577,7 @@ const TransferToLayingForm = ({ onChange={flockSourceChangeHandler} isLoading={isLoadingFlockSourceOptions} onInputChange={setFlockSourceInputValue} + onMenuScrollToBottom={loadMoreFlockSource} isError={ formik.touched.flockSource && Boolean(typeof formik.errors.flockSource === 'string') @@ -591,6 +596,7 @@ const TransferToLayingForm = ({ onChange={flockDestinationChangeHandler} isLoading={isLoadingFlockDestinationOptions} onInputChange={setFlockDestinationInputValue} + onMenuScrollToBottom={loadMoreFlockDestination} isError={ formik.touched.flockDestination && Boolean(typeof formik.errors.flockDestination === 'string') diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx index 63c446ac..c2049ab1 100644 --- a/src/components/pages/production/uniformity/UniformityTable.tsx +++ b/src/components/pages/production/uniformity/UniformityTable.tsx @@ -37,7 +37,10 @@ import DateInput from '@/components/input/DateInput'; import { LocationApi } from '@/services/api/master-data'; import { ProjectFlockApi } from '@/services/api/production'; import { Kandang } from '@/types/api/master-data/kandang'; -import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; +import { + ProjectFlockKandangLookup, + ProjectFlock, +} from '@/types/api/production/project-flock'; import { getStatusColor, getStatusIndicatorColor, @@ -229,63 +232,37 @@ const UniformityTable = () => { useState(undefined); const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); - const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); + const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] = + useState(''); const [filterErrors, setFilterErrors] = useState>({}); const { setInputValue: setFilterLocationInputValue, options: filterLocationOptions, isLoadingOptions: isLoadingFilterLocations, - } = useSelect(LocationApi.basePath, 'id', 'name', 'search', { - limit: '100', - }); + loadMore: loadMoreFilterLocations, + hasMore: hasMoreFilterLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); // ===== FETCH PROJECT FLOCKS DATA FOR FILTER ===== - const filterProjectFlocksUrl = useMemo(() => { - const params = new URLSearchParams({ - search: projectFlockSearchValue || '', - limit: '100', - }); - if (filterLocation) { - params.append('location_id', filterLocation.value.toString()); - } - return `${ProjectFlockApi.basePath}?${params.toString()}`; - }, [projectFlockSearchValue, filterLocation]); - const { - data: filterProjectFlocksData, - isLoading: isLoadingFilterProjectFlocks, - } = useSWR(filterProjectFlocksUrl, ProjectFlockApi.getAllFetcher); - - const filterProjectFlocksDataList = useMemo( - () => - isResponseSuccess(filterProjectFlocksData) - ? filterProjectFlocksData.data - : undefined, - [filterProjectFlocksData] - ); - - const filterProjectFlockOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(filterProjectFlocksData)) { - const flockOptions = - filterProjectFlocksData?.data.map((projectFlock) => ({ - value: projectFlock.id, - label: projectFlock.flock_name || '', - })) || []; - options = options.concat(flockOptions); - } - - return options; - }, [filterProjectFlocksData]); + setInputValue: setFilterProjectFlockSearchValue, + options: filterProjectFlockOptions, + rawData: filterProjectFlocksRawData, + isLoadingOptions: isLoadingFilterProjectFlocks, + loadMore: loadMoreFilterProjectFlocks, + hasMore: hasMoreFilterProjectFlocks, + } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { + location_id: filterProjectFlockLocationId, + }); // ===== KANDANG OPTIONS FOR FILTER ===== const filterKandangOptions = useMemo(() => { let options: OptionType[] = []; - if (filterProjectFlock && filterProjectFlocksDataList) { - const selectedProjectFlockData = filterProjectFlocksDataList.find( + if (filterProjectFlock && isResponseSuccess(filterProjectFlocksRawData)) { + const data = filterProjectFlocksRawData.data as unknown as ProjectFlock[]; + const selectedProjectFlockData = data.find( (pf) => pf.id === filterProjectFlock.value ); @@ -301,7 +278,7 @@ const UniformityTable = () => { } return options; - }, [filterProjectFlock, filterProjectFlocksDataList]); + }, [filterProjectFlock, filterProjectFlocksRawData]); // ===== PROJECT FLOCK KANDANG LOOKUP ===== const projectFlockKandangLookupUrl = useMemo(() => { @@ -394,9 +371,13 @@ const UniformityTable = () => { // ===== FILTER HANDLERS ===== const handleFilterLocationChange = useCallback( (val: OptionType | OptionType[] | null) => { - setFilterLocation(val as OptionType | null); + const location = val as OptionType | null; + setFilterLocation(location); setFilterProjectFlock(null); setFilterKandang(null); + setFilterProjectFlockLocationId( + location ? location.value.toString() : '' + ); }, [] ); @@ -1206,6 +1187,7 @@ const UniformityTable = () => { options={filterLocationOptions} onInputChange={setFilterLocationInputValue} isLoading={isLoadingFilterLocations} + onMenuScrollToBottom={loadMoreFilterLocations} className={{ wrapper: 'w-full' }} /> {filterErrors.location && ( @@ -1225,8 +1207,9 @@ const UniformityTable = () => { setFilterErrors((prev) => ({ ...prev, project_flock: '' })); }} options={filterProjectFlockOptions} - onInputChange={setProjectFlockSearchValue} + onInputChange={setFilterProjectFlockSearchValue} isLoading={isLoadingFilterProjectFlocks} + onMenuScrollToBottom={loadMoreFilterProjectFlocks} isDisabled={!filterLocation} className={{ wrapper: 'w-full' }} /> diff --git a/src/components/pages/production/uniformity/chart/UniformityStat.tsx b/src/components/pages/production/uniformity/chart/UniformityStat.tsx index ea8a5c0e..e7603e16 100644 --- a/src/components/pages/production/uniformity/chart/UniformityStat.tsx +++ b/src/components/pages/production/uniformity/chart/UniformityStat.tsx @@ -1,4 +1,4 @@ -import Badge from '../../../../Badge'; +import Badge from '@/components/Badge'; import Card from '@/components/Card'; import { Icon } from '@iconify/react'; import { formatNumber } from '@/lib/helper'; diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx index 54b4ee2b..f46a15b3 100644 --- a/src/components/pages/production/uniformity/form/UniformityForm.tsx +++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx @@ -36,7 +36,10 @@ import { VerifyUniformityPayload, } from '@/types/api/production/uniformity'; import { type BaseApiResponse } from '@/types/api/api-general'; -import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock'; +import { + ProjectFlockKandangLookup, + ProjectFlock, +} from '@/types/api/production/project-flock'; import { Kandang } from '@/types/api/master-data/kandang'; import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm'; import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm'; @@ -88,7 +91,9 @@ const UniformityForm = ({ null ); - const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); + const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] = + useState(''); + const [selectedProjectFlock, setSelectedProjectFlock] = useState(null); @@ -100,50 +105,21 @@ const UniformityForm = ({ setInputValue: setLocationSelectInputValue, options: locationOptions, isLoadingOptions: isLoadingLocations, - } = useSelect(LocationApi.basePath, 'id', 'name', 'search', { - page: '1', - limit: '100', + loadMore: loadMoreLocations, + hasMore: hasMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', 'search'); + + const { + setInputValue: setProjectFlockSearchValue, + options: projectFlockOptions, + rawData: projectFlocksRawData, + isLoadingOptions: isLoadingProjectFlocks, + loadMore: loadMoreProjectFlocks, + hasMore: hasMoreProjectFlocks, + } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', { + location_id: selectedProjectFlockLocationId, }); - // ===== FETCH PROJECT FLOCKS DATA ===== - const projectFlocksUrl = useMemo(() => { - const params = new URLSearchParams({ - search: projectFlockSearchValue || '', - page: '1', - limit: '100', - }); - if (selectedLocation) { - params.append('location_id', selectedLocation.value.toString()); - } - return `${ProjectFlockApi.basePath}?${params.toString()}`; - }, [projectFlockSearchValue, selectedLocation]); - - const { data: projectFlocksData, isLoading: isLoadingProjectFlocks } = useSWR( - projectFlocksUrl, - ProjectFlockApi.getAllFetcher - ); - - const projectFlocksDataList = - projectFlocksData?.status === 'success' - ? projectFlocksData.data - : undefined; - - // ===== PROJECT FLOCK OPTIONS ===== - const projectFlockOptions = useMemo(() => { - let options: OptionType[] = []; - - if (isResponseSuccess(projectFlocksData)) { - const flockOptions = - projectFlocksData?.data.map((projectFlock) => ({ - value: projectFlock.id, - label: projectFlock.flock_name || '', - })) || []; - options = options.concat(flockOptions); - } - - return options; - }, [projectFlocksData]); - // ===== APPROVED PROJECT FLOCK KANDANGS ===== const approvedProjectFlockKandangsUrl = useMemo(() => { const params = new URLSearchParams({ @@ -168,8 +144,9 @@ const UniformityForm = ({ const kandangOptions = useMemo(() => { let options: OptionType[] = []; - if (selectedProjectFlock && projectFlocksDataList) { - const selectedProjectFlockData = projectFlocksDataList.find( + if (selectedProjectFlock && isResponseSuccess(projectFlocksRawData)) { + const data = projectFlocksRawData.data as unknown as ProjectFlock[]; + const selectedProjectFlockData = data.find( (pf) => pf.id === selectedProjectFlock.value ); @@ -196,7 +173,7 @@ const UniformityForm = ({ return options; }, [ selectedProjectFlock, - projectFlocksDataList, + projectFlocksRawData, approvedProjectFlockKandangs, formType, ]); @@ -313,6 +290,10 @@ const UniformityForm = ({ formik.setFieldValue('location_id', locationId); setSelectedLocation(location); + setSelectedProjectFlock(null); + setSelectedProjectFlockLocationId( + location ? location.value.toString() : '' + ); }, [] ); @@ -513,6 +494,7 @@ const UniformityForm = ({ options={locationOptions} onInputChange={setLocationSelectInputValue} isLoading={isLoadingLocations} + onMenuScrollToBottom={loadMoreLocations} isError={ formik.touched.location_id && Boolean(formik.errors.location_id) } @@ -530,6 +512,7 @@ const UniformityForm = ({ options={projectFlockOptions} onInputChange={setProjectFlockSearchValue} isLoading={isLoadingProjectFlocks} + onMenuScrollToBottom={loadMoreProjectFlocks} isDisabled={!formik.values.location_id} isError={ formik.touched.project_flock_id && diff --git a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx index 2b18afee..de27a169 100644 --- a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx +++ b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx @@ -156,8 +156,11 @@ const PurchaseOrderAcceptApprovalForm = ({ setInputValue: setExpeditionsSelectInputValue, options: expeditionVendors, isLoadingOptions: isLoadingExpeditions, + loadMore: loadMoreExpeditions, + hasMore: hasMoreExpeditions, } = useSelect(SupplierApi.basePath, 'id', 'name', 'search', { category: 'BOP', + flag: 'EKSPEDISI', }); // ===== FORM CONFIGURATION ===== @@ -183,8 +186,8 @@ const PurchaseOrderAcceptApprovalForm = ({ purchase_item_id: formItem.purchase_item_id || 0, received_date: formItem.received_date || '', travel_number: formItem.travel_number || '', - vehicle_number: formItem.vehicle_number || '', - expedition_vendor_id: formItem.expedition_vendor_id || 0, + vehicle_number: formItem.vehicle_number || null, + expedition_vendor_id: formItem.expedition_vendor_id || null, received_qty: typeof formItem.received_qty === 'string' ? parseFloat(formItem.received_qty) || 0 @@ -192,10 +195,13 @@ const PurchaseOrderAcceptApprovalForm = ({ transport_per_item: typeof formItem.transport_per_item === 'string' ? parseFloat(formItem.transport_per_item) || 0 - : formItem.transport_per_item || 0, + : formItem.transport_per_item || null, }; }) || [], - travel_documents: values.travel_documents || [], + travel_documents: + values.travel_documents + ?.filter((file): file is File => file instanceof File) + .filter(Boolean) || undefined, }; switch (type) { @@ -403,22 +409,13 @@ const PurchaseOrderAcceptApprovalForm = ({ Dokumen Surat Jalan * - - Nomor Kendaraan - * - - - Vendor Ekspedisi - * - + Nomor Kendaraan + Vendor Ekspedisi Jumlah Diterima * - - Transport/Item - * - + Transport/Item @@ -536,7 +533,6 @@ const PurchaseOrderAcceptApprovalForm = ({ @@ -680,7 +676,6 @@ const PurchaseOrderAcceptApprovalForm = ({
0); - } - ) - .typeError('Vendor ekspedisi harus dipilih!'), + .nullable() + .optional() + .typeError('Vendor ekspedisi harus berupa angka!'), received_qty: Yup.mixed() .required('Jumlah diterima wajib diisi!') .test( @@ -217,13 +212,14 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema() - .required('Biaya transport per item wajib diisi!') + .nullable() + .optional() .test( 'is-valid-transport-per-item', 'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!', function (value) { if (value === '' || value === null || value === undefined) - return false; + return true; const numValue = typeof value === 'string' ? parseFloat(value) : value; return !isNaN(numValue) && numValue >= 0; @@ -389,16 +385,17 @@ export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema() - .required('Dokumen surat jalan wajib diupload!') + .nullable() + .optional() .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => { if (!value) return true; if (value instanceof File) return value.size <= 5 * 1024 * 1024; return true; }) ) - .required('Dokumen surat jalan wajib diupload!') - .min(1, 'Minimal upload 1 dokumen surat jalan!') - .typeError('Dokumen surat jalan wajib diupload!'), + .nullable() + .optional() + .typeError('Dokumen surat jalan harus berupa array!'), }); export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType = diff --git a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx index 0c319f5a..9a54d537 100644 --- a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx +++ b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx @@ -63,11 +63,9 @@ const PurchaseRequestForm = ({ useState(''); const [formErrorList, setFormErrorList] = useState([]); - // ===== TYPE DEFINITIONS ===== - interface ProductOptionType { - value: number; - label: string; - } + const [selectedArea, setSelectedArea] = useState(''); + const [selectedLocation, setSelectedLocation] = useState(''); + const [disabledLocation, setDisabledLocation] = useState(true); // ===== UTILITY FUNCTIONS ===== const isRepeaterInputError = ( @@ -160,11 +158,35 @@ const PurchaseRequestForm = ({ isLoadingOptions: isLoadingAreas, } = useSelect(AreaApi.basePath, 'id', 'name', 'search'); + const { + options: locationOptions, + isLoadingOptions: isLoadingLocations, + loadMore: loadMoreLocations, + hasMore: hasMoreLocations, + } = useSelect(LocationApi.basePath, 'id', 'name', '', { + area_id: + selectedArea != '' + ? selectedArea + : ((initialValues?.area?.id ?? '') as string), + }); + const { inputValue: warehouseSelectInputValue, setInputValue: setWarehouseSelectInputValue, + options: warehouseOptions, isLoadingOptions: isLoadingWarehouses, - } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search'); + loadMore: loadMoreWarehouses, + hasMore: hasMoreWarehouses, + } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search', { + area_id: + selectedArea != '' + ? selectedArea + : ((initialValues?.area?.id ?? '') as string), + location_id: + selectedLocation != '' + ? selectedLocation + : ((initialValues?.location?.id ?? '') as string), + }); // ===== FORM CONFIGURATION ===== const formikInitialValues = useMemo( @@ -267,70 +289,6 @@ const PurchaseRequestForm = ({ return data; }, [supplierData]); - const locationsUrl = useMemo(() => { - const params = new URLSearchParams({ - search: locationSelectInputValue, - ...(formik.values.area_id && formik.values.area_id > 0 - ? { area_id: formik.values.area_id.toString() } - : {}), - }); - return `${LocationApi.basePath}?${params.toString()}`; - }, [locationSelectInputValue, formik.values.area_id]); - - const { data: locations, isLoading: isLoadingLocations } = useSWR( - locationsUrl, - LocationApi.getAllFetcher - ); - - const locationOptions = useMemo(() => { - if (!isResponseSuccess(locations)) return []; - return ( - locations?.data.map((location) => ({ - value: location.id, - label: location.name, - })) || [] - ); - }, [locations]); - - const warehousesUrl = useMemo(() => { - const params = new URLSearchParams({ search: warehouseSelectInputValue }); - - if (formik.values.area_id && formik.values.area_id > 0) { - params.append('area_id', formik.values.area_id.toString()); - } - - if (formik.values.location_id && formik.values.location_id > 0) { - params.append('location_id', formik.values.location_id.toString()); - } - - return `${WarehouseApi.basePath}?${params.toString()}`; - }, [ - warehouseSelectInputValue, - formik.values.area_id, - formik.values.location_id, - ]); - - const { data: warehouses } = useSWR( - warehousesUrl, - WarehouseApi.getAllFetcher - ); - - const warehouseOptions = useMemo(() => { - if (!isResponseSuccess(warehouses)) return []; - - return ( - warehouses?.data.map((w) => ({ - value: w.id, - label: w.name, - area: w.area?.name, - location: - 'type' in w && (w.type === 'LOKASI' || w.type === 'KANDANG') - ? w.location?.name - : undefined, - })) || [] - ); - }, [warehouses]); - const addPurchaseItem = () => { const newItems = [ ...(formik.values.items || []), @@ -407,6 +365,18 @@ const PurchaseRequestForm = ({ } }, [formik.values.supplier_id]); + useEffect(() => { + if (type !== 'add' && initialValues) { + if (initialValues.area?.id) { + setSelectedArea(initialValues.area.id.toString()); + setDisabledLocation(false); + } + if (initialValues.location?.id) { + setSelectedLocation(initialValues.location.id.toString()); + } + } + }, [type, initialValues]); + // ===== FORM HANDLERS ===== const handleSupplierChange = useCallback( (val: OptionType | OptionType[] | null) => { @@ -445,6 +415,16 @@ const PurchaseRequestForm = ({ formik.setFieldValue('area_id', (area as OptionType)?.value || 0); formik.setFieldTouched('area', true); formik.setFieldValue('area', area); + + setSelectedArea((area as OptionType)?.value as string); + setSelectedLocation(''); + const disabled = (area as OptionType)?.value == null; + setDisabledLocation(disabled); + + formik.setFieldTouched('location_id', false); + formik.setFieldValue('location_id', 0); + formik.setFieldTouched('location', false); + formik.setFieldValue('location', null); }, [] ); @@ -456,6 +436,8 @@ const PurchaseRequestForm = ({ formik.setFieldValue('location_id', (location as OptionType)?.value || 0); formik.setFieldTouched('location', true); formik.setFieldValue('location', location); + + setSelectedLocation((location as OptionType)?.value as string); }, [] ); @@ -596,10 +578,15 @@ const PurchaseRequestForm = ({ placeholder='Pilih Lokasi...' value={formik.values.location} onChange={handleLocationChange} - options={locationOptions} + options={ + selectedArea != '' || initialValues?.area?.id + ? locationOptions + : [] + } onInputChange={setLocationSelectInputValue} isLoading={isLoadingLocations} - isDisabled={type === 'detail'} + onMenuScrollToBottom={loadMoreLocations} + isDisabled={type === 'detail' || disabledLocation} isClearable={type !== 'detail'} /> @@ -713,6 +700,7 @@ const PurchaseRequestForm = ({ options={warehouseOptions} onInputChange={setWarehouseSelectInputValue} isLoading={isLoadingWarehouses} + onMenuScrollToBottom={loadMoreWarehouses} isError={ isRepeaterInputError(idx, 'warehouse_id').isError } @@ -732,9 +720,9 @@ const PurchaseRequestForm = ({ required value={item.product ?? undefined} onChange={(val) => { - const product = val as ProductOptionType | null; + const product = val as OptionType | null; const productId = - (product as ProductOptionType)?.value || 0; + (product as OptionType)?.value || 0; formik.setFieldTouched( `items.${idx}.product`, diff --git a/src/components/pages/report/DailyMarketingsTable.tsx b/src/components/pages/report/DailyMarketingsTable.tsx index 2dba0309..d270d6d7 100644 --- a/src/components/pages/report/DailyMarketingsTable.tsx +++ b/src/components/pages/report/DailyMarketingsTable.tsx @@ -168,7 +168,7 @@ const DailyMarketingsTable = ({ ]; useEffect(() => { - console.log({ sorting }); + // console.log({ sorting }); if (sorting.length === 1) { onFilterByChange(sorting[0].id); diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index 5adcb694..9b1fd640 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -177,10 +177,12 @@ interface CustomerPaymentExportPDFParams { data: CustomerPaymentReport[]; params?: { customer_name?: string; - sales?: string; + // TODO: Uncomment when BE is ready + // sales?: string; start_date?: string; end_date?: string; - filter_by?: string; + // TODO: Uncomment when BE is ready + // filter_by?: string; }; } @@ -195,9 +197,10 @@ const getParameterText = ( paramsText.push('Semua Customer'); } - if (params?.sales) { - paramsText.push(`Sales: ${params.sales}`); - } + // TODO: Uncomment when BE is ready + // if (params?.sales) { + // paramsText.push(`Sales: ${params.sales}`); + // } if (params?.start_date && params?.end_date) { const startDate = formatDate(params.start_date, 'DD MMM YYYY'); @@ -242,9 +245,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { : '-'} - + {/* TODO: Uncomment when BE is ready */} + {/* Filter Tanggal: Tanggal DO - + */} Customer: {params.params?.customer_name || 'Semua Customer'} @@ -280,7 +284,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Aging - + Referensi @@ -298,15 +302,9 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { Harga Awal - - CN - Harga Akhir - - Pajak - Total @@ -343,13 +341,15 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - {item.do_date ? formatDate(item.do_date, 'DD MMM YY') : '-'} + {item.trans_date + ? formatDate(item.trans_date, 'DD MMM YY') + : '-'} - {item.realization_date - ? formatDate(item.realization_date, 'DD MMM YY') + {item.delivery_date + ? formatDate(item.delivery_date, 'DD MMM YY') : '-'} @@ -358,11 +358,15 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {item.aging_day ? formatNumber(item.aging_day) : '-'} hari - + {item.reference || '-'} - {item.vehicle_plate || '-'} + + {Array.isArray(item.vehicle_numbers) + ? item.vehicle_numbers.join(', ') + : item.vehicle_numbers || '-'} + {formatNumber(item.qty)} @@ -376,20 +380,14 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {formatCurrency(item.price)} - - {formatCurrency(item.credit_note)} - {formatCurrency(item.final_price)} - - {formatNumber(item.ppn)}% + + {formatCurrency(item.total_price)} - {formatCurrency(item.total)} - - - {formatCurrency(item.payment)} + {formatCurrency(item.payment_amount)} @@ -397,30 +395,32 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - {item.notes ? ( - {item.notes} - ) : ( + {item.status ? ( - {item.accounts_receivable === 0 - ? 'Lunas' - : 'Belum Lunas'} + {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'} + ) : ( + - )} - {item.pickup_info || '-'} + + {Array.isArray(item.pickup_info) + ? item.pickup_info.join(', ') + : item.pickup_info || '-'} + - {item.sales_marketing || '-'} + {item.sales_person || '-'} ))} @@ -440,7 +440,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - + @@ -458,25 +458,13 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - - {formatCurrency( - customerReport.summary.total_initial_amount - )} - - - - - {formatCurrency(customerReport.summary.total_credit_note)} - + {formatCurrency(customerReport.summary.total_final_amount)} - - - {formatCurrency(customerReport.summary.total_grand_amount)} diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index d51aa3b7..3fb21488 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -24,30 +24,30 @@ export const generateCustomerPaymentExcel = ( const excelData: { [key: string]: string | number }[] = customerData.map( (item, index) => ({ No: index + 1, - 'Tanggal DO/Bayar': item.do_date - ? formatDate(item.do_date, 'DD MMM YYYY') + 'Tanggal DO/Bayar': item.trans_date + ? formatDate(item.trans_date, 'DD MMM YYYY') : '', - 'Tanggal Realisasi': item.realization_date - ? formatDate(item.realization_date, 'DD MMM YYYY') + 'Tanggal Realisasi': item.delivery_date + ? formatDate(item.delivery_date, 'DD MMM YYYY') : '', Aging: formatNumber(item.aging_day || 0), Referensi: item.reference || '', - 'Nomor Polisi': Array.isArray(item.vehicle_plate) - ? item.vehicle_plate.join(', ') + 'Nomor Polisi': Array.isArray(item.vehicle_numbers) + ? item.vehicle_numbers.join(', ') : '', 'Ekor/Qty': formatNumber(item.qty || 0), 'Berat (Kg)': formatNumber(item.weight || 0), AVG: formatNumber(item.average_weight || 0), 'Harga Awal': formatCurrency(item.price || 0), - CN: formatCurrency(item.credit_note || 0), 'Harga Akhir': formatCurrency(item.final_price || 0), - 'PPN (%)': formatNumber(item.ppn || 0), - Total: formatCurrency(item.total || 0), - Pembayaran: formatCurrency(item.payment || 0), + Total: formatCurrency(item.total_price || 0), + Pembayaran: formatCurrency(item.payment_amount || 0), 'Saldo Piutang': formatCurrency(item.accounts_receivable || 0), - Keterangan: item.notes || '', - Pengambilan: item.pickup_info || '', - 'Sales/Marketing': item.sales_marketing || '', + Keterangan: item.status || '', + Pengambilan: Array.isArray(item.pickup_info) + ? item.pickup_info.join(', ') + : '', + 'Sales/Marketing': item.sales_person || '', }) ); @@ -62,14 +62,10 @@ export const generateCustomerPaymentExcel = ( 'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0), 'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0), AVG: '', - 'Harga Awal': formatCurrency( - customerReport.summary.total_initial_amount || 0 - ), - CN: formatCurrency(customerReport.summary.total_credit_note || 0), + 'Harga Awal': '', 'Harga Akhir': formatCurrency( customerReport.summary.total_final_amount || 0 ), - 'PPN (%)': '', Total: formatCurrency(customerReport.summary.total_grand_amount || 0), Pembayaran: formatCurrency(customerReport.summary.total_payment || 0), 'Saldo Piutang': formatCurrency( @@ -94,9 +90,7 @@ export const generateCustomerPaymentExcel = ( { wch: 12 }, // Berat { wch: 10 }, // AVG { wch: 15 }, // Harga Awal - { wch: 10 }, // CN { wch: 15 }, // Harga Akhir - { wch: 10 }, // PPN { wch: 15 }, // Total { wch: 15 }, // Pembayaran { wch: 15 }, // Saldo Piutang diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index adc5b375..ef748b5f 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -47,6 +47,8 @@ const CustomerPaymentTab = () => { const [filterCustomer, setFilterCustomer] = useState( [] ); + // TODO: Uncomment when BE is ready + // const [filterSales, setFilterSales] = useState([]); const [filterSales, setFilterSales] = useState([]); const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); @@ -61,6 +63,7 @@ const CustomerPaymentTab = () => { hasMore: hasMoreCustomers, } = useSelect(CustomerApi.basePath, 'id', 'name', 'search'); + // TODO: Uncomment when BE is ready const { options: salesOptions, setInputValue: setSalesInputValue, @@ -135,23 +138,18 @@ const CustomerPaymentTab = () => { count += 1; } - // Sales filter - if (filterSales.length > 0) { - count += 1; - } - - // Filter by (always count if submitted) - if (isSubmitted) { - count += 1; - } + // TODO: Uncomment when BE is ready + // // Sales filter + // if (filterSales.length > 0) { + // count += 1; + // } return count; }, [ filterStartDate, filterEndDate, filterCustomer, - filterSales, - isSubmitted, + // filterSales, ]); const hasFilters = activeFiltersCount > 0; @@ -165,11 +163,12 @@ const CustomerPaymentTab = () => { filterCustomer.length > 0 ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, - sales_id: - filterSales.length > 0 - ? filterSales.map((v) => String(v.value)).join(',') - : undefined, - filter_by: 'do_date' as const, + // TODO: Uncomment when BE is ready + // sales_id: + // filterSales.length > 0 + // ? filterSales.map((v) => String(v.value)).join(',') + // : undefined, + // filter_by: 'do_date' as const, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, page: currentPage, @@ -182,8 +181,8 @@ const CustomerPaymentTab = () => { ([, params]) => FinanceApi.getCustomerPaymentReport( params.customer_id, - params.sales_id, - params.filter_by, + undefined, // TODO: Change to params.sales_id when BE is ready + undefined, // TODO: Change to params.filter_by when BE is ready params.start_date, params.end_date, params.page, @@ -208,11 +207,11 @@ const CustomerPaymentTab = () => { filterCustomer.length > 0 ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, - sales_id: - filterSales.length > 0 - ? filterSales.map((v) => String(v.value)).join(',') - : undefined, - filter_by: 'do_date' as const, + // TODO: Uncomment when BE is ready + // sales_id: + // filterSales.length > 0 + // ? filterSales.map((v) => String(v.value)).join(',') + // : undefined, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, limit: 100, @@ -221,8 +220,8 @@ const CustomerPaymentTab = () => { const response = await FinanceApi.getCustomerPaymentReport( params.customer_id, - params.sales_id, - params.filter_by, + undefined, // TODO: Change to params.sales_id when BE is ready + undefined, // TODO: Change to params.filter_by when BE is ready params.start_date, params.end_date, params.page, @@ -279,13 +278,15 @@ const CustomerPaymentTab = () => { filterCustomer.length > 0 ? filterCustomer.map((c) => c.label).join(', ') : undefined, - sales: - filterSales.length > 0 - ? filterSales.map((s) => s.label).join(', ') - : undefined, + // TODO: Uncomment when BE is ready + // sales: + // filterSales.length > 0 + // ? filterSales.map((s) => s.label).join(', ') + // : undefined, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, - filter_by: 'do_date', + // TODO: Uncomment when BE is ready + // filter_by: 'do_date' as const, }, }); toast.success('PDF berhasil dibuat dan diunduh.'); @@ -303,36 +304,39 @@ const CustomerPaymentTab = () => { { id: 'no', header: 'No', - cell: (props) => props.row.index + 1, + cell: (props) => props.row.index, footer: () =>
Total
, }, { id: 'do_date_or_payment_date', - header: 'Tanggal DO/Bayar', - accessorKey: 'do_date', + header: 'Tanggal Jual/Bayar', + accessorKey: 'trans_date', + enableSorting: false, cell: (props) => { - const value = props.row.original.do_date; - return formatDate(value, 'DD MMM YYYY'); + const value = props.row.original.trans_date; + return value ? formatDate(value, 'DD MMM YYYY') : '-'; }, }, { id: 'realization_date', header: 'Tanggal Realisasi', - accessorKey: 'realization_date', + accessorKey: 'delivery_date', + enableSorting: false, cell: (props) => { - const value = props.row.original.realization_date; - return formatDate(value, 'DD MMM YYYY'); + const value = props.row.original.delivery_date; + return value ? formatDate(value, 'DD MMM YYYY') : '-'; }, }, { id: 'aging', header: 'Aging', accessorKey: 'aging_day', + enableSorting: false, cell: (props) => { const value = props.row.original.aging_day; return (
- {value ? formatNumber(value) : '-'} hari + {value && value > 0 ? `${formatNumber(value)} hari` : '-'}
); }, @@ -341,6 +345,7 @@ const CustomerPaymentTab = () => { id: 'reference', header: 'Referensi', accessorKey: 'reference', + enableSorting: false, cell: (props) => { const value = props.row.original.reference; return value || '-'; @@ -349,16 +354,18 @@ const CustomerPaymentTab = () => { { id: 'vehicle_plate', header: 'Nomor Polisi', - accessorKey: 'vehicle_plate', + accessorKey: 'vehicle_numbers', + enableSorting: false, cell: (props) => { - const value = props.row.original.vehicle_plate; - return value || '-'; + const value = props.row.original.vehicle_numbers; + return Array.isArray(value) ? value.join(', ') : value || '-'; }, }, { id: 'qty', - header: 'Ekor/Qty', + header: 'Qty', accessorKey: 'qty', + enableSorting: false, cell: (props) => { const value = props.row.original.qty; return
{formatNumber(value)}
; @@ -373,6 +380,7 @@ const CustomerPaymentTab = () => { id: 'weight', header: 'Berat (Kg)', accessorKey: 'weight', + enableSorting: false, cell: (props) => { const value = props.row.original.weight; return
{formatNumber(value)}
; @@ -387,6 +395,7 @@ const CustomerPaymentTab = () => { id: 'average_weight', header: 'AVG', accessorKey: 'average_weight', + enableSorting: false, cell: (props) => { const value = props.row.original.average_weight; return
{formatNumber(value)}
; @@ -399,34 +408,20 @@ const CustomerPaymentTab = () => { id: 'price', header: 'Harga Awal', accessorKey: 'price', + enableSorting: false, cell: (props) => { const value = props.row.original.price; return
{formatCurrency(value)}
; }, footer: () => ( -
- {formatCurrency(summary.total_initial_amount) || '-'} -
- ), - }, - { - id: 'credit_note', - header: 'CN', - accessorKey: 'credit_note', - cell: (props) => { - const value = props.row.original.credit_note; - return
{formatCurrency(value)}
; - }, - footer: () => ( -
- {formatCurrency(summary.total_credit_note) || '-'} -
+
-
), }, { id: 'final_price', header: 'Harga Akhir', accessorKey: 'final_price', + enableSorting: false, cell: (props) => { const value = props.row.original.final_price; return
{formatCurrency(value)}
; @@ -437,24 +432,13 @@ const CustomerPaymentTab = () => {
), }, - { - id: 'ppn', - header: 'PPN (%)', - accessorKey: 'ppn', - cell: (props) => { - const value = props.row.original.ppn; - return
{formatNumber(value)}%
; - }, - footer: () => ( -
-
- ), - }, { id: 'total', header: 'Total', - accessorKey: 'total', + accessorKey: 'total_price', + enableSorting: false, cell: (props) => { - const value = props.row.original.total; + const value = props.row.original.total_price; return
{formatCurrency(value)}
; }, footer: () => ( @@ -466,9 +450,10 @@ const CustomerPaymentTab = () => { { id: 'payment', header: 'Pembayaran', - accessorKey: 'payment', + accessorKey: 'payment_amount', + enableSorting: false, cell: (props) => { - const value = props.row.original.payment; + const value = props.row.original.payment_amount; return
{formatCurrency(value)}
; }, footer: () => ( @@ -481,14 +466,25 @@ const CustomerPaymentTab = () => { id: 'accounts_receivable', header: 'Saldo Piutang', accessorKey: 'accounts_receivable', + enableSorting: false, cell: (props) => { const value = props.row.original.accounts_receivable; return ( -
{formatCurrency(value)}
+
+ {formatCurrency(value)} +
); }, footer: () => ( -
+
{formatCurrency(summary.total_accounts_receivable) || '-'}
), @@ -496,9 +492,10 @@ const CustomerPaymentTab = () => { { id: 'notes', header: 'Keterangan', - accessorKey: 'notes', + accessorKey: 'status', + enableSorting: false, cell: (props) => { - const value = props.row.original.notes; + const value = props.row.original.status; if (!value) { return '-'; @@ -513,7 +510,7 @@ const CustomerPaymentTab = () => { status: getPaymentStatusIndicatorColor(value), }} > - {getPaymentStatusText(value)} + {getPaymentStatusText(value)} ); }, @@ -522,17 +519,19 @@ const CustomerPaymentTab = () => { id: 'pickup_info', header: 'Pengambilan', accessorKey: 'pickup_info', + enableSorting: false, cell: (props) => { const value = props.row.original.pickup_info; - return value || '-'; + return Array.isArray(value) ? value.join(', ') : value || '-'; }, }, { id: 'sales_marketing', header: 'Sales/Marketing', - accessorKey: 'sales_marketing', + accessorKey: 'sales_person', + enableSorting: false, cell: (props) => { - const value = props.row.original.sales_marketing; + const value = props.row.original.sales_person; return value || '-'; }, }, @@ -664,7 +663,8 @@ const CustomerPaymentTab = () => { />
-
+ {/* TODO: Uncomment when BE is ready */} + {/*
{ onMenuScrollToBottom={loadMoreSales} className={{ wrapper: 'w-full' }} /> -
+
*/} -
+ {/* TODO: Uncomment when BE is ready */} + {/*
{ isDisabled={true} className={{ wrapper: 'w-full' }} /> -
+
*/}
{/* Action Buttons */} @@ -730,10 +731,7 @@ const CustomerPaymentTab = () => { const summary = customerReport.summary || { total_qty: 0, total_weight: 0, - total_initial_amount: 0, - total_credit_note: 0, total_final_amount: 0, - total_ppn: 0, total_grand_amount: 0, total_payment: 0, total_accounts_receivable: 0, @@ -745,19 +743,27 @@ const CustomerPaymentTab = () => { 0} className={{ containerClassName: 'w-full', @@ -777,6 +783,36 @@ const CustomerPaymentTab = () => { 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', paginationClassName: 'hidden', }} + renderCustomRow={(row) => { + if (row.index === 0) { + return ( + + + + + + ); + } + }} /> ); diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx index 0a712a6c..95f42df6 100644 --- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx +++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx @@ -226,7 +226,7 @@ const createPDFDocument = ( Rentang BW - Sisa Ekor + Sisa Butir Sisa Kg @@ -234,12 +234,6 @@ const createPDFDocument = ( Rata-Rata Bobot (Kg) - - Produksi Telur (Butir) - - - Produksi Telur (Kg) - Feed (Supplier) @@ -249,12 +243,6 @@ const createPDFDocument = ( Rata-Rata Harga DOC - - Nilai Nominal Telur - - - HPP Ayam - HPP Telur (RP/KG) @@ -278,23 +266,15 @@ const createPDFDocument = ( {group.label} - - {formatNumber(group.remaining_chicken_birds)} - - - - {formatNumber(group.remaining_chicken_weight_kg)} - - - - {formatNumber(group.avg_weight_kg)} - {formatNumber(group.egg_production_pieces)} {formatNumber(group.egg_production_kg)} + + {formatNumber(group.avg_weight_kg)} + {group.feed_suppliers @@ -318,17 +298,11 @@ const createPDFDocument = ( {formatCurrency(group.average_doc_price_rp)} - - {formatCurrency(group.egg_value_rp)} - - - {formatCurrency(group.hpp_rp)} - {formatCurrency(group.egg_hpp_rp_per_kg)} - {formatCurrency(group.remaining_value_rp)} + {formatCurrency(group.egg_value_rp)} ) @@ -356,16 +330,10 @@ const createPDFDocument = ( Rata-Rata Bobot (Kg) - Sisa Ekor + Sisa Butir - Sisa Kg (Ayam) - - - Produksi Telur (Butir) - - - Produksi Telur (Kg) + Sisa Kg (Telur) Feed (Supplier) @@ -376,12 +344,6 @@ const createPDFDocument = ( Rata-Rata Harga DOC - - Nilai Nominal Telur - - - HPP Ayam - HPP Telur (RP/KG) @@ -416,12 +378,6 @@ const createPDFDocument = ( {formatNumber(item.avg_weight_kg)} - - {formatNumber(item.remaining_chicken_birds)} - - - {formatNumber(item.remaining_chicken_weight_kg)} - {formatNumber(item.egg_production_pieces)} @@ -451,17 +407,11 @@ const createPDFDocument = ( {formatCurrency(item.average_doc_price_rp)} - - {formatCurrency(item.egg_value_rp)} - - - {formatCurrency(item.hpp_rp)} - {formatCurrency(item.egg_hpp_rp_per_kg)} - {formatCurrency(item.remaining_value_rp)} + {formatCurrency(item.egg_value_rp)} ))} diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index eda88d8c..22d80220 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -335,10 +335,8 @@ const HppPerKandangTab = () => { ? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}` : '', 'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0, - 'Sisa Ayam (Ekor)': item.remaining_chicken_birds || 0, - 'Sisa Ayam (KG)': item.remaining_chicken_weight_kg || 0, - 'Produksi Telur (Butir)': item.egg_production_pieces || 0, - 'Produksi Telur (KG)': item.egg_production_kg || 0, + 'Sisa Telur (Butir)': item.egg_production_pieces || 0, + 'Sisa Telur (KG)': item.egg_production_kg || 0, 'Feed (Supplier)': item.feed_suppliers ?.map((s: { alias?: string; name: string }) => s.alias || s.name) @@ -348,10 +346,8 @@ const HppPerKandangTab = () => { ?.map((s: { alias?: string; name: string }) => s.alias || s.name) .join(' | ') || '', 'Rata-Rata Harga DOC (RP)': item.average_doc_price_rp || 0, - 'Nilai Nominal Telur (RP)': item.egg_value_rp || 0, - 'HPP Ayam (RP)': item.hpp_rp || 0, 'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0, - 'Nilai Nominal Sisa Ayam (RP)': item.remaining_value_rp || 0, + 'Nilai Nominal Sisa Telur (RP)': item.egg_value_rp || 0, }) ); @@ -360,20 +356,14 @@ const HppPerKandangTab = () => { Kandang: 'ALL', 'Rentang Bobot': '-', 'Rata-Rata Bobot (KG)': summaryTotal?.average_weight_kg || 0, - 'Sisa Ayam (Ekor)': summaryTotal?.total_remaining_chicken_birds || 0, - 'Sisa Ayam (KG)': summaryTotal?.total_remaining_chicken_weight_kg || 0, - 'Produksi Telur (Butir)': - summaryTotal?.total_egg_production_pieces || 0, - 'Produksi Telur (KG)': summaryTotal?.total_egg_production_kg || 0, + 'Sisa Telur (Butir)': summaryTotal?.total_egg_production_pieces || 0, + 'Sisa Telur (KG)': summaryTotal?.total_egg_production_kg || 0, 'Feed (Supplier)': allFeedSuppliers, 'DOC (Supplier)': allDocSuppliers, 'Rata-Rata Harga DOC (RP)': summaryTotal?.total_average_doc_price_rp || 0, - 'Nilai Nominal Telur (RP)': summaryTotal?.total_egg_value_rp || 0, - 'HPP Ayam (RP)': summaryTotal?.total_hpp_rp || 0, 'HPP Telur (RP/KG)': summaryTotal?.average_egg_hpp_rp_per_kg || 0, - 'Nilai Nominal Sisa Ayam (RP)': - summaryTotal?.total_remaining_value_rp || 0, + 'Nilai Nominal Sisa Telur (RP)': summaryTotal?.total_egg_value_rp || 0, }); const worksheet = XLSX.utils.json_to_sheet(excelData); @@ -383,17 +373,13 @@ const HppPerKandangTab = () => { { wch: 30 }, // Kandang { wch: 15 }, // Rentang Bobot { wch: 18 }, // Rata-Rata Bobot (KG) - { wch: 15 }, // Sisa Ayam (Ekor) - { wch: 15 }, // Sisa Ayam (KG) - { wch: 18 }, // Produksi Telur (Butir) - { wch: 18 }, // Produksi Telur (KG) + { wch: 15 }, // Sisa Telur (Butir) + { wch: 15 }, // Sisa Telur (KG) { wch: 20 }, // Feed (Supplier) { wch: 20 }, // DOC (Supplier) { wch: 20 }, // Rata-Rata Harga DOC (RP) - { wch: 20 }, // Nilai Nominal Telur (RP) - { wch: 15 }, // HPP Ayam (RP) { wch: 18 }, // HPP Telur (RP/KG) - { wch: 25 }, // Nilai Nominal Sisa Ayam (RP) + { wch: 25 }, // Nilai Nominal Sisa Telur (RP) ]; worksheet['!cols'] = colWidths; @@ -533,37 +519,9 @@ const HppPerKandangTab = () => { ), }, - { - id: 'remaining_chicken_birds', - header: 'Sisa Ayam (Ekor)', - accessorKey: 'remaining_chicken_birds', - cell: (props) => { - const value = props.row.original.remaining_chicken_birds; - return
{formatNumber(value)}
; - }, - footer: () => ( -
- {formatNumber(summaryTotal?.total_remaining_chicken_birds || 0)} -
- ), - }, - { - id: 'remaining_chicken_weight_kg', - header: 'Sisa Ayam (KG)', - accessorKey: 'remaining_chicken_weight_kg', - cell: (props) => { - const value = props.row.original.remaining_chicken_weight_kg; - return
{formatNumber(value)}
; - }, - footer: () => ( -
- {formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)} -
- ), - }, { id: 'egg_production_pieces', - header: 'Produksi Telur (Butir)', + header: 'Sisa Telur (Butir)', accessorKey: 'egg_production_pieces', cell: (props) => { const value = props.row.original.egg_production_pieces; @@ -577,7 +535,7 @@ const HppPerKandangTab = () => { }, { id: 'egg_production_kg', - header: 'Produksi Telur (KG)', + header: 'Sisa Telur (KG)', accessorKey: 'egg_production_kg', cell: (props) => { const value = props.row.original.egg_production_kg; @@ -585,7 +543,7 @@ const HppPerKandangTab = () => { }, footer: () => (
- {formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)} + {formatNumber(summaryTotal?.total_egg_production_kg || 0)}
), }, @@ -639,34 +597,6 @@ const HppPerKandangTab = () => { ), }, - { - id: 'egg_value_rp', - header: 'Nilai Nominal Telur (RP)', - accessorKey: 'egg_value_rp', - cell: (props) => { - const value = props.row.original.egg_value_rp; - return
{formatCurrency(value)}
; - }, - footer: () => ( -
- {formatCurrency(summaryTotal?.total_egg_value_rp || 0)} -
- ), - }, - { - id: 'hpp_rp', - header: 'HPP Ayam (RP)', - accessorKey: 'hpp_rp', - cell: (props) => { - const value = props.row.original.hpp_rp; - return
{formatCurrency(value)}
; - }, - footer: () => ( -
- {formatCurrency(summaryTotal?.total_hpp_rp || 0)} -
- ), - }, { id: 'egg_hpp_rp_per_kg', header: 'HPP Telur (RP/KG)', @@ -682,16 +612,16 @@ const HppPerKandangTab = () => { ), }, { - id: 'remaining_value_rp', - header: 'Nilai Nominal Sisa Ayam (RP)', - accessorKey: 'remaining_value_rp', + id: 'egg_value_rp', + header: 'Nilai Nominal Sisa Telur (RP)', + accessorKey: 'egg_value_rp', cell: (props) => { - const value = props.row.original.remaining_value_rp; + const value = props.row.original.egg_value_rp; return
{formatCurrency(value)}
; }, footer: () => (
- {formatCurrency(summaryTotal?.total_remaining_value_rp || 0)} + {formatCurrency(summaryTotal?.total_egg_value_rp || 0)}
), }, @@ -725,7 +655,7 @@ const HppPerKandangTab = () => { key={'rekapitulasi-row'} > - - @@ -772,15 +696,11 @@ const HppPerKandangTab = () => { - - ); diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts index 165bc8ee..44f3728e 100644 --- a/src/config/route-permission.ts +++ b/src/config/route-permission.ts @@ -121,6 +121,7 @@ export const ROUTE_PERMISSIONS: Record = { '/report/finance/': [ 'lti.repport.finance.list', 'lti.repport.debtsupplier.list', + 'lti.repport.customerpayment.list', ], // Inventory diff --git a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx index 7bd0be83..314381fd 100644 --- a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx +++ b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx @@ -601,15 +601,15 @@ export function DailyChecklistContent() { ) => { const taskId = taskIdsByPhaseActivityId[activityId]; - console.log('[CHECKBOX] Click detected:', { - activityId, - employeeId, - checked, - taskId, - hasTaskId: !!taskId, - checklistStatus, - isEditable, - }); + // console.log('[CHECKBOX] Click detected:', { + // activityId, + // employeeId, + // checked, + // taskId, + // hasTaskId: !!taskId, + // checklistStatus, + // isEditable, + // }); if (!taskId) { console.error('[CHECKBOX] No taskId found for activityId:', activityId); @@ -638,10 +638,10 @@ export function DailyChecklistContent() { }, }, }; - console.log( - '[CHECKBOX] State updated optimistically:', - updated[taskId]?.[employeeId] - ); + // console.log( + // '[CHECKBOX] State updated optimistically:', + // updated[taskId]?.[employeeId] + // ); return updated; }); @@ -653,7 +653,7 @@ export function DailyChecklistContent() { note: assignments[taskId]?.[employeeId]?.note || null, }; - console.log('[CHECKBOX] Saving to database:', payload); + // console.log('[CHECKBOX] Saving to database:', payload); const checkOrUncheckAssignmentRes = await DailyChecklistApi.checkOrUncheckAssignment(payload); @@ -679,7 +679,7 @@ export function DailyChecklistContent() { return; } - console.log('[CHECKBOX] Saved successfully'); + // console.log('[CHECKBOX] Saved successfully'); }; const handleNoteChange = async ( diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 9fa4f37c..81f23481 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -1,7 +1,6 @@ import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; -import { DebtSupplier } from '@/types/api/report/debt-supplier'; export class FinanceApiService extends BaseApiService< CustomerPaymentReport, @@ -14,8 +13,11 @@ export class FinanceApiService extends BaseApiService< async getCustomerPaymentReport( customer_id?: string, + // TODO: Uncomment when BE is ready + // sales_id?: string, + // filter_by?: 'do_date', sales_id?: string, - filter_by?: 'do_date', + filter_by?: 'do_date' | undefined, start_date?: string, end_date?: string, page?: number, @@ -27,8 +29,9 @@ export class FinanceApiService extends BaseApiService< method: 'GET', params: { customer_id: customer_id, - sales_id: sales_id, - filter_by: filter_by, + // TODO: Uncomment when BE is ready + // sales_id: sales_id, + // filter_by: filter_by, start_date: start_date, end_date: end_date, page: page, @@ -39,8 +42,8 @@ export class FinanceApiService extends BaseApiService< } } -// export const FinanceApi = new FinanceApiService('reports'); +export const FinanceApi = new FinanceApiService('reports'); -export const FinanceApi = new FinanceApiService( - 'http://localhost:4010/api/reports/finance' -); +// export const FinanceApi = new FinanceApiService( +// 'http://localhost:4010/api/reports/finance' +// ); 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; diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 66cc39ed..dcc1a348 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -6,6 +6,7 @@ import { Location } from '@/types/api/master-data/location'; import { BaseApproval, BaseMetadata } from '@/types/api/api-general'; import { Nonstock } from '@/types/api/master-data/nonstock'; import { ProductionStandard } from '@/types/api/master-data/production-standard'; +import { Warehouse } from '@/types/api/master-data/warehouse'; export type BaseProjectFlock = { id: number; @@ -71,6 +72,7 @@ export type ProjectFlockKandangLookup = { kandang_id: number; kandang: Kandang; project_flock: ProjectFlock; + warehouse: Warehouse; quantity: number; available_quantity?: number; population: number; diff --git a/src/types/api/purchase/purchase.d.ts b/src/types/api/purchase/purchase.d.ts index 34798ac3..9ad59f8b 100644 --- a/src/types/api/purchase/purchase.d.ts +++ b/src/types/api/purchase/purchase.d.ts @@ -120,12 +120,12 @@ export type CreateAcceptApprovalRequestPayload = { purchase_item_id: number; received_date: string; travel_number: string; - vehicle_number: string; - expedition_vendor_id: number; + vehicle_number?: string | null; + expedition_vendor_id?: number | null; received_qty: number; - transport_per_item: number; + transport_per_item?: number | null; }[]; - travel_documents?: File[]; + travel_documents?: File[] | null; }; export type DeletePurchaseRequestItemPayload = { diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts index bfa059c9..9169c99b 100644 --- a/src/types/api/report/customer-payment.d.ts +++ b/src/types/api/report/customer-payment.d.ts @@ -2,34 +2,30 @@ import { BaseCustomer } from '@/types/api/master-data/customer'; import { BaseMetadata } from '@/types/api/api-general'; export type CustomerPaymentRow = { - id: number; - do_date: string; - realization_date: string; - aging_day: number | null; + transaction_type: string; + transaction_id: number; + trans_date: string; + delivery_date: string | null; reference: string; - vehicle_plate: string[]; + vehicle_numbers: string[]; qty: number; weight: number; average_weight: number; price: number; - credit_note: number; final_price: number; - ppn: number; - total: number; - payment: number; + total_price: number; + payment_amount: number; accounts_receivable: number; - notes: string; - pickup_info: string; - sales_marketing: string; + aging_day: number | null; + status: string; + pickup_info: string[]; + sales_person: string; }; export type CustomerPaymentSummary = { total_qty: number; total_weight: number; - total_initial_amount: number; - total_credit_note: number; total_final_amount: number; - total_ppn: number; total_grand_amount: number; total_payment: number; total_accounts_receivable: number; @@ -37,6 +33,7 @@ export type CustomerPaymentSummary = { export type CustomerPaymentReport = BaseMetadata & { customer: BaseCustomer; + initial_balance: number; rows: CustomerPaymentRow[]; summary: CustomerPaymentSummary; }; diff --git a/src/types/api/report/hpp-per-kandang.d.ts b/src/types/api/report/hpp-per-kandang.d.ts index 824a3837..208a2cdb 100644 --- a/src/types/api/report/hpp-per-kandang.d.ts +++ b/src/types/api/report/hpp-per-kandang.d.ts @@ -9,30 +9,22 @@ export type HppPerKandangRow = { weight_min: number; weight_max: number; }; - remaining_chicken_birds: number; - remaining_chicken_weight_kg: number; avg_weight_kg: number; egg_production_pieces: number; egg_production_kg: number; egg_hpp_rp_per_kg: number; egg_value_rp: number; - feed_suppliers: Supplier[]; - doc_suppliers: Supplier[]; + feed_suppliers: Supplier[] | null; + doc_suppliers: Supplier[] | null; average_doc_price_rp: number; - hpp_rp: number; - remaining_value_rp: number; }; export type HppPerKandangSummaryTotal = { - total_remaining_chicken_birds: number; - total_remaining_chicken_weight_kg: number; average_weight_kg: number; - total_remaining_value_rp: number; total_egg_production_pieces: number; total_egg_production_kg: number; average_egg_hpp_rp_per_kg: number; total_egg_value_rp: number; - total_hpp_rp: number; total_average_doc_price_rp: number; }; @@ -43,8 +35,6 @@ export type HppPerKandangPerWeightRange = { weight_max: number; }; label: string; - remaining_chicken_birds: number; - remaining_chicken_weight_kg: number; avg_weight_kg: number; egg_production_pieces: number; egg_production_kg: number; @@ -53,8 +43,6 @@ export type HppPerKandangPerWeightRange = { feed_suppliers: Supplier[]; doc_suppliers: Supplier[]; average_doc_price_rp: number; - hpp_rp: number; - remaining_value_rp: number; }; export type HppPerKandangSummary = {
+
+ {formatCurrency(row.original.accounts_receivable)} +
+
Rekapitulasi per rentang bobot @@ -747,12 +677,6 @@ const HppPerKandangTab = () => { {formatNumber(item.avg_weight_kg)} - {formatNumber(item.remaining_chicken_birds)} - - {formatNumber(item.remaining_chicken_weight_kg)} - {formatNumber(item.egg_production_pieces)} {formatCurrency(item.average_doc_price_rp)} - {formatCurrency(item.egg_value_rp)} - {formatCurrency(item.hpp_rp)} {formatCurrency(item.egg_hpp_rp_per_kg)} - {formatCurrency(item.remaining_value_rp)} + {formatCurrency(item.egg_value_rp)}