Merge branch 'development' into 'staging'

refactor(FE): Improve vehicle number validation message and set

See merge request mbugroup/lti-web-client!196
This commit is contained in:
Adnan Zahir
2026-01-17 09:05:42 +07:00
50 changed files with 1972 additions and 1458 deletions
-2
View File
@@ -24,8 +24,6 @@ const FinanceDetailPage = () => {
); );
} }
console.log(finance);
// if (!finance || isResponseError(finance)) { // if (!finance || isResponseError(finance)) {
// router.replace('/404'); // router.replace('/404');
// return; // return;
@@ -3,54 +3,11 @@ import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { formatCurrency, formatTitleCase } from '@/lib/helper'; import { formatCurrency, formatTitleCase } from '@/lib/helper';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
import { import { HppItem, ProfitLossItem } from '@/types/api/closing';
DataSummarySubTotal,
HppPurchaseData,
ProfitLossDataAmount,
} from '@/types/api/closing';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useMemo } from 'react';
import useSWR from 'swr'; 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 = ({ const ClosingFinanceTable = ({
projectFlockId, projectFlockId,
}: { }: {
@@ -68,167 +25,60 @@ const ClosingFinanceTable = ({
) )
); );
const staticHppRows: Array<{ const hppTableData: HppItem[] = useMemo(() => {
group_name: string; if (isResponseSuccess(finance)) {
type: string; const customItems = {
group_index: number; label: 'HPP dan Pengeluaran',
}> = [ code: 'custom_row',
{ } as HppItem;
group_name: 'HPP dan Pengeluaran', const purchases = finance.data.hpp.items.filter(
type: 'Pembelian PAKAN', (item) => item.category === 'purchase'
group_index: 0, );
}, const totalBudgeting = {
{ label: 'HPP dan Bahan Baku',
group_name: 'HPP dan Pengeluaran', code: 'custom_row',
type: 'Pembelian STARTER', } as HppItem;
group_index: 0, const overheads = finance.data.hpp.items.filter(
}, (item) => item.category === 'overhead'
{ );
group_name: 'HPP dan Pengeluaran', return [customItems, ...purchases, totalBudgeting, ...overheads];
type: 'Pembelian DOC', }
group_index: 0, return [];
}, }, [finance]);
{
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: HppTableRow[] = [ const profitLossTableData: ProfitLossItem[] = useMemo(() => {
{ if (isResponseSuccess(finance)) {
group_name: 'HPP dan Pengeluaran', const incomes = finance.data.profit_loss.items.filter(
group_index: 0, (item) => item.type === 'income'
isGroupHeader: true as const, );
}, const purchases = finance.data.profit_loss.items.filter(
...staticHppRows (item) => item.type === 'purchase'
.filter((row) => row.group_index === 0) );
.map((staticRow) => { const overheads = finance.data.profit_loss.items.filter(
const apiData = isResponseSuccess(finance) (item) => item.type === 'overhead'
? finance.data.hpp_purchases.hpp );
.find((g) => g.group_name === staticRow.group_name) const grossProfit = {
?.data.find((d) => d.type === staticRow.type) label: 'LABA RUGI BRUTO',
: null; code: 'custom_row',
type: 'gross_profit',
return { rp_per_bird:
group_name: staticRow.group_name, finance.data.profit_loss.summary.gross_profit.rp_per_bird ?? 0,
group_index: staticRow.group_index, rp_per_kg: finance.data.profit_loss.summary.gross_profit.rp_per_kg ?? 0,
type: staticRow.type, amount: finance.data.profit_loss.summary.gross_profit.amount ?? 0,
budgeting: apiData?.budgeting || { } as ProfitLossItem;
rp_per_bird: 0, const subtotal = {
rp_per_kg: 0, label: 'Subtotal',
amount: 0, code: 'custom_row',
}, type: 'subtotal',
realization: apiData?.realization || { rp_per_bird:
rp_per_bird: 0, finance.data.profit_loss.summary.sub_total.rp_per_bird ?? 0,
rp_per_kg: 0, rp_per_kg: finance.data.profit_loss.summary.sub_total.rp_per_kg ?? 0,
amount: 0, amount: finance.data.profit_loss.summary.sub_total.amount ?? 0,
}, } as ProfitLossItem;
isGroupHeader: false as const, return [...incomes, ...purchases, grossProfit, ...overheads, subtotal];
}; }
}), return [];
{ }, [finance]);
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,
},
]
: [];
return ( return (
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
@@ -241,35 +91,21 @@ const ClosingFinanceTable = ({
> >
<div className='grid grid-cols-2 gap-6'> <div className='grid grid-cols-2 gap-6'>
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
<div> <div>Laba Rugi Brutto</div>
{isResponseSuccess(finance)
? formatTitleCase(
finance.data.profit_loss.data.summary.gross_profit
.label || '-'
)
: 'Laba Rugi Brutto'}
</div>
<div className='text-lg font-bold'> <div className='text-lg font-bold'>
{isResponseSuccess(finance) {isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.profit_loss.data.summary.gross_profit.amount finance.data.profit_loss.summary.gross_profit.amount
) )
: '-'} : '-'}
</div> </div>
</div> </div>
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
<div> <div>Laba Rugi Netto</div>
{isResponseSuccess(finance)
? formatTitleCase(
finance.data.profit_loss.data.summary.net_profit.label ||
'-'
)
: 'Laba Rugi Netto'}
</div>
<div className='text-lg font-bold'> <div className='text-lg font-bold'>
{isResponseSuccess(finance) {isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.profit_loss.data.summary.net_profit.amount finance.data.profit_loss.summary.net_profit.amount
) )
: '-'} : '-'}
</div> </div>
@@ -277,11 +113,7 @@ const ClosingFinanceTable = ({
</div> </div>
</Card> </Card>
<Card <Card
title={ title='HPP Purchases'
isResponseSuccess(finance)
? finance.data.hpp_purchases.title
: 'HPP Purchases'
}
variant='bordered' variant='bordered'
collapsible collapsible
className={{ className={{
@@ -289,7 +121,7 @@ const ClosingFinanceTable = ({
}} }}
> >
<div className='mt-6 p-0 mb-0'> <div className='mt-6 p-0 mb-0'>
<Table<HppTableRow> <Table<HppItem>
data={hppTableData} data={hppTableData}
isLoading={isLoading} isLoading={isLoading}
columns={[ columns={[
@@ -297,10 +129,10 @@ const ClosingFinanceTable = ({
header: 'No.', header: 'No.',
enableSorting: false, enableSorting: false,
accessorFn: (item, index) => { accessorFn: (item, index) => {
if (item.isGroupHeader) return '-'; if (item.code === 'custom_row') return '-';
const dataRowsBefore = hppTableData const dataRowsBefore = hppTableData
.slice(0, index) .slice(0, index)
.filter((row) => !row.isGroupHeader).length; .filter((row) => row.code !== 'custom_row').length;
return dataRowsBefore + 1; return dataRowsBefore + 1;
}, },
footer: (props) => { footer: (props) => {
@@ -310,7 +142,7 @@ const ClosingFinanceTable = ({
{ {
header: 'Jenis', header: 'Jenis',
enableSorting: false, enableSorting: false,
accessorFn: (item) => formatTitleCase(item.type || '-'), accessorFn: (item) => formatTitleCase(item.label || '-'),
}, },
{ {
header: 'Budgeting', header: 'Budgeting',
@@ -326,7 +158,7 @@ const ClosingFinanceTable = ({
return props.column.id === 'budgeting_rp_per_bird' && return props.column.id === 'budgeting_rp_per_bird' &&
isResponseSuccess(finance) isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.hpp_purchases.summary_hpp?.budgeting finance.data.hpp.summary?.budgeting
?.rp_per_bird || 0 ?.rp_per_bird || 0
) )
: '-'; : '-';
@@ -342,8 +174,8 @@ const ClosingFinanceTable = ({
return props.column.id === 'budgeting_rp_per_kg' && return props.column.id === 'budgeting_rp_per_kg' &&
isResponseSuccess(finance) isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.hpp_purchases.summary_hpp?.budgeting finance.data.hpp.summary?.budgeting?.rp_per_kg ||
?.rp_per_kg || 0 0
) )
: '-'; : '-';
}, },
@@ -358,8 +190,7 @@ const ClosingFinanceTable = ({
return props.column.id === 'budgeting_amount' && return props.column.id === 'budgeting_amount' &&
isResponseSuccess(finance) isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.hpp_purchases.summary_hpp?.budgeting finance.data.hpp.summary?.budgeting?.amount || 0
?.amount || 0
) )
: '-'; : '-';
}, },
@@ -380,8 +211,8 @@ const ClosingFinanceTable = ({
return props.column.id === 'realization_rp_per_bird' && return props.column.id === 'realization_rp_per_bird' &&
isResponseSuccess(finance) isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.hpp_purchases.summary_hpp finance.data.hpp.summary?.realization
?.realization?.rp_per_bird || 0 ?.rp_per_bird || 0
) )
: '-'; : '-';
}, },
@@ -396,8 +227,8 @@ const ClosingFinanceTable = ({
return props.column.id === 'realization_rp_per_kg' && return props.column.id === 'realization_rp_per_kg' &&
isResponseSuccess(finance) isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.hpp_purchases.summary_hpp finance.data.hpp.summary?.realization
?.realization?.rp_per_kg || 0 ?.rp_per_kg || 0
) )
: '-'; : '-';
}, },
@@ -412,8 +243,7 @@ const ClosingFinanceTable = ({
return props.column.id === 'realization_amount' && return props.column.id === 'realization_amount' &&
isResponseSuccess(finance) isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.hpp_purchases.summary_hpp finance.data.hpp.summary?.realization?.amount || 0
?.realization?.amount || 0
) )
: '-'; : '-';
}, },
@@ -423,7 +253,7 @@ const ClosingFinanceTable = ({
]} ]}
renderCustomRow={(row) => { renderCustomRow={(row) => {
const rowData = row.original; const rowData = row.original;
if (rowData.isGroupHeader) { if (rowData.code === 'custom_row') {
return ( return (
<tr <tr
key={row.id} key={row.id}
@@ -437,7 +267,7 @@ const ClosingFinanceTable = ({
className={TABLE_DEFAULT_STYLING.bodyColumnClassName} className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
> >
<div className='font-bold'> <div className='font-bold'>
{formatTitleCase(rowData.group_name ?? '-')} {formatTitleCase(rowData.label ?? '-')}
</div> </div>
</td> </td>
</tr> </tr>
@@ -450,11 +280,7 @@ const ClosingFinanceTable = ({
</div> </div>
</Card> </Card>
<Card <Card
title={ title='Profit/Loss'
isResponseSuccess(finance)
? finance.data.profit_loss.title
: 'Profit/Loss'
}
variant='bordered' variant='bordered'
collapsible collapsible
className={{ className={{
@@ -462,39 +288,32 @@ const ClosingFinanceTable = ({
}} }}
> >
<div className='mt-6 p-0 mb-0'> <div className='mt-6 p-0 mb-0'>
<Table<ProfitLossTableRow> <Table<ProfitLossItem>
data={profitLossTableData} data={profitLossTableData}
isLoading={isLoading} isLoading={isLoading}
columns={[ columns={[
{ {
header: 'Jenis', header: 'Jenis',
enableSorting: false, enableSorting: false,
accessorFn: (item) => item.type, accessorFn: (item) => item.label,
cell: (item) => ( cell: (item) => (
<div className=''> <div className=''>
{formatTitleCase(item.row.original.type || '-')} {formatTitleCase(item.row.original.label || '-')}
</div> </div>
), ),
footer: (item) => ( footer: () => (
<div className='font-bold uppercase'> <div className='font-bold uppercase'>LABA RUGI NETTO</div>
{isResponseSuccess(finance)
? formatTitleCase(
finance.data.profit_loss.data.summary.net_profit
.label || '-'
)
: '-'}
</div>
), ),
}, },
{ {
header: 'Rp/Ekor', header: 'Rp/Ekor',
enableSorting: false, enableSorting: false,
accessorFn: (item) => formatCurrency(item.rp_per_bird || 0), accessorFn: (item) => formatCurrency(item.rp_per_bird || 0),
footer: (item) => ( footer: () => (
<div className='font-bold'> <div className='font-bold'>
{isResponseSuccess(finance) {isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.profit_loss.data.summary.net_profit finance.data.profit_loss.summary.net_profit
.rp_per_bird || 0 .rp_per_bird || 0
) )
: formatCurrency(0)} : formatCurrency(0)}
@@ -505,11 +324,11 @@ const ClosingFinanceTable = ({
header: 'Rp/Kg', header: 'Rp/Kg',
enableSorting: false, enableSorting: false,
accessorFn: (item) => formatCurrency(item.rp_per_kg || 0), accessorFn: (item) => formatCurrency(item.rp_per_kg || 0),
footer: (item) => ( footer: () => (
<div className='font-bold'> <div className='font-bold'>
{isResponseSuccess(finance) {isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.profit_loss.data.summary.net_profit finance.data.profit_loss.summary.net_profit
.rp_per_kg || 0 .rp_per_kg || 0
) )
: formatCurrency(0)} : formatCurrency(0)}
@@ -520,11 +339,11 @@ const ClosingFinanceTable = ({
header: 'Jumlah (Rp)', header: 'Jumlah (Rp)',
enableSorting: false, enableSorting: false,
accessorFn: (item) => formatCurrency(item.amount || 0), accessorFn: (item) => formatCurrency(item.amount || 0),
footer: (item) => ( footer: () => (
<div className='font-bold'> <div className='font-bold'>
{isResponseSuccess(finance) {isResponseSuccess(finance)
? formatCurrency( ? formatCurrency(
finance.data.profit_loss.data.summary.net_profit finance.data.profit_loss.summary.net_profit
.amount || 0 .amount || 0
) )
: formatCurrency(0)} : formatCurrency(0)}
@@ -534,55 +353,30 @@ const ClosingFinanceTable = ({
]} ]}
renderCustomRow={(row) => { renderCustomRow={(row) => {
const rowData = row.original; const rowData = row.original;
if (rowData.isGroupHeader) { if (rowData.code === 'custom_row') {
if (rowData.amount) {
return (
<tr
key={row.id}
className={TABLE_DEFAULT_STYLING.footerRowClassName}
>
<td
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
>
<div className='font-bold ps-6 uppercase'>
{formatTitleCase(rowData.label ?? '-')}
</div>
</td>
<td
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
>
<div className='font-bold'>
{formatCurrency(rowData.rp_per_bird ?? 0)}
</div>
</td>
<td
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
>
<div className='font-bold'>
{formatCurrency(rowData.rp_per_kg ?? 0)}
</div>
</td>
<td
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
>
<div className='font-bold'>
{formatCurrency(rowData.amount ?? 0)}
</div>
</td>
</tr>
);
}
return ( return (
<tr <tr
key={row.id} key={row.id}
className={TABLE_DEFAULT_STYLING.bodyRowClassName} className={TABLE_DEFAULT_STYLING.footerRowClassName}
> >
<td <td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
colSpan={4} <div className='font-bold ps-6 uppercase'>
className={TABLE_DEFAULT_STYLING.bodyColumnClassName} {formatTitleCase(rowData.label ?? '-')}
> </div>
</td>
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
<div className='font-bold'> <div className='font-bold'>
{formatTitleCase(rowData.group_name ?? '-')} {formatCurrency(rowData.rp_per_bird ?? 0)}
</div>
</td>
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
<div className='font-bold'>
{formatCurrency(rowData.rp_per_kg ?? 0)}
</div>
</td>
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
<div className='font-bold'>
{formatCurrency(rowData.amount ?? 0)}
</div> </div>
</td> </td>
</tr> </tr>
@@ -32,101 +32,160 @@ const ClosingOverheadTable = ({
); );
// Helper function to create columns with footer support // Helper function to create columns with footer support
const createColumns = (total?: OverheadTotal): ColumnDef<Overhead>[] => [ const createColumns = (
// Group untuk kolom tanpa footer total?: OverheadTotal,
{ kandangId?: number
header: 'Nama Item', ): ColumnDef<Overhead>[] => {
accessorFn: (props) => props.item_name, const flockColumn: ColumnDef<Overhead>[] = [
footer: 'Total Pengeluaran Overhead', {
}, header: 'Budget Pengajuan',
{ footer: '',
header: 'Satuan', columns: [
accessorFn: (props) => props.uom_name, {
}, id: 'budget_quantity',
{ header: 'Jumlah',
header: 'Budget Pengajuan', accessorFn: (props) =>
footer: '', props.budget_quantity ? formatNumber(props.budget_quantity) : '-',
columns: [ footer: total ? () => formatNumber(total.budget_quantity) : '',
{ },
id: 'budget_quantity', {
header: 'Jumlah', id: 'budget_unit_price',
accessorFn: (props) => header: 'Harga Satuan',
props.budget_quantity ? formatNumber(props.budget_quantity) : '-', accessorFn: (props) =>
footer: total ? () => formatNumber(total.budget_quantity) : '', props.budget_unit_price
}, ? formatCurrency(props.budget_unit_price)
{ : '-',
id: 'budget_unit_price', footer: '',
header: 'Harga Satuan', },
accessorFn: (props) => {
props.budget_unit_price id: 'budget_total_amount',
? formatCurrency(props.budget_unit_price) header: 'Total',
: '-', accessorFn: (props) =>
footer: '', props.budget_total_amount
}, ? formatCurrency(props.budget_total_amount)
{ : '-',
id: 'budget_total_amount', footer: total
header: 'Total', ? () => formatCurrency(total.budget_total_amount)
accessorFn: (props) => : '',
props.budget_total_amount },
? formatCurrency(props.budget_total_amount) ],
: '-', },
footer: total ? () => formatCurrency(total.budget_total_amount) : '', {
}, header: 'Realisasi',
], footer: '',
}, columns: [
{ {
header: 'Realisasi', id: 'actual_date',
footer: '', header: 'Tanggal',
columns: [ accessorFn: (props) =>
{ props.actual_date
id: 'actual_date', ? formatDate(props.actual_date, 'DD MMM, YYYY')
header: 'Tanggal', : '-',
accessorFn: (props) => footer: '',
props.actual_date },
? formatDate(props.actual_date, 'DD MMM, YYYY') {
: '-', id: 'actual_quantity',
footer: '', header: 'Jumlah',
}, accessorFn: (props) =>
{ props.actual_quantity ? formatNumber(props.actual_quantity) : '-',
id: 'actual_quantity', footer: total ? () => formatNumber(total.actual_quantity) : '',
header: 'Jumlah', },
accessorFn: (props) => {
props.actual_quantity ? formatNumber(props.actual_quantity) : '-', id: 'actual_unit_price',
footer: total ? () => formatNumber(total.actual_quantity) : '', header: 'Harga Satuan',
}, accessorFn: (props) =>
{ props.actual_unit_price
id: 'actual_unit_price', ? formatCurrency(props.actual_unit_price)
header: 'Harga Satuan', : '-',
accessorFn: (props) => footer: '',
props.actual_unit_price },
? formatCurrency(props.actual_unit_price) {
: '-', id: 'actual_total_amount',
footer: '', header: 'Total',
}, accessorFn: (props) =>
{ props.actual_total_amount
id: 'actual_total_amount', ? formatCurrency(props.actual_total_amount)
header: 'Total', : '-',
accessorFn: (props) => footer: total
props.actual_total_amount ? () => formatCurrency(total.actual_total_amount)
? formatCurrency(props.actual_total_amount) : '',
: '-', },
footer: total ? () => formatCurrency(total.actual_total_amount) : '', ],
}, },
], ];
},
{ const kandangColumn: ColumnDef<Overhead>[] = [
id: 'cost_per_bird', {
header: 'Rp/Ekor', id: 'actual_date',
accessorFn: (props) => header: 'Tanggal',
props.cost_per_bird ? formatCurrency(props.cost_per_bird) : '-', accessorFn: (props) =>
footer: total ? () => formatCurrency(total.cost_per_bird) : '', 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<Overhead>[] = [
// 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( const columns = useMemo(
() => () =>
isResponseSuccess(overhead) isResponseSuccess(overhead)
? createColumns(overhead.data?.total) ? createColumns(
overhead.data?.total,
kandangId ? Number(kandangId) : undefined
)
: createColumns(), : createColumns(),
[overhead] [overhead]
); );
@@ -118,8 +118,6 @@ const DashboardProduction = () => {
} as DashboardFilterType, } as DashboardFilterType,
validationSchema: getDashboardFilterSchema(analysisMode), validationSchema: getDashboardFilterSchema(analysisMode),
onSubmit: (values) => { onSubmit: (values) => {
console.log(values);
handleApplyFilter({ handleApplyFilter({
start_date: values.startDate || '', start_date: values.startDate || '',
end_date: values.endDate || '', end_date: values.endDate || '',
@@ -139,8 +137,6 @@ const DashboardProduction = () => {
}; };
const handleApplyFilter = (values: DashboardFilter) => { const handleApplyFilter = (values: DashboardFilter) => {
console.log(values);
// Build query params object, only include non-empty values // Build query params object, only include non-empty values
const params: Record<string, string> = {}; const params: Record<string, string> = {};
@@ -156,7 +152,6 @@ const DashboardProduction = () => {
if (values.comparison_type) params.comparison_type = values.comparison_type; if (values.comparison_type) params.comparison_type = values.comparison_type;
setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`); setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`);
console.log(endpointUrl);
filterModal.closeModal(); filterModal.closeModal();
refreshDashboardProductionData(); refreshDashboardProductionData();
}; };
+10 -16
View File
@@ -1,21 +1,17 @@
import { ChangeEventHandler, useMemo, useState } from 'react'; 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 { useSearchParams } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import Button from '@/components/Button'; import Button from '@/components/Button';
import Card from '@/components/Card'; import Card from '@/components/Card';
import Dropdown from '@/components/dropdown/Dropdown';
import DateInput from '@/components/input/DateInput'; import DateInput from '@/components/input/DateInput';
import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, { import SelectInput, {
OptionType, OptionType,
useSelect, useSelect,
} from '@/components/input/SelectInput'; } from '@/components/input/SelectInput';
import Menu from '@/components/menu/Menu';
import MenuItem from '@/components/menu/MenuItem';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Tooltip from '@/components/Tooltip';
import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Finance } from '@/types/api/finance/finance'; import { Finance } from '@/types/api/finance/finance';
@@ -23,11 +19,10 @@ import {
FINANCE_INITIAL_BALANCE_STATUS, FINANCE_INITIAL_BALANCE_STATUS,
FINANCE_INJECTION_STATUS, FINANCE_INJECTION_STATUS,
FINANCE_TRANSACTION_STATUS, FINANCE_TRANSACTION_STATUS,
ROWS_OPTIONS,
} from '@/config/constant'; } from '@/config/constant';
import { FinanceApi } from '@/services/api/finance'; import { FinanceApi } from '@/services/api/finance';
import { isResponseSuccess } from '@/lib/api-helper'; 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 { Bank } from '@/types/api/master-data/bank';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
@@ -219,15 +214,12 @@ const FinanceTable = () => {
{ label: 'Tanggal Dibuat', value: 'created_at' }, { label: 'Tanggal Dibuat', value: 'created_at' },
]; ];
}, []); }, []);
const { options: bankOptions, rawData: bankRawData } = useSelect<Bank>( const {
BankApi.basePath, options: bankOptions,
'id', rawData: bankRawData,
'alias', setInputValue: bankInputValue,
'', loadMore: bankLoadMore,
{ } = useSelect<Bank>(BankApi.basePath, 'id', 'alias');
limit: 'limit',
}
);
// ===== Handler ===== // ===== Handler =====
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
@@ -501,6 +493,8 @@ const FinanceTable = () => {
label='Bank' label='Bank'
value={selectedBank} value={selectedBank}
onChange={bankChangeHandler} onChange={bankChangeHandler}
onInputChange={bankInputValue}
onMenuScrollToBottom={bankLoadMore}
isClearable isClearable
/> />
<SelectInput <SelectInput
@@ -113,20 +113,22 @@ const FormFinanceAdd = ({
options: partyOptions, options: partyOptions,
isLoadingOptions: isLoadingPartyOptions, isLoadingOptions: isLoadingPartyOptions,
rawData: partyRawData, rawData: partyRawData,
setInputValue: setPartyInputValue,
loadMore: loadMorePartyOptions,
} = useSelect<PartyCommonProps>( } = useSelect<PartyCommonProps>(
formik.values.party_type_option?.value === 'CUSTOMER' formik.values.party_type_option?.value === 'CUSTOMER'
? CustomerApi.basePath ? CustomerApi.basePath
: SupplierApi.basePath, : SupplierApi.basePath,
'id', 'id',
'name', 'name'
'',
{ limit: 'limit' }
); );
const { const {
options: bankOptions, options: bankOptions,
rawData: bankRawData, rawData: bankRawData,
isLoadingOptions: isLoadingBankOptions, isLoadingOptions: isLoadingBankOptions,
} = useSelect<Bank>(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); setInputValue: setBankInputValue,
loadMore: loadMoreBankOptions,
} = useSelect<Bank>(BankApi.basePath, 'id', 'name');
// ===== Helper Functions ===== // ===== Helper Functions =====
const transformFormValuesToPayload = ( 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'}`} placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis transaksi dahulu'}`}
options={partyOptions} options={partyOptions}
value={formik.values.party_id_option} value={formik.values.party_id_option}
onInputChange={setPartyInputValue}
onMenuScrollToBottom={loadMorePartyOptions}
onChange={(value) => { onChange={(value) => {
formik.setFieldValue('party_id_option', value); formik.setFieldValue('party_id_option', value);
if (isResponseSuccess(partyRawData) && value) { if (isResponseSuccess(partyRawData) && value) {
@@ -304,6 +308,8 @@ const FormFinanceAdd = ({
: [] : []
} }
value={formik.values.bank_id_option} value={formik.values.bank_id_option}
onInputChange={setBankInputValue}
onMenuScrollToBottom={loadMoreBankOptions}
onChange={(value) => { onChange={(value) => {
formik.setFieldValue('bank_id_option', value); formik.setFieldValue('bank_id_option', value);
}} }}
@@ -104,21 +104,25 @@ const FormFinanceAddInitialBalance = ({
}); });
// ===== Options ===== // ===== Options =====
const { options: partyOptions, isLoadingOptions: isLoadingPartyOptions } = const {
useSelect( options: partyOptions,
formik.values.party_type_option?.value === 'CUSTOMER' isLoadingOptions: isLoadingPartyOptions,
? CustomerApi.basePath setInputValue: setPartyInputValue,
: SupplierApi.basePath, loadMore: loadMorePartyOptions,
'id', } = useSelect(
'name', formik.values.party_type_option?.value === 'CUSTOMER'
'', ? CustomerApi.basePath
{ limit: 'limit' } : SupplierApi.basePath,
); 'id',
'name'
);
const { const {
options: bankOptions, options: bankOptions,
rawData: bankRawData, rawData: bankRawData,
isLoadingOptions: isLoadingBankOptions, isLoadingOptions: isLoadingBankOptions,
} = useSelect<Bank>(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); setInputValue: setBankInputValue,
loadMore: loadMoreBankOptions,
} = useSelect<Bank>(BankApi.basePath, 'id', 'name');
// ===== Helper Functions ===== // ===== Helper Functions =====
const transformFormValuesToPayload = ( const transformFormValuesToPayload = (
@@ -189,6 +193,8 @@ const FormFinanceAddInitialBalance = ({
placeholder='Pilih jenis pihak' placeholder='Pilih jenis pihak'
options={FINANCE_PARTY_TYPE_OPTIONS} options={FINANCE_PARTY_TYPE_OPTIONS}
value={formik.values.party_type_option} value={formik.values.party_type_option}
onInputChange={setPartyInputValue}
onMenuScrollToBottom={loadMorePartyOptions}
onChange={(value) => { onChange={(value) => {
formik.setFieldValue('party_type_option', value); formik.setFieldValue('party_type_option', value);
formik.setFieldValue('party_id_option', null); 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'}`} placeholder={`Pilih ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'jenis pihak dahulu'}`}
options={partyOptions} options={partyOptions}
value={formik.values.party_id_option} value={formik.values.party_id_option}
onInputChange={setPartyInputValue}
onMenuScrollToBottom={loadMorePartyOptions}
onChange={(value) => { onChange={(value) => {
formik.setFieldValue('party_id_option', value); formik.setFieldValue('party_id_option', value);
}} }}
@@ -80,7 +80,9 @@ const FormFinanceInjection = ({
options: bankOptions, options: bankOptions,
rawData: bankRawData, rawData: bankRawData,
isLoadingOptions: isLoadingBankOptions, isLoadingOptions: isLoadingBankOptions,
} = useSelect<Bank>(BankApi.basePath, 'id', 'name', '', { limit: 'limit' }); setInputValue: setBankInputValue,
loadMore: loadMoreBankOptions,
} = useSelect<Bank>(BankApi.basePath, 'id', 'name');
// ===== Helper Functions ===== // ===== Helper Functions =====
const transformFormValuesToPayload = ( const transformFormValuesToPayload = (
@@ -162,6 +164,8 @@ const FormFinanceInjection = ({
: [] : []
} }
value={formik.values.bank_id_option} value={formik.values.bank_id_option}
onInputChange={setBankInputValue}
onMenuScrollToBottom={loadMoreBankOptions}
onChange={(value) => { onChange={(value) => {
formik.setFieldValue('bank_id_option', value); formik.setFieldValue('bank_id_option', value);
}} }}
@@ -184,12 +184,16 @@ const MarketingTable = () => {
const { const {
options: productsOptions, options: productsOptions,
isLoadingOptions: isLoadingProductsOptions, isLoadingOptions: isLoadingProductsOptions,
setInputValue: setProductsInputValue,
loadMore: loadMoreProducts,
} = useSelect(ProductApi.basePath, 'id', 'name', '', { } = useSelect(ProductApi.basePath, 'id', 'name', '', {
limit: 'limit', limit: 'limit',
}); });
const { const {
options: customersOptions, options: customersOptions,
isLoadingOptions: isLoadingCustomersOptions, isLoadingOptions: isLoadingCustomersOptions,
setInputValue: setCustomersInputValue,
loadMore: loadMoreCustomers,
} = useSelect(CustomerApi.basePath, 'id', 'name', '', { } = useSelect(CustomerApi.basePath, 'id', 'name', '', {
limit: 'limit', limit: 'limit',
}); });
@@ -400,6 +404,8 @@ const MarketingTable = () => {
.join(',') || '' .join(',') || ''
) )
} }
onInputChange={setProductsInputValue}
onMenuScrollToBottom={loadMoreProducts}
isMulti isMulti
/> />
{/* select status */} {/* select status */}
@@ -444,6 +450,8 @@ const MarketingTable = () => {
(value as OptionType)?.value.toString() || '' (value as OptionType)?.value.toString() || ''
) )
} }
onInputChange={setCustomersInputValue}
onMenuScrollToBottom={loadMoreCustomers}
/> />
</TableRowSizeSelector> </TableRowSizeSelector>
</div> </div>
@@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Area } from '@/types/api/master-data/area'; import { Area } from '@/types/api/master-data/area';
import { AreaApi } from '@/services/api/master-data'; import { AreaApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -164,7 +164,14 @@ const AreasTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshAreas();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Bank } from '@/types/api/master-data/bank'; import { Bank } from '@/types/api/master-data/bank';
import { BankApi } from '@/services/api/master-data'; import { BankApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -177,7 +177,14 @@ const BanksTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshBanks();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -11,7 +11,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import { ROWS_OPTIONS } from '@/config/constant'; 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 { cn } from '@/lib/helper';
import { CustomerApi } from '@/services/api/master-data'; import { CustomerApi } from '@/services/api/master-data';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
@@ -186,7 +186,16 @@ const CustomersTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshCustomers();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Fcr } from '@/types/api/master-data/fcr'; import { Fcr } from '@/types/api/master-data/fcr';
import { FcrApi } from '@/services/api/master-data'; import { FcrApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -164,7 +164,14 @@ const FcrsTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshFcrs();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -19,7 +19,7 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import Table from '@/components/Table'; import Table from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
const RowsOptions = ({ const RowsOptions = ({
@@ -182,7 +182,14 @@ const FlockTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshFlocks();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Kandang } from '@/types/api/master-data/kandang'; import { Kandang } from '@/types/api/master-data/kandang';
import { KandangApi } from '@/services/api/master-data'; import { KandangApi } from '@/services/api/master-data';
import { cn, formatNumber } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -199,7 +199,16 @@ const KandangsTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshKandangs();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Location } from '@/types/api/master-data/location'; import { Location } from '@/types/api/master-data/location';
import { LocationApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -186,7 +186,16 @@ const LocationsTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshLocations();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Nonstock } from '@/types/api/master-data/nonstock'; import { Nonstock } from '@/types/api/master-data/nonstock';
import { NonstockApi } from '@/services/api/master-data'; import { NonstockApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -198,7 +198,16 @@ const NonstocksTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshNonstocks();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { ProductCategory } from '@/types/api/master-data/product-category'; import { ProductCategory } from '@/types/api/master-data/product-category';
import { ProductCategoryApi } from '@/services/api/master-data'; import { ProductCategoryApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -170,7 +170,16 @@ const ProductCategoryTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshProductCategories();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Product } from '@/types/api/master-data/product'; import { Product } from '@/types/api/master-data/product';
import { ProductApi } from '@/services/api/master-data'; import { ProductApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -230,8 +230,19 @@ const ProductsTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshProducts();
deleteModal.closeModal(); deleteModal.closeModal();
toast.success('Successfully delete Product!'); toast.success('Successfully delete Product!');
setIsDeleteLoading(false); setIsDeleteLoading(false);
@@ -7,7 +7,7 @@ import { ProductionStandard } from '@/types/api/master-data/production-standard'
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import useSWR from 'swr'; import useSWR from 'swr';
import { ProductionStandardApi } from '@/services/api/master-data'; 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 RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { CellContext } from '@tanstack/react-table'; import { CellContext } from '@tanstack/react-table';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
@@ -94,9 +94,16 @@ const ProductionStandardTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
await ProductionStandardApi.delete( const deleteResponse = await ProductionStandardApi.delete(
selectedProductionStandard?.id as number selectedProductionStandard?.id as number
); );
if (isResponseError(deleteResponse)) {
toast.error(deleteResponse.message);
setIsDeleteLoading(false);
return;
}
refreshProductionStandards(); refreshProductionStandards();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -11,7 +11,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import { ROWS_OPTIONS } from '@/config/constant'; 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 { cn } from '@/lib/helper';
import { SupplierApi } from '@/services/api/master-data'; import { SupplierApi } from '@/services/api/master-data';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
@@ -205,7 +205,16 @@ const SuppliersTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshSuppliers();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -20,7 +20,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Uom } from '@/types/api/master-data/uom'; import { Uom } from '@/types/api/master-data/uom';
import { UomApi } from '@/services/api/master-data'; import { UomApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -164,7 +164,14 @@ const UomsTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshUoms();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -25,7 +25,7 @@ import RequirePermission from '@/components/helper/RequirePermission';
import { Warehouse } from '@/types/api/master-data/warehouse'; import { Warehouse } from '@/types/api/master-data/warehouse';
import { WarehouseApi } from '@/services/api/master-data'; import { WarehouseApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; 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 { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
@@ -220,7 +220,16 @@ const WarehousesTable = () => {
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); 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(); refreshWarehouses();
deleteModal.closeModal(); deleteModal.closeModal();
@@ -5,7 +5,10 @@ import Button from '@/components/Button';
import FloatingActionsButton from '@/components/FloatingActionsButton'; import FloatingActionsButton from '@/components/FloatingActionsButton';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
import DebouncedTextInput from '@/components/input/DebouncedTextInput'; 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 { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
@@ -59,9 +62,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const selectedRowIds = Object.keys(rowSelection) const selectedRowIds = Object.keys(rowSelection)
.filter((id) => rowSelection[id]) .filter((id) => rowSelection[id])
.map((id) => parseInt(id)); .map((id) => parseInt(id));
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null); const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>( const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null null
@@ -90,55 +90,25 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
{ revalidateOnMount: true } { revalidateOnMount: true }
); );
const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({ // ===== Fetch Data Select =====
search: areaSelectInputValue, const {
limit: '100', options: optionsArea,
}).toString()}`; isLoadingOptions: isLoadingArea,
const { data: areas, isLoading: isLoadingAreas } = useSWR( setInputValue: setAreaSelectInputValue,
areaUrl, loadMore: loadMoreArea,
AreaApi.getAllFetcher } = useSelect(AreaApi.basePath, 'id', 'name');
); const {
options: optionsLocation,
const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ isLoadingOptions: isLoadingLocation,
search: locationSelectInputValue, setInputValue: setLocationSelectInputValue,
area_id: selectedArea != null ? selectedArea.value.toString() : '', loadMore: loadMoreLocation,
limit: '100', } = useSelect(LocationApi.basePath, 'id', 'name');
}).toString()}`; const {
const { data: locations, isLoading: isLoadingLocations } = useSWR( options: optionsKandang,
locationUrl, isLoadingOptions: isLoadingKandang,
LocationApi.getAllFetcher setInputValue: setKandangSelectInputValue,
); loadMore: loadMoreKandang,
} = useSelect(KandangApi.basePath, 'id', 'name');
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,
}))
: [];
// ====== HANDLER ====== // ====== HANDLER ======
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -385,7 +355,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
<SelectInput <SelectInput
label='Area' label='Area'
options={optionsArea} options={optionsArea}
isLoading={isLoadingAreas} isLoading={isLoadingArea}
value={selectedArea} value={selectedArea}
onChange={(val) => { onChange={(val) => {
setSelectedArea(val as OptionType); setSelectedArea(val as OptionType);
@@ -395,12 +365,13 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
}} }}
onInputChange={setAreaSelectInputValue} onInputChange={setAreaSelectInputValue}
onMenuScrollToBottom={loadMoreArea}
isClearable isClearable
/> />
<SelectInput <SelectInput
label='Lokasi' label='Lokasi'
options={optionsLocation} options={optionsLocation}
isLoading={isLoadingLocations} isLoading={isLoadingLocation}
value={selectedLocation} value={selectedLocation}
onChange={(val) => { onChange={(val) => {
setSelectedLocation(val as OptionType); setSelectedLocation(val as OptionType);
@@ -410,6 +381,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
}} }}
onInputChange={setLocationSelectInputValue} onInputChange={setLocationSelectInputValue}
onMenuScrollToBottom={loadMoreLocation}
isClearable isClearable
/> />
<SelectInput <SelectInput
@@ -425,6 +397,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
}} }}
onInputChange={setKandangSelectInputValue} onInputChange={setKandangSelectInputValue}
onMenuScrollToBottom={loadMoreKandang}
isClearable isClearable
/> />
<DebouncedTextInput <DebouncedTextInput
@@ -557,15 +557,12 @@ const ProjectFlockForm = ({
}; };
const onDeleteBudgetRowHandler = (nonstock_id: number, index?: number) => { const onDeleteBudgetRowHandler = (nonstock_id: number, index?: number) => {
console.log(`nonstock_id: ${nonstock_id}, index: ${index}`);
if (!nonstock_id) { if (!nonstock_id) {
const updatedBudgets = formik.values.project_budgets const updatedBudgets = formik.values.project_budgets
.map((budget, i) => { .map((budget, i) => {
if (i == index) { if (i == index) {
console.log(`buget: ${null}, index: ${index}, i: ${i}`);
return null; return null;
} else { } else {
console.log(`buget: ${budget}, index: ${index}, i: ${i}`);
return budget; return budget;
} }
}) })
@@ -5,7 +5,7 @@ import { RefObject } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { SortingState, CellContext } from '@tanstack/react-table'; 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 RequirePermission from '@/components/helper/RequirePermission';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
@@ -656,20 +656,30 @@ const RecordingTable = () => {
); );
}, },
cell: ({ row }) => { cell: ({ row }) => {
const recording = row.original;
const isDisabled = isRecordingApproved(recording);
const handleToggleSelection = (e: unknown) => {
if (!isDisabled) {
row.getToggleSelectedHandler()(e);
}
};
return ( return (
<div> <div className={cn({ 'opacity-50': isDisabled })}>
<CheckboxInput <CheckboxInput
name='row' name='row'
checked={row.getIsSelected()} checked={row.getIsSelected()}
indeterminate={row.getIsSomeSelected()} indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()} onChange={handleToggleSelection}
disabled={isDisabled}
/> />
</div> </div>
); );
}, },
}, },
{ {
header: '#', header: 'No',
cell: (props) => cell: (props) =>
tableFilterState.pageSize * (tableFilterState.page - 1) + tableFilterState.pageSize * (tableFilterState.page - 1) +
props.row.index + props.row.index +
@@ -680,6 +690,10 @@ const RecordingTable = () => {
cell: (props) => cell: (props) =>
props.row.original.project_flock?.flock_name || '-', props.row.original.project_flock?.flock_name || '-',
}, },
{
header: 'Periode',
cell: (props) => props.row.original.project_flock?.period || '-',
},
{ {
header: 'Kategori', header: 'Kategori',
cell: (props) => { cell: (props) => {
@@ -696,7 +710,21 @@ const RecordingTable = () => {
}, },
{ {
header: 'Umur (hari)', header: 'Umur (hari)',
cell: (props) => props.row.original.day, cell: (props) => {
return (
<>
<span>
{props.row.original.day} (Minggu ke-
{props.row.original.project_flock.production_standart.week})
</span>
</>
);
},
},
{
accessorKey: 'warehouse.name',
header: 'Gudang',
cell: (props) => props.row.original.warehouse?.name,
}, },
{ {
accessorKey: 'record_date', accessorKey: 'record_date',
@@ -705,10 +733,263 @@ const RecordingTable = () => {
formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'), formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'),
}, },
{ {
header: 'Populasi Awal', header: 'Populasi Akhir',
cell: (props) => 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 (
<div className='text-center'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
{
id: 'fcr_standard',
header: 'Standard',
cell: (props) => {
const value = props.row.original.project_flock?.fcr?.fcr_std;
return (
<div className='text-center text-gray-600'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
],
},
{
id: 'feed_intake',
header: 'Feed Intake (KG)',
columns: [
{
id: 'feed_intake_actual',
header: 'Actual',
cell: (props) => {
const value = props.row.original.feed_intake;
return (
<div className='text-center'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
{
id: 'feed_intake_standard',
header: 'Standard',
cell: (props) => {
const value =
props.row.original.project_flock?.production_standart
?.feed_intake_std;
return (
<div className='text-center text-gray-600'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
],
},
{
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 (
<div className='text-center'>
{value !== null && value !== undefined
? `${value.toFixed(2)}%`
: '-'}
</div>
);
},
},
{
id: 'max_depletion_std',
header: 'Max Depletion Std',
cell: (props) => {
const value =
props.row.original.project_flock?.production_standart
?.max_depletion_std;
return (
<div className='text-center text-gray-600'>
{value !== null && value !== undefined
? `${value.toFixed(2)}%`
: '-'}
</div>
);
},
},
{
id: 'total_depletion',
header: 'Total Depletion',
cell: (props) => {
const value = props.row.original.total_depletion_qty;
return (
<div className='text-center'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
],
},
{
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 (
<div className='text-center'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
{
id: 'egg_mass_standard',
header: 'Egg Mass Standar',
cell: (props) => {
const value =
props.row.original.project_flock?.production_standart
?.egg_mass_std;
return (
<div className='text-center text-gray-600'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
{
id: 'egg_weight_actual',
header: 'Egg Weight Actual',
cell: (props) => {
const value = props.row.original.egg_weight;
return (
<div className='text-center'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
{
id: 'egg_weight_standard',
header: 'Egg Weight Standar',
cell: (props) => {
const value =
props.row.original.project_flock?.production_standart
?.egg_weight_std;
return (
<div className='text-center text-gray-600'>
{value !== null && value !== undefined
? formatNumber(value)
: '-'}
</div>
);
},
},
],
},
{
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 (
<div className='text-center'>
{value !== null && value !== undefined
? `${value.toFixed(2)}%`
: '-'}
</div>
);
},
},
{
id: 'hen_day_standard',
header: 'Hen Day Standar',
cell: (props) => {
const value =
props.row.original.project_flock?.production_standart
?.hen_day_std;
return (
<div className='text-center text-gray-600'>
{value !== null && value !== undefined
? `${value.toFixed(2)}%`
: '-'}
</div>
);
},
},
{
id: 'hen_house_actual',
header: 'Hen House Actual',
cell: (props) => {
const value = props.row.original.hen_house;
return (
<div className='text-center'>
{value !== null && value !== undefined
? `${value.toFixed(2)}%`
: '-'}
</div>
);
},
},
{
id: 'hen_house_standard',
header: 'Hen House Standar',
cell: (props) => {
const value =
props.row.original.project_flock?.production_standart
?.hen_house_std;
return (
<div className='text-center text-gray-600'>
{value !== null && value !== undefined
? `${value.toFixed(2)}%`
: '-'}
</div>
);
},
},
],
}, },
{ {
header: 'Status Approval', header: 'Status Approval',
@@ -874,14 +1155,15 @@ const RecordingTable = () => {
'mb-20': 'mb-20':
isResponseSuccess(recordings) && recordings?.data?.length === 0, isResponseSuccess(recordings) && recordings?.data?.length === 0,
}), }),
tableWrapperClassName: 'overflow-x-auto min-h-full!', tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'font-inter w-full table-auto min-h-full!', tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200', headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName: headerColumnClassName:
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', '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: 'border-b border-b-gray-200', 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: bodyColumnClassName:
'px-6 py-3 last:flex last:flex-row last:justify-end', 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}} }}
/> />
@@ -7,6 +7,7 @@ import {
} from '@/types/api/production/recording'; } from '@/types/api/production/recording';
type RecordingGrowingFormSchemaType = { type RecordingGrowingFormSchemaType = {
record_date: string;
project_flock_kandang: { project_flock_kandang: {
value: number; value: number;
label: string; label: string;
@@ -85,6 +86,9 @@ const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({
export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> = export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> =
Yup.object({ Yup.object({
record_date: Yup.string()
.required('Tanggal recording wajib diisi!')
.typeError('Tanggal recording wajib diisi!'),
project_flock_kandang: Yup.object({ project_flock_kandang: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
@@ -179,6 +183,9 @@ type RecordingFormData = Partial<Recording> & {
export const getRecordingGrowingFormInitialValues = ( export const getRecordingGrowingFormInitialValues = (
initialValues?: RecordingFormData initialValues?: RecordingFormData
): RecordingGrowingFormValues => ({ ): 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 project_flock_kandang: initialValues?.project_flock_kandang_id
? { ? {
value: initialValues.project_flock_kandang_id, value: initialValues.project_flock_kandang_id,
File diff suppressed because it is too large Load Diff
@@ -179,12 +179,16 @@ const TransferToLayingsTable = () => {
setInputValue: setFlockSourceInputValue, setInputValue: setFlockSourceInputValue,
options: flockSourceOptions, options: flockSourceOptions,
isLoadingOptions: isLoadingFlockSourceOptions, isLoadingOptions: isLoadingFlockSourceOptions,
loadMore: loadMoreFlockSource,
hasMore: hasMoreFlockSource,
} = useSelect<Flock>(FlockApi.basePath, 'id', 'name'); } = useSelect<Flock>(FlockApi.basePath, 'id', 'name');
const { const {
setInputValue: setFlockDestinationInputValue, setInputValue: setFlockDestinationInputValue,
options: flockDestinationOptions, options: flockDestinationOptions,
isLoadingOptions: isLoadingFlockDestinationOptions, isLoadingOptions: isLoadingFlockDestinationOptions,
loadMore: loadMoreFlockDestination,
hasMore: hasMoreFlockDestination,
} = useSelect<Flock>(FlockApi.basePath, 'id', 'name'); } = useSelect<Flock>(FlockApi.basePath, 'id', 'name');
// Flocks value // Flocks value
@@ -595,6 +599,7 @@ const TransferToLayingsTable = () => {
value={selectedFlockSource} value={selectedFlockSource}
onChange={flockSourceChangeHandler} onChange={flockSourceChangeHandler}
onInputChange={setFlockSourceInputValue} onInputChange={setFlockSourceInputValue}
onMenuScrollToBottom={loadMoreFlockSource}
isClearable isClearable
className={{ className={{
wrapper: 'col-span-12 sm:col-span-3', wrapper: 'col-span-12 sm:col-span-3',
@@ -608,6 +613,7 @@ const TransferToLayingsTable = () => {
value={selectedFlockDestination} value={selectedFlockDestination}
onChange={flockDestinationChangeHandler} onChange={flockDestinationChangeHandler}
onInputChange={setFlockDestinationInputValue} onInputChange={setFlockDestinationInputValue}
onMenuScrollToBottom={loadMoreFlockDestination}
isClearable isClearable
className={{ className={{
wrapper: 'col-span-12 sm:col-span-3', wrapper: 'col-span-12 sm:col-span-3',
@@ -270,6 +270,8 @@ const TransferToLayingForm = ({
options: flockSourceOptions, options: flockSourceOptions,
isLoadingOptions: isLoadingFlockSourceOptions, isLoadingOptions: isLoadingFlockSourceOptions,
rawData: flockSources, rawData: flockSources,
loadMore: loadMoreFlockSource,
hasMore: hasMoreFlockSource,
} = useSelect<ProjectFlock>( } = useSelect<ProjectFlock>(
'/production/project-flocks', '/production/project-flocks',
'id', 'id',
@@ -360,6 +362,8 @@ const TransferToLayingForm = ({
options: flockDestinationOptions, options: flockDestinationOptions,
isLoadingOptions: isLoadingFlockDestinationOptions, isLoadingOptions: isLoadingFlockDestinationOptions,
rawData: flockDestinations, rawData: flockDestinations,
loadMore: loadMoreFlockDestination,
hasMore: hasMoreFlockDestination,
} = useSelect<ProjectFlock>( } = useSelect<ProjectFlock>(
'/production/project-flocks', '/production/project-flocks',
'id', 'id',
@@ -573,6 +577,7 @@ const TransferToLayingForm = ({
onChange={flockSourceChangeHandler} onChange={flockSourceChangeHandler}
isLoading={isLoadingFlockSourceOptions} isLoading={isLoadingFlockSourceOptions}
onInputChange={setFlockSourceInputValue} onInputChange={setFlockSourceInputValue}
onMenuScrollToBottom={loadMoreFlockSource}
isError={ isError={
formik.touched.flockSource && formik.touched.flockSource &&
Boolean(typeof formik.errors.flockSource === 'string') Boolean(typeof formik.errors.flockSource === 'string')
@@ -591,6 +596,7 @@ const TransferToLayingForm = ({
onChange={flockDestinationChangeHandler} onChange={flockDestinationChangeHandler}
isLoading={isLoadingFlockDestinationOptions} isLoading={isLoadingFlockDestinationOptions}
onInputChange={setFlockDestinationInputValue} onInputChange={setFlockDestinationInputValue}
onMenuScrollToBottom={loadMoreFlockDestination}
isError={ isError={
formik.touched.flockDestination && formik.touched.flockDestination &&
Boolean(typeof formik.errors.flockDestination === 'string') Boolean(typeof formik.errors.flockDestination === 'string')
@@ -37,7 +37,10 @@ import DateInput from '@/components/input/DateInput';
import { LocationApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data';
import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlockApi } from '@/services/api/production';
import { Kandang } from '@/types/api/master-data/kandang'; 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 { import {
getStatusColor, getStatusColor,
getStatusIndicatorColor, getStatusIndicatorColor,
@@ -229,63 +232,37 @@ const UniformityTable = () => {
useState<number | undefined>(undefined); useState<number | undefined>(undefined);
const [filterStartDate, setFilterStartDate] = useState(''); const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState('');
const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] =
useState<string>('');
const [filterErrors, setFilterErrors] = useState<Record<string, string>>({}); const [filterErrors, setFilterErrors] = useState<Record<string, string>>({});
const { const {
setInputValue: setFilterLocationInputValue, setInputValue: setFilterLocationInputValue,
options: filterLocationOptions, options: filterLocationOptions,
isLoadingOptions: isLoadingFilterLocations, isLoadingOptions: isLoadingFilterLocations,
} = useSelect(LocationApi.basePath, 'id', 'name', 'search', { loadMore: loadMoreFilterLocations,
limit: '100', hasMore: hasMoreFilterLocations,
}); } = useSelect(LocationApi.basePath, 'id', 'name', 'search');
// ===== FETCH PROJECT FLOCKS DATA FOR FILTER ===== // ===== 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 { const {
data: filterProjectFlocksData, setInputValue: setFilterProjectFlockSearchValue,
isLoading: isLoadingFilterProjectFlocks, options: filterProjectFlockOptions,
} = useSWR(filterProjectFlocksUrl, ProjectFlockApi.getAllFetcher); rawData: filterProjectFlocksRawData,
isLoadingOptions: isLoadingFilterProjectFlocks,
const filterProjectFlocksDataList = useMemo( loadMore: loadMoreFilterProjectFlocks,
() => hasMore: hasMoreFilterProjectFlocks,
isResponseSuccess(filterProjectFlocksData) } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
? filterProjectFlocksData.data location_id: filterProjectFlockLocationId,
: 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]);
// ===== KANDANG OPTIONS FOR FILTER ===== // ===== KANDANG OPTIONS FOR FILTER =====
const filterKandangOptions = useMemo(() => { const filterKandangOptions = useMemo(() => {
let options: OptionType[] = []; let options: OptionType[] = [];
if (filterProjectFlock && filterProjectFlocksDataList) { if (filterProjectFlock && isResponseSuccess(filterProjectFlocksRawData)) {
const selectedProjectFlockData = filterProjectFlocksDataList.find( const data = filterProjectFlocksRawData.data as unknown as ProjectFlock[];
const selectedProjectFlockData = data.find(
(pf) => pf.id === filterProjectFlock.value (pf) => pf.id === filterProjectFlock.value
); );
@@ -301,7 +278,7 @@ const UniformityTable = () => {
} }
return options; return options;
}, [filterProjectFlock, filterProjectFlocksDataList]); }, [filterProjectFlock, filterProjectFlocksRawData]);
// ===== PROJECT FLOCK KANDANG LOOKUP ===== // ===== PROJECT FLOCK KANDANG LOOKUP =====
const projectFlockKandangLookupUrl = useMemo(() => { const projectFlockKandangLookupUrl = useMemo(() => {
@@ -394,9 +371,13 @@ const UniformityTable = () => {
// ===== FILTER HANDLERS ===== // ===== FILTER HANDLERS =====
const handleFilterLocationChange = useCallback( const handleFilterLocationChange = useCallback(
(val: OptionType | OptionType[] | null) => { (val: OptionType | OptionType[] | null) => {
setFilterLocation(val as OptionType | null); const location = val as OptionType | null;
setFilterLocation(location);
setFilterProjectFlock(null); setFilterProjectFlock(null);
setFilterKandang(null); setFilterKandang(null);
setFilterProjectFlockLocationId(
location ? location.value.toString() : ''
);
}, },
[] []
); );
@@ -1206,6 +1187,7 @@ const UniformityTable = () => {
options={filterLocationOptions} options={filterLocationOptions}
onInputChange={setFilterLocationInputValue} onInputChange={setFilterLocationInputValue}
isLoading={isLoadingFilterLocations} isLoading={isLoadingFilterLocations}
onMenuScrollToBottom={loadMoreFilterLocations}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
/> />
{filterErrors.location && ( {filterErrors.location && (
@@ -1225,8 +1207,9 @@ const UniformityTable = () => {
setFilterErrors((prev) => ({ ...prev, project_flock: '' })); setFilterErrors((prev) => ({ ...prev, project_flock: '' }));
}} }}
options={filterProjectFlockOptions} options={filterProjectFlockOptions}
onInputChange={setProjectFlockSearchValue} onInputChange={setFilterProjectFlockSearchValue}
isLoading={isLoadingFilterProjectFlocks} isLoading={isLoadingFilterProjectFlocks}
onMenuScrollToBottom={loadMoreFilterProjectFlocks}
isDisabled={!filterLocation} isDisabled={!filterLocation}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
/> />
@@ -1,4 +1,4 @@
import Badge from '../../../../Badge'; import Badge from '@/components/Badge';
import Card from '@/components/Card'; import Card from '@/components/Card';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { formatNumber } from '@/lib/helper'; import { formatNumber } from '@/lib/helper';
@@ -36,7 +36,10 @@ import {
VerifyUniformityPayload, VerifyUniformityPayload,
} from '@/types/api/production/uniformity'; } from '@/types/api/production/uniformity';
import { type BaseApiResponse } from '@/types/api/api-general'; 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 { Kandang } from '@/types/api/master-data/kandang';
import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm'; import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm';
import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm'; import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm';
@@ -88,7 +91,9 @@ const UniformityForm = ({
null null
); );
const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] =
useState<string>('');
const [selectedProjectFlock, setSelectedProjectFlock] = const [selectedProjectFlock, setSelectedProjectFlock] =
useState<OptionType | null>(null); useState<OptionType | null>(null);
@@ -100,50 +105,21 @@ const UniformityForm = ({
setInputValue: setLocationSelectInputValue, setInputValue: setLocationSelectInputValue,
options: locationOptions, options: locationOptions,
isLoadingOptions: isLoadingLocations, isLoadingOptions: isLoadingLocations,
} = useSelect(LocationApi.basePath, 'id', 'name', 'search', { loadMore: loadMoreLocations,
page: '1', hasMore: hasMoreLocations,
limit: '100', } = 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 ===== // ===== APPROVED PROJECT FLOCK KANDANGS =====
const approvedProjectFlockKandangsUrl = useMemo(() => { const approvedProjectFlockKandangsUrl = useMemo(() => {
const params = new URLSearchParams({ const params = new URLSearchParams({
@@ -168,8 +144,9 @@ const UniformityForm = ({
const kandangOptions = useMemo(() => { const kandangOptions = useMemo(() => {
let options: OptionType[] = []; let options: OptionType[] = [];
if (selectedProjectFlock && projectFlocksDataList) { if (selectedProjectFlock && isResponseSuccess(projectFlocksRawData)) {
const selectedProjectFlockData = projectFlocksDataList.find( const data = projectFlocksRawData.data as unknown as ProjectFlock[];
const selectedProjectFlockData = data.find(
(pf) => pf.id === selectedProjectFlock.value (pf) => pf.id === selectedProjectFlock.value
); );
@@ -196,7 +173,7 @@ const UniformityForm = ({
return options; return options;
}, [ }, [
selectedProjectFlock, selectedProjectFlock,
projectFlocksDataList, projectFlocksRawData,
approvedProjectFlockKandangs, approvedProjectFlockKandangs,
formType, formType,
]); ]);
@@ -313,6 +290,10 @@ const UniformityForm = ({
formik.setFieldValue('location_id', locationId); formik.setFieldValue('location_id', locationId);
setSelectedLocation(location); setSelectedLocation(location);
setSelectedProjectFlock(null);
setSelectedProjectFlockLocationId(
location ? location.value.toString() : ''
);
}, },
[] []
); );
@@ -513,6 +494,7 @@ const UniformityForm = ({
options={locationOptions} options={locationOptions}
onInputChange={setLocationSelectInputValue} onInputChange={setLocationSelectInputValue}
isLoading={isLoadingLocations} isLoading={isLoadingLocations}
onMenuScrollToBottom={loadMoreLocations}
isError={ isError={
formik.touched.location_id && Boolean(formik.errors.location_id) formik.touched.location_id && Boolean(formik.errors.location_id)
} }
@@ -530,6 +512,7 @@ const UniformityForm = ({
options={projectFlockOptions} options={projectFlockOptions}
onInputChange={setProjectFlockSearchValue} onInputChange={setProjectFlockSearchValue}
isLoading={isLoadingProjectFlocks} isLoading={isLoadingProjectFlocks}
onMenuScrollToBottom={loadMoreProjectFlocks}
isDisabled={!formik.values.location_id} isDisabled={!formik.values.location_id}
isError={ isError={
formik.touched.project_flock_id && formik.touched.project_flock_id &&
@@ -156,8 +156,11 @@ const PurchaseOrderAcceptApprovalForm = ({
setInputValue: setExpeditionsSelectInputValue, setInputValue: setExpeditionsSelectInputValue,
options: expeditionVendors, options: expeditionVendors,
isLoadingOptions: isLoadingExpeditions, isLoadingOptions: isLoadingExpeditions,
loadMore: loadMoreExpeditions,
hasMore: hasMoreExpeditions,
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', { } = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', {
category: 'BOP', category: 'BOP',
flag: 'EKSPEDISI',
}); });
// ===== FORM CONFIGURATION ===== // ===== FORM CONFIGURATION =====
@@ -183,8 +186,8 @@ const PurchaseOrderAcceptApprovalForm = ({
purchase_item_id: formItem.purchase_item_id || 0, purchase_item_id: formItem.purchase_item_id || 0,
received_date: formItem.received_date || '', received_date: formItem.received_date || '',
travel_number: formItem.travel_number || '', travel_number: formItem.travel_number || '',
vehicle_number: formItem.vehicle_number || '', vehicle_number: formItem.vehicle_number || null,
expedition_vendor_id: formItem.expedition_vendor_id || 0, expedition_vendor_id: formItem.expedition_vendor_id || null,
received_qty: received_qty:
typeof formItem.received_qty === 'string' typeof formItem.received_qty === 'string'
? parseFloat(formItem.received_qty) || 0 ? parseFloat(formItem.received_qty) || 0
@@ -192,10 +195,13 @@ const PurchaseOrderAcceptApprovalForm = ({
transport_per_item: transport_per_item:
typeof formItem.transport_per_item === 'string' typeof formItem.transport_per_item === 'string'
? parseFloat(formItem.transport_per_item) || 0 ? 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) { switch (type) {
@@ -403,22 +409,13 @@ const PurchaseOrderAcceptApprovalForm = ({
Dokumen Surat Jalan Dokumen Surat Jalan
<span className='text-error'>*</span> <span className='text-error'>*</span>
</th> </th>
<th> <th>Nomor Kendaraan</th>
Nomor Kendaraan <th>Vendor Ekspedisi</th>
<span className='text-error'>*</span>
</th>
<th>
Vendor Ekspedisi
<span className='text-error'>*</span>
</th>
<th> <th>
Jumlah Diterima Jumlah Diterima
<span className='text-error'>*</span> <span className='text-error'>*</span>
</th> </th>
<th> <th>Transport/Item</th>
Transport/Item
<span className='text-error'>*</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -536,7 +533,6 @@ const PurchaseOrderAcceptApprovalForm = ({
</td> </td>
<td> <td>
<TextInput <TextInput
required
name={`items.${idx}.vehicle_number`} name={`items.${idx}.vehicle_number`}
type='text' type='text'
value={formItem?.vehicle_number || ''} value={formItem?.vehicle_number || ''}
@@ -562,7 +558,6 @@ const PurchaseOrderAcceptApprovalForm = ({
</td> </td>
<td> <td>
<SelectInput <SelectInput
required
isClearable={true} isClearable={true}
value={formItem?.expedition_vendor} value={formItem?.expedition_vendor}
key={`expedition-vendor-${idx}`} key={`expedition-vendor-${idx}`}
@@ -570,6 +565,8 @@ const PurchaseOrderAcceptApprovalForm = ({
expeditionVendorChangeHandler(idx, val) expeditionVendorChangeHandler(idx, val)
} }
options={getExpeditionVendorOptions()} options={getExpeditionVendorOptions()}
isLoading={isLoadingExpeditions}
onMenuScrollToBottom={loadMoreExpeditions}
isError={ isError={
isRepeaterInputError(idx, 'expedition_vendor_id') isRepeaterInputError(idx, 'expedition_vendor_id')
.isError .isError
@@ -629,7 +626,6 @@ const PurchaseOrderAcceptApprovalForm = ({
</td> </td>
<td> <td>
<NumberInput <NumberInput
required
name={`items.${idx}.transport_per_item`} name={`items.${idx}.transport_per_item`}
value={formItem?.transport_per_item || ''} value={formItem?.transport_per_item || ''}
onChange={(e) => onChange={(e) =>
@@ -680,7 +676,6 @@ const PurchaseOrderAcceptApprovalForm = ({
<div className={'col-span-2 my-2'}> <div className={'col-span-2 my-2'}>
<FileInput <FileInput
required
name='travel_documents' name='travel_documents'
label='Dokumen Surat Jalan' label='Dokumen Surat Jalan'
accept='.pdf,.jpg,.jpeg,.png' accept='.pdf,.jpg,.jpeg,.png'
@@ -38,16 +38,16 @@ type PurchaseRequestAcceptApprovalFormSchemaType = {
purchase_item_id: number; purchase_item_id: number;
received_date: string; received_date: string;
travel_number: string; travel_number: string;
vehicle_number: string; vehicle_number?: string | null;
expedition_vendor?: { expedition_vendor?: {
value: number; value: number;
label: string; label: string;
} | null; } | null;
expedition_vendor_id: number; expedition_vendor_id?: number | null;
received_qty: number | string; received_qty: number | string;
transport_per_item: number | string; transport_per_item?: number | string | null;
}[]; }[];
travel_documents: File[]; travel_documents?: (File | null | undefined)[] | null;
}; };
export type PurchaseStaffApprovalItemSchema = { export type PurchaseStaffApprovalItemSchema = {
@@ -75,14 +75,14 @@ export type PurchaseAcceptApprovalItemSchema = {
purchase_item_id: number; purchase_item_id: number;
received_date: string; received_date: string;
travel_number: string; travel_number: string;
vehicle_number: string; vehicle_number?: string | null;
expedition_vendor?: { expedition_vendor?: {
value: number; value: number;
label: string; label: string;
} | null; } | null;
expedition_vendor_id: number; expedition_vendor_id?: number | null;
received_qty: number | string; received_qty: number | string;
transport_per_item: number | string; transport_per_item?: number | string | null;
}; };
export type PurchaseDeleteItemsSchema = { export type PurchaseDeleteItemsSchema = {
@@ -184,24 +184,19 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
.required('No. Surat jalan wajib diisi!') .required('No. Surat jalan wajib diisi!')
.typeError('No. Surat jalan wajib diisi!'), .typeError('No. Surat jalan wajib diisi!'),
vehicle_number: Yup.string() vehicle_number: Yup.string()
.required('Nomor kendaraan wajib diisi!') .nullable()
.typeError('Nomor kendaraan wajib diisi!'), .optional()
.typeError('Nomor kendaraan harus berupa plat nomor!'),
expedition_vendor: Yup.object({ expedition_vendor: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).nullable(), })
.nullable()
.optional(),
expedition_vendor_id: Yup.number() expedition_vendor_id: Yup.number()
.min(1, 'Vendor ekspedisi wajib diisi!') .nullable()
.required('Vendor ekspedisi wajib diisi!') .optional()
.test( .typeError('Vendor ekspedisi harus berupa angka!'),
'is-valid-expedition-vendor',
'Vendor ekspedisi harus dipilih!',
function (value) {
if (!this.parent.expedition_vendor) return true;
return Boolean(value && value > 0);
}
)
.typeError('Vendor ekspedisi harus dipilih!'),
received_qty: Yup.mixed<string | number>() received_qty: Yup.mixed<string | number>()
.required('Jumlah diterima wajib diisi!') .required('Jumlah diterima wajib diisi!')
.test( .test(
@@ -217,13 +212,14 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
) )
.typeError('Jumlah diterima harus berupa angka!'), .typeError('Jumlah diterima harus berupa angka!'),
transport_per_item: Yup.mixed<string | number>() transport_per_item: Yup.mixed<string | number>()
.required('Biaya transport per item wajib diisi!') .nullable()
.optional()
.test( .test(
'is-valid-transport-per-item', 'is-valid-transport-per-item',
'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!', 'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!',
function (value) { function (value) {
if (value === '' || value === null || value === undefined) if (value === '' || value === null || value === undefined)
return false; return true;
const numValue = const numValue =
typeof value === 'string' ? parseFloat(value) : value; typeof value === 'string' ? parseFloat(value) : value;
return !isNaN(numValue) && numValue >= 0; return !isNaN(numValue) && numValue >= 0;
@@ -389,16 +385,17 @@ export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema<PurchaseR
travel_documents: Yup.array() travel_documents: Yup.array()
.of( .of(
Yup.mixed<File>() Yup.mixed<File>()
.required('Dokumen surat jalan wajib diupload!') .nullable()
.optional()
.test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => { .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => {
if (!value) return true; if (!value) return true;
if (value instanceof File) return value.size <= 5 * 1024 * 1024; if (value instanceof File) return value.size <= 5 * 1024 * 1024;
return true; return true;
}) })
) )
.required('Dokumen surat jalan wajib diupload!') .nullable()
.min(1, 'Minimal upload 1 dokumen surat jalan!') .optional()
.typeError('Dokumen surat jalan wajib diupload!'), .typeError('Dokumen surat jalan harus berupa array!'),
}); });
export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType = export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType =
@@ -63,11 +63,9 @@ const PurchaseRequestForm = ({
useState(''); useState('');
const [formErrorList, setFormErrorList] = useState<string[]>([]); const [formErrorList, setFormErrorList] = useState<string[]>([]);
// ===== TYPE DEFINITIONS ===== const [selectedArea, setSelectedArea] = useState('');
interface ProductOptionType { const [selectedLocation, setSelectedLocation] = useState('');
value: number; const [disabledLocation, setDisabledLocation] = useState(true);
label: string;
}
// ===== UTILITY FUNCTIONS ===== // ===== UTILITY FUNCTIONS =====
const isRepeaterInputError = ( const isRepeaterInputError = (
@@ -160,11 +158,35 @@ const PurchaseRequestForm = ({
isLoadingOptions: isLoadingAreas, isLoadingOptions: isLoadingAreas,
} = useSelect(AreaApi.basePath, 'id', 'name', 'search'); } = 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 { const {
inputValue: warehouseSelectInputValue, inputValue: warehouseSelectInputValue,
setInputValue: setWarehouseSelectInputValue, setInputValue: setWarehouseSelectInputValue,
options: warehouseOptions,
isLoadingOptions: isLoadingWarehouses, 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 ===== // ===== FORM CONFIGURATION =====
const formikInitialValues = useMemo<PurchaseRequestFormValues>( const formikInitialValues = useMemo<PurchaseRequestFormValues>(
@@ -267,70 +289,6 @@ const PurchaseRequestForm = ({
return data; return data;
}, [supplierData]); }, [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 addPurchaseItem = () => {
const newItems = [ const newItems = [
...(formik.values.items || []), ...(formik.values.items || []),
@@ -407,6 +365,18 @@ const PurchaseRequestForm = ({
} }
}, [formik.values.supplier_id]); }, [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 ===== // ===== FORM HANDLERS =====
const handleSupplierChange = useCallback( const handleSupplierChange = useCallback(
(val: OptionType | OptionType[] | null) => { (val: OptionType | OptionType[] | null) => {
@@ -445,6 +415,16 @@ const PurchaseRequestForm = ({
formik.setFieldValue('area_id', (area as OptionType)?.value || 0); formik.setFieldValue('area_id', (area as OptionType)?.value || 0);
formik.setFieldTouched('area', true); formik.setFieldTouched('area', true);
formik.setFieldValue('area', area); 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.setFieldValue('location_id', (location as OptionType)?.value || 0);
formik.setFieldTouched('location', true); formik.setFieldTouched('location', true);
formik.setFieldValue('location', location); formik.setFieldValue('location', location);
setSelectedLocation((location as OptionType)?.value as string);
}, },
[] []
); );
@@ -596,10 +578,15 @@ const PurchaseRequestForm = ({
placeholder='Pilih Lokasi...' placeholder='Pilih Lokasi...'
value={formik.values.location} value={formik.values.location}
onChange={handleLocationChange} onChange={handleLocationChange}
options={locationOptions} options={
selectedArea != '' || initialValues?.area?.id
? locationOptions
: []
}
onInputChange={setLocationSelectInputValue} onInputChange={setLocationSelectInputValue}
isLoading={isLoadingLocations} isLoading={isLoadingLocations}
isDisabled={type === 'detail'} onMenuScrollToBottom={loadMoreLocations}
isDisabled={type === 'detail' || disabledLocation}
isClearable={type !== 'detail'} isClearable={type !== 'detail'}
/> />
@@ -713,6 +700,7 @@ const PurchaseRequestForm = ({
options={warehouseOptions} options={warehouseOptions}
onInputChange={setWarehouseSelectInputValue} onInputChange={setWarehouseSelectInputValue}
isLoading={isLoadingWarehouses} isLoading={isLoadingWarehouses}
onMenuScrollToBottom={loadMoreWarehouses}
isError={ isError={
isRepeaterInputError(idx, 'warehouse_id').isError isRepeaterInputError(idx, 'warehouse_id').isError
} }
@@ -732,9 +720,9 @@ const PurchaseRequestForm = ({
required required
value={item.product ?? undefined} value={item.product ?? undefined}
onChange={(val) => { onChange={(val) => {
const product = val as ProductOptionType | null; const product = val as OptionType | null;
const productId = const productId =
(product as ProductOptionType)?.value || 0; (product as OptionType)?.value || 0;
formik.setFieldTouched( formik.setFieldTouched(
`items.${idx}.product`, `items.${idx}.product`,
@@ -168,7 +168,7 @@ const DailyMarketingsTable = ({
]; ];
useEffect(() => { useEffect(() => {
console.log({ sorting }); // console.log({ sorting });
if (sorting.length === 1) { if (sorting.length === 1) {
onFilterByChange(sorting[0].id); onFilterByChange(sorting[0].id);
@@ -177,10 +177,12 @@ interface CustomerPaymentExportPDFParams {
data: CustomerPaymentReport[]; data: CustomerPaymentReport[];
params?: { params?: {
customer_name?: string; customer_name?: string;
sales?: string; // TODO: Uncomment when BE is ready
// sales?: string;
start_date?: string; start_date?: string;
end_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'); paramsText.push('Semua Customer');
} }
if (params?.sales) { // TODO: Uncomment when BE is ready
paramsText.push(`Sales: ${params.sales}`); // if (params?.sales) {
} // paramsText.push(`Sales: ${params.sales}`);
// }
if (params?.start_date && params?.end_date) { if (params?.start_date && params?.end_date) {
const startDate = formatDate(params.start_date, 'DD MMM YYYY'); const startDate = formatDate(params.start_date, 'DD MMM YYYY');
@@ -242,9 +245,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
: '-'} : '-'}
</Text> </Text>
</View> </View>
<View style={pdfStyles.parameterBadge}> {/* TODO: Uncomment when BE is ready */}
{/* <View style={pdfStyles.parameterBadge}>
<Text>Filter Tanggal: Tanggal DO</Text> <Text>Filter Tanggal: Tanggal DO</Text>
</View> </View> */}
<View style={pdfStyles.parameterBadge}> <View style={pdfStyles.parameterBadge}>
<Text> <Text>
Customer: {params.params?.customer_name || 'Semua Customer'} Customer: {params.params?.customer_name || 'Semua Customer'}
@@ -280,7 +284,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
<View style={[pdfStyles.tableCellHeader, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellHeader, { flex: 0.8 }]}>
<Text>Aging</Text> <Text>Aging</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}> <View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
<Text>Referensi</Text> <Text>Referensi</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
@@ -298,15 +302,9 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Harga Awal</Text> <Text>Harga Awal</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>CN</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Harga Akhir</Text> <Text>Harga Akhir</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Pajak</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Total</Text> <Text>Total</Text>
</View> </View>
@@ -343,13 +341,15 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
</View> </View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text> <Text>
{item.do_date ? formatDate(item.do_date, 'DD MMM YY') : '-'} {item.trans_date
? formatDate(item.trans_date, 'DD MMM YY')
: '-'}
</Text> </Text>
</View> </View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text> <Text>
{item.realization_date {item.delivery_date
? formatDate(item.realization_date, 'DD MMM YY') ? formatDate(item.delivery_date, 'DD MMM YY')
: '-'} : '-'}
</Text> </Text>
</View> </View>
@@ -358,11 +358,15 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
{item.aging_day ? formatNumber(item.aging_day) : '-'} hari {item.aging_day ? formatNumber(item.aging_day) : '-'} hari
</Text> </Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}> <View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text>{item.reference || '-'}</Text> <Text>{item.reference || '-'}</Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}> <View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text>{item.vehicle_plate || '-'}</Text> <Text>
{Array.isArray(item.vehicle_numbers)
? item.vehicle_numbers.join(', ')
: item.vehicle_numbers || '-'}
</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.qty)}</Text> <Text>{formatNumber(item.qty)}</Text>
@@ -376,20 +380,14 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.price)}</Text> <Text>{formatCurrency(item.price)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatCurrency(item.credit_note)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.final_price)}</Text> <Text>{formatCurrency(item.final_price)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatNumber(item.ppn)}%</Text> <Text>{formatCurrency(item.total_price)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.total)}</Text> <Text>{formatCurrency(item.payment_amount)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.payment)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text style={pdfStyles.textError}> <Text style={pdfStyles.textError}>
@@ -397,30 +395,32 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
</Text> </Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}> <View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
{item.notes ? ( {item.status ? (
<Text>{item.notes}</Text>
) : (
<View <View
style={[ style={[
pdfStyles.badge, pdfStyles.badge,
item.accounts_receivable === 0 item.status === 'LUNAS'
? pdfStyles.badgeLunas ? pdfStyles.badgeLunas
: pdfStyles.badgeBelumLunas, : pdfStyles.badgeBelumLunas,
]} ]}
> >
<Text> <Text>
{item.accounts_receivable === 0 {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
? 'Lunas'
: 'Belum Lunas'}
</Text> </Text>
</View> </View>
) : (
<Text>-</Text>
)} )}
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}> <View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text>{item.pickup_info || '-'}</Text> <Text>
{Array.isArray(item.pickup_info)
? item.pickup_info.join(', ')
: item.pickup_info || '-'}
</Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}> <View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text>{item.sales_marketing || '-'}</Text> <Text>{item.sales_person || '-'}</Text>
</View> </View>
</View> </View>
))} ))}
@@ -440,7 +440,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
<View style={[pdfStyles.tableCell, { flex: 0.8 }]}> <View style={[pdfStyles.tableCell, { flex: 0.8 }]}>
<Text></Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}> <View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text></Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}> <View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
@@ -458,25 +458,13 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
<Text></Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text> <Text></Text>
{formatCurrency(
customerReport.summary.total_initial_amount
)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>
{formatCurrency(customerReport.summary.total_credit_note)}
</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text> <Text>
{formatCurrency(customerReport.summary.total_final_amount)} {formatCurrency(customerReport.summary.total_final_amount)}
</Text> </Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text> <Text>
{formatCurrency(customerReport.summary.total_grand_amount)} {formatCurrency(customerReport.summary.total_grand_amount)}
@@ -24,30 +24,30 @@ export const generateCustomerPaymentExcel = (
const excelData: { [key: string]: string | number }[] = customerData.map( const excelData: { [key: string]: string | number }[] = customerData.map(
(item, index) => ({ (item, index) => ({
No: index + 1, No: index + 1,
'Tanggal DO/Bayar': item.do_date 'Tanggal DO/Bayar': item.trans_date
? formatDate(item.do_date, 'DD MMM YYYY') ? formatDate(item.trans_date, 'DD MMM YYYY')
: '', : '',
'Tanggal Realisasi': item.realization_date 'Tanggal Realisasi': item.delivery_date
? formatDate(item.realization_date, 'DD MMM YYYY') ? formatDate(item.delivery_date, 'DD MMM YYYY')
: '', : '',
Aging: formatNumber(item.aging_day || 0), Aging: formatNumber(item.aging_day || 0),
Referensi: item.reference || '', Referensi: item.reference || '',
'Nomor Polisi': Array.isArray(item.vehicle_plate) 'Nomor Polisi': Array.isArray(item.vehicle_numbers)
? item.vehicle_plate.join(', ') ? item.vehicle_numbers.join(', ')
: '', : '',
'Ekor/Qty': formatNumber(item.qty || 0), 'Ekor/Qty': formatNumber(item.qty || 0),
'Berat (Kg)': formatNumber(item.weight || 0), 'Berat (Kg)': formatNumber(item.weight || 0),
AVG: formatNumber(item.average_weight || 0), AVG: formatNumber(item.average_weight || 0),
'Harga Awal': formatCurrency(item.price || 0), 'Harga Awal': formatCurrency(item.price || 0),
CN: formatCurrency(item.credit_note || 0),
'Harga Akhir': formatCurrency(item.final_price || 0), 'Harga Akhir': formatCurrency(item.final_price || 0),
'PPN (%)': formatNumber(item.ppn || 0), Total: formatCurrency(item.total_price || 0),
Total: formatCurrency(item.total || 0), Pembayaran: formatCurrency(item.payment_amount || 0),
Pembayaran: formatCurrency(item.payment || 0),
'Saldo Piutang': formatCurrency(item.accounts_receivable || 0), 'Saldo Piutang': formatCurrency(item.accounts_receivable || 0),
Keterangan: item.notes || '', Keterangan: item.status || '',
Pengambilan: item.pickup_info || '', Pengambilan: Array.isArray(item.pickup_info)
'Sales/Marketing': item.sales_marketing || '', ? item.pickup_info.join(', ')
: '',
'Sales/Marketing': item.sales_person || '',
}) })
); );
@@ -62,14 +62,10 @@ export const generateCustomerPaymentExcel = (
'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0), 'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0),
'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0), 'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0),
AVG: '', AVG: '',
'Harga Awal': formatCurrency( 'Harga Awal': '',
customerReport.summary.total_initial_amount || 0
),
CN: formatCurrency(customerReport.summary.total_credit_note || 0),
'Harga Akhir': formatCurrency( 'Harga Akhir': formatCurrency(
customerReport.summary.total_final_amount || 0 customerReport.summary.total_final_amount || 0
), ),
'PPN (%)': '',
Total: formatCurrency(customerReport.summary.total_grand_amount || 0), Total: formatCurrency(customerReport.summary.total_grand_amount || 0),
Pembayaran: formatCurrency(customerReport.summary.total_payment || 0), Pembayaran: formatCurrency(customerReport.summary.total_payment || 0),
'Saldo Piutang': formatCurrency( 'Saldo Piutang': formatCurrency(
@@ -94,9 +90,7 @@ export const generateCustomerPaymentExcel = (
{ wch: 12 }, // Berat { wch: 12 }, // Berat
{ wch: 10 }, // AVG { wch: 10 }, // AVG
{ wch: 15 }, // Harga Awal { wch: 15 }, // Harga Awal
{ wch: 10 }, // CN
{ wch: 15 }, // Harga Akhir { wch: 15 }, // Harga Akhir
{ wch: 10 }, // PPN
{ wch: 15 }, // Total { wch: 15 }, // Total
{ wch: 15 }, // Pembayaran { wch: 15 }, // Pembayaran
{ wch: 15 }, // Saldo Piutang { wch: 15 }, // Saldo Piutang
@@ -47,6 +47,8 @@ const CustomerPaymentTab = () => {
const [filterCustomer, setFilterCustomer] = useState<typeof customerOptions>( const [filterCustomer, setFilterCustomer] = useState<typeof customerOptions>(
[] []
); );
// TODO: Uncomment when BE is ready
// const [filterSales, setFilterSales] = useState<typeof salesOptions>([]);
const [filterSales, setFilterSales] = useState<typeof salesOptions>([]); const [filterSales, setFilterSales] = useState<typeof salesOptions>([]);
const [filterStartDate, setFilterStartDate] = useState(''); const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState('');
@@ -61,6 +63,7 @@ const CustomerPaymentTab = () => {
hasMore: hasMoreCustomers, hasMore: hasMoreCustomers,
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search'); } = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
// TODO: Uncomment when BE is ready
const { const {
options: salesOptions, options: salesOptions,
setInputValue: setSalesInputValue, setInputValue: setSalesInputValue,
@@ -135,23 +138,18 @@ const CustomerPaymentTab = () => {
count += 1; count += 1;
} }
// Sales filter // TODO: Uncomment when BE is ready
if (filterSales.length > 0) { // // Sales filter
count += 1; // if (filterSales.length > 0) {
} // count += 1;
// }
// Filter by (always count if submitted)
if (isSubmitted) {
count += 1;
}
return count; return count;
}, [ }, [
filterStartDate, filterStartDate,
filterEndDate, filterEndDate,
filterCustomer, filterCustomer,
filterSales, // filterSales,
isSubmitted,
]); ]);
const hasFilters = activeFiltersCount > 0; const hasFilters = activeFiltersCount > 0;
@@ -165,11 +163,12 @@ const CustomerPaymentTab = () => {
filterCustomer.length > 0 filterCustomer.length > 0
? filterCustomer.map((v) => String(v.value)).join(',') ? filterCustomer.map((v) => String(v.value)).join(',')
: undefined, : undefined,
sales_id: // TODO: Uncomment when BE is ready
filterSales.length > 0 // sales_id:
? filterSales.map((v) => String(v.value)).join(',') // filterSales.length > 0
: undefined, // ? filterSales.map((v) => String(v.value)).join(',')
filter_by: 'do_date' as const, // : undefined,
// filter_by: 'do_date' as const,
start_date: filterStartDate || undefined, start_date: filterStartDate || undefined,
end_date: filterEndDate || undefined, end_date: filterEndDate || undefined,
page: currentPage, page: currentPage,
@@ -182,8 +181,8 @@ const CustomerPaymentTab = () => {
([, params]) => ([, params]) =>
FinanceApi.getCustomerPaymentReport( FinanceApi.getCustomerPaymentReport(
params.customer_id, params.customer_id,
params.sales_id, undefined, // TODO: Change to params.sales_id when BE is ready
params.filter_by, undefined, // TODO: Change to params.filter_by when BE is ready
params.start_date, params.start_date,
params.end_date, params.end_date,
params.page, params.page,
@@ -208,11 +207,11 @@ const CustomerPaymentTab = () => {
filterCustomer.length > 0 filterCustomer.length > 0
? filterCustomer.map((v) => String(v.value)).join(',') ? filterCustomer.map((v) => String(v.value)).join(',')
: undefined, : undefined,
sales_id: // TODO: Uncomment when BE is ready
filterSales.length > 0 // sales_id:
? filterSales.map((v) => String(v.value)).join(',') // filterSales.length > 0
: undefined, // ? filterSales.map((v) => String(v.value)).join(',')
filter_by: 'do_date' as const, // : undefined,
start_date: filterStartDate || undefined, start_date: filterStartDate || undefined,
end_date: filterEndDate || undefined, end_date: filterEndDate || undefined,
limit: 100, limit: 100,
@@ -221,8 +220,8 @@ const CustomerPaymentTab = () => {
const response = await FinanceApi.getCustomerPaymentReport( const response = await FinanceApi.getCustomerPaymentReport(
params.customer_id, params.customer_id,
params.sales_id, undefined, // TODO: Change to params.sales_id when BE is ready
params.filter_by, undefined, // TODO: Change to params.filter_by when BE is ready
params.start_date, params.start_date,
params.end_date, params.end_date,
params.page, params.page,
@@ -279,13 +278,15 @@ const CustomerPaymentTab = () => {
filterCustomer.length > 0 filterCustomer.length > 0
? filterCustomer.map((c) => c.label).join(', ') ? filterCustomer.map((c) => c.label).join(', ')
: undefined, : undefined,
sales: // TODO: Uncomment when BE is ready
filterSales.length > 0 // sales:
? filterSales.map((s) => s.label).join(', ') // filterSales.length > 0
: undefined, // ? filterSales.map((s) => s.label).join(', ')
// : undefined,
start_date: filterStartDate || undefined, start_date: filterStartDate || undefined,
end_date: filterEndDate || 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.'); toast.success('PDF berhasil dibuat dan diunduh.');
@@ -303,36 +304,39 @@ const CustomerPaymentTab = () => {
{ {
id: 'no', id: 'no',
header: 'No', header: 'No',
cell: (props) => props.row.index + 1, cell: (props) => props.row.index,
footer: () => <div className='font-semibold text-gray-900'>Total</div>, footer: () => <div className='font-semibold text-gray-900'>Total</div>,
}, },
{ {
id: 'do_date_or_payment_date', id: 'do_date_or_payment_date',
header: 'Tanggal DO/Bayar', header: 'Tanggal Jual/Bayar',
accessorKey: 'do_date', accessorKey: 'trans_date',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.do_date; const value = props.row.original.trans_date;
return formatDate(value, 'DD MMM YYYY'); return value ? formatDate(value, 'DD MMM YYYY') : '-';
}, },
}, },
{ {
id: 'realization_date', id: 'realization_date',
header: 'Tanggal Realisasi', header: 'Tanggal Realisasi',
accessorKey: 'realization_date', accessorKey: 'delivery_date',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.realization_date; const value = props.row.original.delivery_date;
return formatDate(value, 'DD MMM YYYY'); return value ? formatDate(value, 'DD MMM YYYY') : '-';
}, },
}, },
{ {
id: 'aging', id: 'aging',
header: 'Aging', header: 'Aging',
accessorKey: 'aging_day', accessorKey: 'aging_day',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.aging_day; const value = props.row.original.aging_day;
return ( return (
<div className='text-center'> <div className='text-center'>
{value ? formatNumber(value) : '-'} hari {value && value > 0 ? `${formatNumber(value)} hari` : '-'}
</div> </div>
); );
}, },
@@ -341,6 +345,7 @@ const CustomerPaymentTab = () => {
id: 'reference', id: 'reference',
header: 'Referensi', header: 'Referensi',
accessorKey: 'reference', accessorKey: 'reference',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.reference; const value = props.row.original.reference;
return value || '-'; return value || '-';
@@ -349,16 +354,18 @@ const CustomerPaymentTab = () => {
{ {
id: 'vehicle_plate', id: 'vehicle_plate',
header: 'Nomor Polisi', header: 'Nomor Polisi',
accessorKey: 'vehicle_plate', accessorKey: 'vehicle_numbers',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.vehicle_plate; const value = props.row.original.vehicle_numbers;
return value || '-'; return Array.isArray(value) ? value.join(', ') : value || '-';
}, },
}, },
{ {
id: 'qty', id: 'qty',
header: 'Ekor/Qty', header: 'Qty',
accessorKey: 'qty', accessorKey: 'qty',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.qty; const value = props.row.original.qty;
return <div className='text-right'>{formatNumber(value)}</div>; return <div className='text-right'>{formatNumber(value)}</div>;
@@ -373,6 +380,7 @@ const CustomerPaymentTab = () => {
id: 'weight', id: 'weight',
header: 'Berat (Kg)', header: 'Berat (Kg)',
accessorKey: 'weight', accessorKey: 'weight',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.weight; const value = props.row.original.weight;
return <div className='text-right'>{formatNumber(value)}</div>; return <div className='text-right'>{formatNumber(value)}</div>;
@@ -387,6 +395,7 @@ const CustomerPaymentTab = () => {
id: 'average_weight', id: 'average_weight',
header: 'AVG', header: 'AVG',
accessorKey: 'average_weight', accessorKey: 'average_weight',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.average_weight; const value = props.row.original.average_weight;
return <div className='text-right'>{formatNumber(value)}</div>; return <div className='text-right'>{formatNumber(value)}</div>;
@@ -399,34 +408,20 @@ const CustomerPaymentTab = () => {
id: 'price', id: 'price',
header: 'Harga Awal', header: 'Harga Awal',
accessorKey: 'price', accessorKey: 'price',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.price; const value = props.row.original.price;
return <div className='text-right'>{formatCurrency(value)}</div>; return <div className='text-right'>{formatCurrency(value)}</div>;
}, },
footer: () => ( footer: () => (
<div className='text-right font-semibold text-gray-900'> <div className='text-right font-semibold text-gray-900'>-</div>
{formatCurrency(summary.total_initial_amount) || '-'}
</div>
),
},
{
id: 'credit_note',
header: 'CN',
accessorKey: 'credit_note',
cell: (props) => {
const value = props.row.original.credit_note;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{formatCurrency(summary.total_credit_note) || '-'}
</div>
), ),
}, },
{ {
id: 'final_price', id: 'final_price',
header: 'Harga Akhir', header: 'Harga Akhir',
accessorKey: 'final_price', accessorKey: 'final_price',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.final_price; const value = props.row.original.final_price;
return <div className='text-right'>{formatCurrency(value)}</div>; return <div className='text-right'>{formatCurrency(value)}</div>;
@@ -437,24 +432,13 @@ const CustomerPaymentTab = () => {
</div> </div>
), ),
}, },
{
id: 'ppn',
header: 'PPN (%)',
accessorKey: 'ppn',
cell: (props) => {
const value = props.row.original.ppn;
return <div className='text-right'>{formatNumber(value)}%</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>-</div>
),
},
{ {
id: 'total', id: 'total',
header: 'Total', header: 'Total',
accessorKey: 'total', accessorKey: 'total_price',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.total; const value = props.row.original.total_price;
return <div className='text-right'>{formatCurrency(value)}</div>; return <div className='text-right'>{formatCurrency(value)}</div>;
}, },
footer: () => ( footer: () => (
@@ -466,9 +450,10 @@ const CustomerPaymentTab = () => {
{ {
id: 'payment', id: 'payment',
header: 'Pembayaran', header: 'Pembayaran',
accessorKey: 'payment', accessorKey: 'payment_amount',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.payment; const value = props.row.original.payment_amount;
return <div className='text-right'>{formatCurrency(value)}</div>; return <div className='text-right'>{formatCurrency(value)}</div>;
}, },
footer: () => ( footer: () => (
@@ -481,14 +466,25 @@ const CustomerPaymentTab = () => {
id: 'accounts_receivable', id: 'accounts_receivable',
header: 'Saldo Piutang', header: 'Saldo Piutang',
accessorKey: 'accounts_receivable', accessorKey: 'accounts_receivable',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.accounts_receivable; const value = props.row.original.accounts_receivable;
return ( return (
<div className='text-right text-error'>{formatCurrency(value)}</div> <div
className={`text-right font-semibold ${
value < 0 ? 'text-error' : ''
}`}
>
{formatCurrency(value)}
</div>
); );
}, },
footer: () => ( footer: () => (
<div className='text-right font-semibold text-gray-900'> <div
className={`text-right font-semibold ${
summary.total_accounts_receivable < 0 ? 'text-error' : ''
}`}
>
{formatCurrency(summary.total_accounts_receivable) || '-'} {formatCurrency(summary.total_accounts_receivable) || '-'}
</div> </div>
), ),
@@ -496,9 +492,10 @@ const CustomerPaymentTab = () => {
{ {
id: 'notes', id: 'notes',
header: 'Keterangan', header: 'Keterangan',
accessorKey: 'notes', accessorKey: 'status',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.notes; const value = props.row.original.status;
if (!value) { if (!value) {
return '-'; return '-';
@@ -513,7 +510,7 @@ const CustomerPaymentTab = () => {
status: getPaymentStatusIndicatorColor(value), status: getPaymentStatusIndicatorColor(value),
}} }}
> >
{getPaymentStatusText(value)} <span>{getPaymentStatusText(value)}</span>
</Badge> </Badge>
); );
}, },
@@ -522,17 +519,19 @@ const CustomerPaymentTab = () => {
id: 'pickup_info', id: 'pickup_info',
header: 'Pengambilan', header: 'Pengambilan',
accessorKey: 'pickup_info', accessorKey: 'pickup_info',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.pickup_info; const value = props.row.original.pickup_info;
return value || '-'; return Array.isArray(value) ? value.join(', ') : value || '-';
}, },
}, },
{ {
id: 'sales_marketing', id: 'sales_marketing',
header: 'Sales/Marketing', header: 'Sales/Marketing',
accessorKey: 'sales_marketing', accessorKey: 'sales_person',
enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.sales_marketing; const value = props.row.original.sales_person;
return value || '-'; return value || '-';
}, },
}, },
@@ -664,7 +663,8 @@ const CustomerPaymentTab = () => {
/> />
</div> </div>
<div> {/* TODO: Uncomment when BE is ready */}
{/* <div>
<SelectInputCheckbox <SelectInputCheckbox
label='Sales' label='Sales'
placeholder='Pilih Sales' placeholder='Pilih Sales'
@@ -679,9 +679,10 @@ const CustomerPaymentTab = () => {
onMenuScrollToBottom={loadMoreSales} onMenuScrollToBottom={loadMoreSales}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
/> />
</div> </div> */}
<div> {/* TODO: Uncomment when BE is ready */}
{/* <div>
<SelectInput <SelectInput
label='Filter Berdasarkan' label='Filter Berdasarkan'
placeholder='Pilih Filter Berdasarkan' placeholder='Pilih Filter Berdasarkan'
@@ -690,7 +691,7 @@ const CustomerPaymentTab = () => {
isDisabled={true} isDisabled={true}
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
/> />
</div> </div> */}
</div> </div>
{/* Action Buttons */} {/* Action Buttons */}
@@ -730,10 +731,7 @@ const CustomerPaymentTab = () => {
const summary = customerReport.summary || { const summary = customerReport.summary || {
total_qty: 0, total_qty: 0,
total_weight: 0, total_weight: 0,
total_initial_amount: 0,
total_credit_note: 0,
total_final_amount: 0, total_final_amount: 0,
total_ppn: 0,
total_grand_amount: 0, total_grand_amount: 0,
total_payment: 0, total_payment: 0,
total_accounts_receivable: 0, total_accounts_receivable: 0,
@@ -745,19 +743,27 @@ const CustomerPaymentTab = () => {
<Card <Card
key={customerReport.customer.id} key={customerReport.customer.id}
title={customerReport.customer.name} title={customerReport.customer.name}
subtitle={`(${customerReport.customer.address})`}
className={{ className={{
wrapper: 'w-full rounded-2xl', wrapper: 'w-full rounded-2xl',
body: 'p-0', body: 'p-0',
title: title:
'py-1.5 px-3 bg-[#0069E0] text-white text-lg font-normal', 'py-1.5 px-3 bg-[#0069E0] text-white text-lg font-normal',
subtitle:
'px-3 pb-1 bg-[#0069E0] text-white text-sm font-normal',
}} }}
variant='bordered' variant='bordered'
collapsible={true} collapsible={true}
> >
<Table <Table
data={customerReport.rows} data={[
{
accounts_receivable: customerReport.initial_balance,
} as CustomerPaymentReport['rows'][0],
...customerReport.rows,
]}
columns={tableColumns} columns={tableColumns}
pageSize={10} pageSize={customerReport.rows.length + 1}
renderFooter={customerReport.rows.length > 0} renderFooter={customerReport.rows.length > 0}
className={{ className={{
containerClassName: 'w-full', containerClassName: 'w-full',
@@ -777,6 +783,36 @@ const CustomerPaymentTab = () => {
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
paginationClassName: 'hidden', paginationClassName: 'hidden',
}} }}
renderCustomRow={(row) => {
if (row.index === 0) {
return (
<tr
className='hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200'
key={row.index}
>
<td
className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap'
colSpan={13}
></td>
<td className='px-4 py-3 text-xs whitespace-nowrap'>
<div
className={`text-right ${
row.original.accounts_receivable < 0
? 'text-error'
: ''
}`}
>
{formatCurrency(row.original.accounts_receivable)}
</div>
</td>
<td
className='px-4 py-3 text-xs text-gray-900 whitespace-nowrap'
colSpan={4}
></td>
</tr>
);
}
}}
/> />
</Card> </Card>
); );
@@ -226,7 +226,7 @@ const createPDFDocument = (
<Text>Rentang BW</Text> <Text>Rentang BW</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Sisa Ekor</Text> <Text>Sisa Butir</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Sisa Kg</Text> <Text>Sisa Kg</Text>
@@ -234,12 +234,6 @@ const createPDFDocument = (
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Rata-Rata Bobot (Kg)</Text> <Text>Rata-Rata Bobot (Kg)</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Produksi Telur (Butir)</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Produksi Telur (Kg)</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}> <View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
<Text>Feed (Supplier)</Text> <Text>Feed (Supplier)</Text>
</View> </View>
@@ -249,12 +243,6 @@ const createPDFDocument = (
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Rata-Rata Harga DOC</Text> <Text>Rata-Rata Harga DOC</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Nilai Nominal Telur</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>HPP Ayam</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>HPP Telur (RP/KG)</Text> <Text>HPP Telur (RP/KG)</Text>
</View> </View>
@@ -278,23 +266,15 @@ const createPDFDocument = (
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text>{group.label}</Text> <Text>{group.label}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(group.remaining_chicken_birds)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>
{formatNumber(group.remaining_chicken_weight_kg)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatNumber(group.avg_weight_kg)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(group.egg_production_pieces)}</Text> <Text>{formatNumber(group.egg_production_pieces)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(group.egg_production_kg)}</Text> <Text>{formatNumber(group.egg_production_kg)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatNumber(group.avg_weight_kg)}</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}> <View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text> <Text>
{group.feed_suppliers {group.feed_suppliers
@@ -318,17 +298,11 @@ const createPDFDocument = (
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(group.average_doc_price_rp)}</Text> <Text>{formatCurrency(group.average_doc_price_rp)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(group.egg_value_rp)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatCurrency(group.hpp_rp)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(group.egg_hpp_rp_per_kg)}</Text> <Text>{formatCurrency(group.egg_hpp_rp_per_kg)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(group.remaining_value_rp)}</Text> <Text>{formatCurrency(group.egg_value_rp)}</Text>
</View> </View>
</View> </View>
) )
@@ -356,16 +330,10 @@ const createPDFDocument = (
<Text>Rata-Rata Bobot (Kg)</Text> <Text>Rata-Rata Bobot (Kg)</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Sisa Ekor</Text> <Text>Sisa Butir</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Sisa Kg (Ayam)</Text> <Text>Sisa Kg (Telur)</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Produksi Telur (Butir)</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Produksi Telur (Kg)</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
<Text>Feed (Supplier)</Text> <Text>Feed (Supplier)</Text>
@@ -376,12 +344,6 @@ const createPDFDocument = (
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Rata-Rata Harga DOC</Text> <Text>Rata-Rata Harga DOC</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Nilai Nominal Telur</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>HPP Ayam</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>HPP Telur (RP/KG)</Text> <Text>HPP Telur (RP/KG)</Text>
</View> </View>
@@ -416,12 +378,6 @@ const createPDFDocument = (
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(item.avg_weight_kg)}</Text> <Text>{formatNumber(item.avg_weight_kg)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.remaining_chicken_birds)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.remaining_chicken_weight_kg)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.egg_production_pieces)}</Text> <Text>{formatNumber(item.egg_production_pieces)}</Text>
</View> </View>
@@ -451,17 +407,11 @@ const createPDFDocument = (
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.average_doc_price_rp)}</Text> <Text>{formatCurrency(item.average_doc_price_rp)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.egg_value_rp)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatCurrency(item.hpp_rp)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatCurrency(item.egg_hpp_rp_per_kg)}</Text> <Text>{formatCurrency(item.egg_hpp_rp_per_kg)}</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.remaining_value_rp)}</Text> <Text>{formatCurrency(item.egg_value_rp)}</Text>
</View> </View>
</View> </View>
))} ))}
@@ -335,10 +335,8 @@ const HppPerKandangTab = () => {
? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}` ? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}`
: '', : '',
'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0, 'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0,
'Sisa Ayam (Ekor)': item.remaining_chicken_birds || 0, 'Sisa Telur (Butir)': item.egg_production_pieces || 0,
'Sisa Ayam (KG)': item.remaining_chicken_weight_kg || 0, 'Sisa Telur (KG)': item.egg_production_kg || 0,
'Produksi Telur (Butir)': item.egg_production_pieces || 0,
'Produksi Telur (KG)': item.egg_production_kg || 0,
'Feed (Supplier)': 'Feed (Supplier)':
item.feed_suppliers item.feed_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name) ?.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) ?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '', .join(' | ') || '',
'Rata-Rata Harga DOC (RP)': item.average_doc_price_rp || 0, '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, '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', Kandang: 'ALL',
'Rentang Bobot': '-', 'Rentang Bobot': '-',
'Rata-Rata Bobot (KG)': summaryTotal?.average_weight_kg || 0, 'Rata-Rata Bobot (KG)': summaryTotal?.average_weight_kg || 0,
'Sisa Ayam (Ekor)': summaryTotal?.total_remaining_chicken_birds || 0, 'Sisa Telur (Butir)': summaryTotal?.total_egg_production_pieces || 0,
'Sisa Ayam (KG)': summaryTotal?.total_remaining_chicken_weight_kg || 0, 'Sisa Telur (KG)': summaryTotal?.total_egg_production_kg || 0,
'Produksi Telur (Butir)':
summaryTotal?.total_egg_production_pieces || 0,
'Produksi Telur (KG)': summaryTotal?.total_egg_production_kg || 0,
'Feed (Supplier)': allFeedSuppliers, 'Feed (Supplier)': allFeedSuppliers,
'DOC (Supplier)': allDocSuppliers, 'DOC (Supplier)': allDocSuppliers,
'Rata-Rata Harga DOC (RP)': 'Rata-Rata Harga DOC (RP)':
summaryTotal?.total_average_doc_price_rp || 0, 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, 'HPP Telur (RP/KG)': summaryTotal?.average_egg_hpp_rp_per_kg || 0,
'Nilai Nominal Sisa Ayam (RP)': 'Nilai Nominal Sisa Telur (RP)': summaryTotal?.total_egg_value_rp || 0,
summaryTotal?.total_remaining_value_rp || 0,
}); });
const worksheet = XLSX.utils.json_to_sheet(excelData); const worksheet = XLSX.utils.json_to_sheet(excelData);
@@ -383,17 +373,13 @@ const HppPerKandangTab = () => {
{ wch: 30 }, // Kandang { wch: 30 }, // Kandang
{ wch: 15 }, // Rentang Bobot { wch: 15 }, // Rentang Bobot
{ wch: 18 }, // Rata-Rata Bobot (KG) { wch: 18 }, // Rata-Rata Bobot (KG)
{ wch: 15 }, // Sisa Ayam (Ekor) { wch: 15 }, // Sisa Telur (Butir)
{ wch: 15 }, // Sisa Ayam (KG) { wch: 15 }, // Sisa Telur (KG)
{ wch: 18 }, // Produksi Telur (Butir)
{ wch: 18 }, // Produksi Telur (KG)
{ wch: 20 }, // Feed (Supplier) { wch: 20 }, // Feed (Supplier)
{ wch: 20 }, // DOC (Supplier) { wch: 20 }, // DOC (Supplier)
{ wch: 20 }, // Rata-Rata Harga DOC (RP) { 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: 18 }, // HPP Telur (RP/KG)
{ wch: 25 }, // Nilai Nominal Sisa Ayam (RP) { wch: 25 }, // Nilai Nominal Sisa Telur (RP)
]; ];
worksheet['!cols'] = colWidths; worksheet['!cols'] = colWidths;
@@ -533,37 +519,9 @@ const HppPerKandangTab = () => {
</div> </div>
), ),
}, },
{
id: 'remaining_chicken_birds',
header: 'Sisa Ayam (Ekor)',
accessorKey: 'remaining_chicken_birds',
cell: (props) => {
const value = props.row.original.remaining_chicken_birds;
return <div className='text-right'>{formatNumber(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{formatNumber(summaryTotal?.total_remaining_chicken_birds || 0)}
</div>
),
},
{
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 <div className='text-right'>{formatNumber(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)}
</div>
),
},
{ {
id: 'egg_production_pieces', id: 'egg_production_pieces',
header: 'Produksi Telur (Butir)', header: 'Sisa Telur (Butir)',
accessorKey: 'egg_production_pieces', accessorKey: 'egg_production_pieces',
cell: (props) => { cell: (props) => {
const value = props.row.original.egg_production_pieces; const value = props.row.original.egg_production_pieces;
@@ -577,7 +535,7 @@ const HppPerKandangTab = () => {
}, },
{ {
id: 'egg_production_kg', id: 'egg_production_kg',
header: 'Produksi Telur (KG)', header: 'Sisa Telur (KG)',
accessorKey: 'egg_production_kg', accessorKey: 'egg_production_kg',
cell: (props) => { cell: (props) => {
const value = props.row.original.egg_production_kg; const value = props.row.original.egg_production_kg;
@@ -585,7 +543,7 @@ const HppPerKandangTab = () => {
}, },
footer: () => ( footer: () => (
<div className='text-right font-semibold text-gray-900'> <div className='text-right font-semibold text-gray-900'>
{formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)} {formatNumber(summaryTotal?.total_egg_production_kg || 0)}
</div> </div>
), ),
}, },
@@ -639,34 +597,6 @@ const HppPerKandangTab = () => {
</div> </div>
), ),
}, },
{
id: 'egg_value_rp',
header: 'Nilai Nominal Telur (RP)',
accessorKey: 'egg_value_rp',
cell: (props) => {
const value = props.row.original.egg_value_rp;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{formatCurrency(summaryTotal?.total_egg_value_rp || 0)}
</div>
),
},
{
id: 'hpp_rp',
header: 'HPP Ayam (RP)',
accessorKey: 'hpp_rp',
cell: (props) => {
const value = props.row.original.hpp_rp;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{formatCurrency(summaryTotal?.total_hpp_rp || 0)}
</div>
),
},
{ {
id: 'egg_hpp_rp_per_kg', id: 'egg_hpp_rp_per_kg',
header: 'HPP Telur (RP/KG)', header: 'HPP Telur (RP/KG)',
@@ -682,16 +612,16 @@ const HppPerKandangTab = () => {
), ),
}, },
{ {
id: 'remaining_value_rp', id: 'egg_value_rp',
header: 'Nilai Nominal Sisa Ayam (RP)', header: 'Nilai Nominal Sisa Telur (RP)',
accessorKey: 'remaining_value_rp', accessorKey: 'egg_value_rp',
cell: (props) => { cell: (props) => {
const value = props.row.original.remaining_value_rp; const value = props.row.original.egg_value_rp;
return <div className='text-right'>{formatCurrency(value)}</div>; return <div className='text-right'>{formatCurrency(value)}</div>;
}, },
footer: () => ( footer: () => (
<div className='text-right font-semibold text-gray-900'> <div className='text-right font-semibold text-gray-900'>
{formatCurrency(summaryTotal?.total_remaining_value_rp || 0)} {formatCurrency(summaryTotal?.total_egg_value_rp || 0)}
</div> </div>
), ),
}, },
@@ -725,7 +655,7 @@ const HppPerKandangTab = () => {
key={'rekapitulasi-row'} key={'rekapitulasi-row'}
> >
<td <td
colSpan={15} colSpan={11}
className='px-4 py-3 text-gray-900 text-center font-semibold' className='px-4 py-3 text-gray-900 text-center font-semibold'
> >
Rekapitulasi per rentang bobot Rekapitulasi per rentang bobot
@@ -747,12 +677,6 @@ const HppPerKandangTab = () => {
<td className='text-right'> <td className='text-right'>
{formatNumber(item.avg_weight_kg)} {formatNumber(item.avg_weight_kg)}
</td> </td>
<td className='text-right'>
{formatNumber(item.remaining_chicken_birds)}
</td>
<td className='text-right'>
{formatNumber(item.remaining_chicken_weight_kg)}
</td>
<td className='text-right'> <td className='text-right'>
{formatNumber(item.egg_production_pieces)} {formatNumber(item.egg_production_pieces)}
</td> </td>
@@ -772,15 +696,11 @@ const HppPerKandangTab = () => {
<td className='text-right'> <td className='text-right'>
{formatCurrency(item.average_doc_price_rp)} {formatCurrency(item.average_doc_price_rp)}
</td> </td>
<td className='text-right'>
{formatCurrency(item.egg_value_rp)}
</td>
<td className='text-right'>{formatCurrency(item.hpp_rp)}</td>
<td className='text-right'> <td className='text-right'>
{formatCurrency(item.egg_hpp_rp_per_kg)} {formatCurrency(item.egg_hpp_rp_per_kg)}
</td> </td>
<td className='text-right'> <td className='text-right'>
{formatCurrency(item.remaining_value_rp)} {formatCurrency(item.egg_value_rp)}
</td> </td>
</tr> </tr>
); );
+1
View File
@@ -121,6 +121,7 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
'/report/finance/': [ '/report/finance/': [
'lti.repport.finance.list', 'lti.repport.finance.list',
'lti.repport.debtsupplier.list', 'lti.repport.debtsupplier.list',
'lti.repport.customerpayment.list',
], ],
// Inventory // Inventory
@@ -601,15 +601,15 @@ export function DailyChecklistContent() {
) => { ) => {
const taskId = taskIdsByPhaseActivityId[activityId]; const taskId = taskIdsByPhaseActivityId[activityId];
console.log('[CHECKBOX] Click detected:', { // console.log('[CHECKBOX] Click detected:', {
activityId, // activityId,
employeeId, // employeeId,
checked, // checked,
taskId, // taskId,
hasTaskId: !!taskId, // hasTaskId: !!taskId,
checklistStatus, // checklistStatus,
isEditable, // isEditable,
}); // });
if (!taskId) { if (!taskId) {
console.error('[CHECKBOX] No taskId found for activityId:', activityId); console.error('[CHECKBOX] No taskId found for activityId:', activityId);
@@ -638,10 +638,10 @@ export function DailyChecklistContent() {
}, },
}, },
}; };
console.log( // console.log(
'[CHECKBOX] State updated optimistically:', // '[CHECKBOX] State updated optimistically:',
updated[taskId]?.[employeeId] // updated[taskId]?.[employeeId]
); // );
return updated; return updated;
}); });
@@ -653,7 +653,7 @@ export function DailyChecklistContent() {
note: assignments[taskId]?.[employeeId]?.note || null, note: assignments[taskId]?.[employeeId]?.note || null,
}; };
console.log('[CHECKBOX] Saving to database:', payload); // console.log('[CHECKBOX] Saving to database:', payload);
const checkOrUncheckAssignmentRes = const checkOrUncheckAssignmentRes =
await DailyChecklistApi.checkOrUncheckAssignment(payload); await DailyChecklistApi.checkOrUncheckAssignment(payload);
@@ -679,7 +679,7 @@ export function DailyChecklistContent() {
return; return;
} }
console.log('[CHECKBOX] Saved successfully'); // console.log('[CHECKBOX] Saved successfully');
}; };
const handleNoteChange = async ( const handleNoteChange = async (
+11 -8
View File
@@ -1,7 +1,6 @@
import { BaseApiService } from '@/services/api/base'; import { BaseApiService } from '@/services/api/base';
import { BaseApiResponse } from '@/types/api/api-general'; import { BaseApiResponse } from '@/types/api/api-general';
import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
import { DebtSupplier } from '@/types/api/report/debt-supplier';
export class FinanceApiService extends BaseApiService< export class FinanceApiService extends BaseApiService<
CustomerPaymentReport, CustomerPaymentReport,
@@ -14,8 +13,11 @@ export class FinanceApiService extends BaseApiService<
async getCustomerPaymentReport( async getCustomerPaymentReport(
customer_id?: string, customer_id?: string,
// TODO: Uncomment when BE is ready
// sales_id?: string,
// filter_by?: 'do_date',
sales_id?: string, sales_id?: string,
filter_by?: 'do_date', filter_by?: 'do_date' | undefined,
start_date?: string, start_date?: string,
end_date?: string, end_date?: string,
page?: number, page?: number,
@@ -27,8 +29,9 @@ export class FinanceApiService extends BaseApiService<
method: 'GET', method: 'GET',
params: { params: {
customer_id: customer_id, customer_id: customer_id,
sales_id: sales_id, // TODO: Uncomment when BE is ready
filter_by: filter_by, // sales_id: sales_id,
// filter_by: filter_by,
start_date: start_date, start_date: start_date,
end_date: end_date, end_date: end_date,
page: page, 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( // export const FinanceApi = new FinanceApiService(
'http://localhost:4010/api/reports/finance' // 'http://localhost:4010/api/reports/finance'
); // );
+32 -47
View File
@@ -219,64 +219,30 @@ export type ClosingSales = BaseMetadata & BaseClosingSales;
// ====== FINANCE ====== // ====== FINANCE ======
export interface ClosingFinance { export interface ClosingFinance {
project_flock_id: number; hpp: ClosingFinanceHpp;
period: number;
project_type: string;
volume_base: ClosingFinanceVolumeBase;
hpp_purchases: ClosingFinanceHppPurchases;
profit_loss: ClosingFinanceProfitLoss; profit_loss: ClosingFinanceProfitLoss;
} }
export interface ClosingFinanceProfitLoss { export interface ClosingFinanceHpp {
title: string; items: HppItem[];
data: ProfitLossData; summary: HppSummary;
} }
export interface ClosingFinanceHppPurchases { export interface HppItem {
title: string; id: number;
hpp: GroupHppPurchase[]; category: string;
summary_hpp: HppPurchasesSummary; code: string;
}
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 {
label: string; label: string;
budgeting: HppPurchaseDataAmount; budgeting: HppPurchaseDataAmount;
realization: HppPurchaseDataAmount; realization: HppPurchaseDataAmount;
} }
export interface HppPurchaseData { export interface HppSummary {
type: string; label: string;
budgeting: HppPurchaseDataAmount; budgeting: HppPurchaseDataAmount;
realization: HppPurchaseDataAmount; realization: HppPurchaseDataAmount;
egg_budgeting: HppPurchaseDataAmount;
egg_realization: HppPurchaseDataAmount;
} }
export interface HppPurchaseDataAmount { export interface HppPurchaseDataAmount {
@@ -285,8 +251,27 @@ export interface HppPurchaseDataAmount {
amount: number; amount: number;
} }
export interface DataSummarySubTotal { export interface ClosingFinanceProfitLoss {
items: ProfitLossItem[];
summary: ProfitLossSummary;
}
export interface ProfitLossItem {
code: string;
label: 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_bird: number;
rp_per_kg: number; rp_per_kg: number;
amount: number; amount: number;
+2
View File
@@ -6,6 +6,7 @@ import { Location } from '@/types/api/master-data/location';
import { BaseApproval, BaseMetadata } from '@/types/api/api-general'; import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
import { Nonstock } from '@/types/api/master-data/nonstock'; import { Nonstock } from '@/types/api/master-data/nonstock';
import { ProductionStandard } from '@/types/api/master-data/production-standard'; import { ProductionStandard } from '@/types/api/master-data/production-standard';
import { Warehouse } from '@/types/api/master-data/warehouse';
export type BaseProjectFlock = { export type BaseProjectFlock = {
id: number; id: number;
@@ -71,6 +72,7 @@ export type ProjectFlockKandangLookup = {
kandang_id: number; kandang_id: number;
kandang: Kandang; kandang: Kandang;
project_flock: ProjectFlock; project_flock: ProjectFlock;
warehouse: Warehouse;
quantity: number; quantity: number;
available_quantity?: number; available_quantity?: number;
population: number; population: number;
+4 -4
View File
@@ -120,12 +120,12 @@ export type CreateAcceptApprovalRequestPayload = {
purchase_item_id: number; purchase_item_id: number;
received_date: string; received_date: string;
travel_number: string; travel_number: string;
vehicle_number: string; vehicle_number?: string | null;
expedition_vendor_id: number; expedition_vendor_id?: number | null;
received_qty: number; received_qty: number;
transport_per_item: number; transport_per_item?: number | null;
}[]; }[];
travel_documents?: File[]; travel_documents?: File[] | null;
}; };
export type DeletePurchaseRequestItemPayload = { export type DeletePurchaseRequestItemPayload = {
+12 -15
View File
@@ -2,34 +2,30 @@ import { BaseCustomer } from '@/types/api/master-data/customer';
import { BaseMetadata } from '@/types/api/api-general'; import { BaseMetadata } from '@/types/api/api-general';
export type CustomerPaymentRow = { export type CustomerPaymentRow = {
id: number; transaction_type: string;
do_date: string; transaction_id: number;
realization_date: string; trans_date: string;
aging_day: number | null; delivery_date: string | null;
reference: string; reference: string;
vehicle_plate: string[]; vehicle_numbers: string[];
qty: number; qty: number;
weight: number; weight: number;
average_weight: number; average_weight: number;
price: number; price: number;
credit_note: number;
final_price: number; final_price: number;
ppn: number; total_price: number;
total: number; payment_amount: number;
payment: number;
accounts_receivable: number; accounts_receivable: number;
notes: string; aging_day: number | null;
pickup_info: string; status: string;
sales_marketing: string; pickup_info: string[];
sales_person: string;
}; };
export type CustomerPaymentSummary = { export type CustomerPaymentSummary = {
total_qty: number; total_qty: number;
total_weight: number; total_weight: number;
total_initial_amount: number;
total_credit_note: number;
total_final_amount: number; total_final_amount: number;
total_ppn: number;
total_grand_amount: number; total_grand_amount: number;
total_payment: number; total_payment: number;
total_accounts_receivable: number; total_accounts_receivable: number;
@@ -37,6 +33,7 @@ export type CustomerPaymentSummary = {
export type CustomerPaymentReport = BaseMetadata & { export type CustomerPaymentReport = BaseMetadata & {
customer: BaseCustomer; customer: BaseCustomer;
initial_balance: number;
rows: CustomerPaymentRow[]; rows: CustomerPaymentRow[];
summary: CustomerPaymentSummary; summary: CustomerPaymentSummary;
}; };
+2 -14
View File
@@ -9,30 +9,22 @@ export type HppPerKandangRow = {
weight_min: number; weight_min: number;
weight_max: number; weight_max: number;
}; };
remaining_chicken_birds: number;
remaining_chicken_weight_kg: number;
avg_weight_kg: number; avg_weight_kg: number;
egg_production_pieces: number; egg_production_pieces: number;
egg_production_kg: number; egg_production_kg: number;
egg_hpp_rp_per_kg: number; egg_hpp_rp_per_kg: number;
egg_value_rp: number; egg_value_rp: number;
feed_suppliers: Supplier[]; feed_suppliers: Supplier[] | null;
doc_suppliers: Supplier[]; doc_suppliers: Supplier[] | null;
average_doc_price_rp: number; average_doc_price_rp: number;
hpp_rp: number;
remaining_value_rp: number;
}; };
export type HppPerKandangSummaryTotal = { export type HppPerKandangSummaryTotal = {
total_remaining_chicken_birds: number;
total_remaining_chicken_weight_kg: number;
average_weight_kg: number; average_weight_kg: number;
total_remaining_value_rp: number;
total_egg_production_pieces: number; total_egg_production_pieces: number;
total_egg_production_kg: number; total_egg_production_kg: number;
average_egg_hpp_rp_per_kg: number; average_egg_hpp_rp_per_kg: number;
total_egg_value_rp: number; total_egg_value_rp: number;
total_hpp_rp: number;
total_average_doc_price_rp: number; total_average_doc_price_rp: number;
}; };
@@ -43,8 +35,6 @@ export type HppPerKandangPerWeightRange = {
weight_max: number; weight_max: number;
}; };
label: string; label: string;
remaining_chicken_birds: number;
remaining_chicken_weight_kg: number;
avg_weight_kg: number; avg_weight_kg: number;
egg_production_pieces: number; egg_production_pieces: number;
egg_production_kg: number; egg_production_kg: number;
@@ -53,8 +43,6 @@ export type HppPerKandangPerWeightRange = {
feed_suppliers: Supplier[]; feed_suppliers: Supplier[];
doc_suppliers: Supplier[]; doc_suppliers: Supplier[];
average_doc_price_rp: number; average_doc_price_rp: number;
hpp_rp: number;
remaining_value_rp: number;
}; };
export type HppPerKandangSummary = { export type HppPerKandangSummary = {