@@ -1744,9 +1742,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
- {initialValues.egg_mass_std &&
- initialValues.egg_mass_std > 0
- ? formatNumber(initialValues.egg_mass_std)
+ {initialValues.project_flock?.production_standart
+ ?.egg_mass_std &&
+ initialValues.project_flock?.production_standart
+ ?.egg_mass_std > 0
+ ? formatNumber(
+ initialValues.project_flock
+ ?.production_standart?.egg_mass_std
+ )
: '-'}
|
@@ -1763,9 +1766,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- {initialValues.egg_weight_std &&
- initialValues.egg_weight_std > 0
- ? formatNumber(initialValues.egg_weight_std)
+ {initialValues.project_flock?.production_standart
+ ?.egg_weight_std &&
+ initialValues.project_flock?.production_standart
+ ?.egg_weight_std > 0
+ ? formatNumber(
+ initialValues.project_flock
+ ?.production_standart?.egg_weight_std
+ )
: '-'}
|
@@ -1780,9 +1788,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- {initialValues.hen_day_std !== undefined &&
- initialValues.hen_day_std > 0
- ? `${initialValues.hen_day_std}%`
+ {initialValues.project_flock?.production_standart
+ ?.hen_day_std !== undefined &&
+ initialValues.project_flock?.production_standart
+ ?.hen_day_std > 0
+ ? `${initialValues.project_flock?.production_standart?.hen_day_std}%`
: '-'}
|
@@ -1797,9 +1807,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- {initialValues.hen_house_std !== undefined &&
- initialValues.hen_house_std > 0
- ? `${initialValues.hen_house_std}%`
+ {initialValues.project_flock?.production_standart
+ ?.hen_house_std !== undefined &&
+ initialValues.project_flock?.production_standart
+ ?.hen_house_std > 0
+ ? `${initialValues.project_flock?.production_standart?.hen_house_std}%`
: '-'}
|
diff --git a/src/components/pages/report/DailyMarketingReportContent.tsx b/src/components/pages/report/DailyMarketingReportContent.tsx
index 3ddbd6cf..01c360d0 100644
--- a/src/components/pages/report/DailyMarketingReportContent.tsx
+++ b/src/components/pages/report/DailyMarketingReportContent.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ChangeEventHandler, useState } from 'react';
+import { ChangeEventHandler, useEffect, useState } from 'react';
import { pdf } from '@react-pdf/renderer';
import toast from 'react-hot-toast';
@@ -28,7 +28,10 @@ import {
import { Warehouse } from '@/types/api/master-data/warehouse';
import { Customer } from '@/types/api/master-data/customer';
import { MarketingReportApi } from '@/services/api/report/marketing-report';
-import { MARKETING_TYPE_OPTIONS } from '@/config/constant';
+import {
+ MARKETING_DATE_FILTER_TYPE_OPTIONS,
+ MARKETING_TYPE_OPTIONS,
+} from '@/config/constant';
import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
import {
@@ -84,6 +87,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setAreaInputValue,
options: areaOptions,
isLoadingOptions: isLoadingAreaOptions,
+ loadMore: loadMoreAreas,
} = useSelect
(AreaApi.basePath, 'id', 'name');
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -98,6 +102,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setLocationInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocationOptions,
+ loadMore: loadMoreLocations,
} = useSelect
(LocationApi.basePath, 'id', 'name');
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -115,6 +120,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setWarehouseInputValue,
options: warehouseOptions,
isLoadingOptions: isLoadingWarehouseOptions,
+ loadMore: loadMoreWarehouses,
} = useSelect(WarehouseApi.basePath, 'id', 'name');
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -132,6 +138,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setCustomerInputValue,
options: customerOptions,
isLoadingOptions: isLoadingCustomerOptions,
+ loadMore: loadMoreCustomers,
} = useSelect(CustomerApi.basePath, 'id', 'name');
const customerChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -150,6 +157,15 @@ const DailyMarketingReportContent = () => {
updateFilter('end_date', e.target.value ? e.target.value : '');
};
+ const [selectedMarketingDateFilterType, setSelectedMarketingDateFilterType] =
+ useState(null);
+ const marketingDateFilterTypeChangeHandler = (
+ val: OptionType | OptionType[] | null
+ ) => {
+ setSelectedMarketingDateFilterType(val as OptionType);
+ updateFilter('filter_by', val ? ((val as OptionType).value as string) : '');
+ };
+
const [selectedMarketingType, setSelectedMarketingType] =
useState(null);
const marketingTypeChangeHandler = (
@@ -252,6 +268,23 @@ const DailyMarketingReportContent = () => {
resetFilter();
};
+ useEffect(() => {
+ if (
+ tableFilterState.filter_by === 'realization_date' ||
+ tableFilterState.filter_by === 'so_date'
+ ) {
+ setSelectedMarketingDateFilterType({
+ label:
+ tableFilterState.filter_by === 'realization_date'
+ ? 'Tanggal Realisasi'
+ : 'Tanggal SO',
+ value: tableFilterState.filter_by,
+ });
+ } else {
+ setSelectedMarketingDateFilterType(null);
+ }
+ }, [tableFilterState.filter_by]);
+
return (
@@ -269,6 +302,7 @@ const DailyMarketingReportContent = () => {
value={selectedArea}
onChange={areaChangeHandler}
onInputChange={setAreaInputValue}
+ onMenuScrollToBottom={loadMoreAreas}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -283,6 +317,7 @@ const DailyMarketingReportContent = () => {
value={selectedLocation}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
+ onMenuScrollToBottom={loadMoreLocations}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -297,6 +332,7 @@ const DailyMarketingReportContent = () => {
value={selectedWarehouse}
onChange={warehouseChangeHandler}
onInputChange={setWarehouseInputValue}
+ onMenuScrollToBottom={loadMoreWarehouses}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -311,6 +347,7 @@ const DailyMarketingReportContent = () => {
value={selectedCustomer}
onChange={customerChangeHandler}
onInputChange={setCustomerInputValue}
+ onMenuScrollToBottom={loadMoreCustomers}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -341,6 +378,18 @@ const DailyMarketingReportContent = () => {
+
+
`${props.row.original.aging_days} hari`,
},
{
- accessorKey: 'warehouse.name',
+ accessorKey: 'warehouse',
header: 'Gudang',
+ cell: ({ row }) => row.original.warehouse.name,
},
{
- accessorKey: 'customer.name',
+ accessorKey: 'customer',
header: 'Pelanggan',
+ cell: ({ row }) => row.original.customer.name,
},
{
accessorKey: 'do_number',
header: 'No. DO',
+ enableSorting: false,
},
{
- accessorKey: 'sales',
+ accessorKey: 'sales_person',
header: 'Sales/Marketing',
cell: (props) => props.row.original.sales.name,
},
@@ -97,10 +100,12 @@ const DailyMarketingsTable = ({
{
accessorKey: 'marketing_type',
header: 'Marketing Type',
+ enableSorting: false,
},
{
- accessorKey: 'product.name',
+ accessorKey: 'product',
header: 'Produk',
+ cell: ({ row }) => row.original.product.name,
},
{
accessorKey: 'qty',
@@ -115,12 +120,12 @@ const DailyMarketingsTable = ({
},
},
{
- accessorKey: 'average_weight_kg',
+ accessorKey: 'average_weight',
header: 'Bobot Rata-Rata (Kg)',
cell: (props) => formatNumber(props.row.original.average_weight_kg),
},
{
- accessorKey: 'total_weight_kg',
+ accessorKey: 'total_weight',
header: 'Bobot Total (Kg)',
cell: (props) => formatNumber(props.row.original.total_weight_kg),
footer: () => {
@@ -132,12 +137,12 @@ const DailyMarketingsTable = ({
},
},
{
- accessorKey: 'sales_price_per_kg',
+ accessorKey: 'sales_price',
header: 'Harga Jual (Rp)',
cell: (props) => formatCurrency(props.row.original.sales_price_per_kg),
},
{
- accessorKey: 'hpp_price_per_kg',
+ accessorKey: 'hpp_price',
header: 'HPP (Rp)',
cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg),
footer: () => {
@@ -163,6 +168,8 @@ const DailyMarketingsTable = ({
];
useEffect(() => {
+ console.log({ sorting });
+
if (sorting.length === 1) {
onFilterByChange(sorting[0].id);
onSortByChange(sorting[0].desc ? 'desc' : 'asc');
diff --git a/src/components/pages/report/MarketingReportContent.tsx b/src/components/pages/report/MarketingReportContent.tsx
index d54c935a..3ebacecb 100644
--- a/src/components/pages/report/MarketingReportContent.tsx
+++ b/src/components/pages/report/MarketingReportContent.tsx
@@ -33,7 +33,7 @@ const MarketingReportContent = () => {
const [activeTab, setActiveTab] = useState('daily');
return (
-
+
{
// ===== STATE MANAGEMENT =====
@@ -64,16 +73,33 @@ const ReportExpenseTable = () => {
});
// ===== SELECT OPTIONS =====
- const { options: optionsLocation, isLoadingOptions: isLoadingLocation } =
- useSelect(`/master-data/locations`, 'id', 'name');
- const { options: optionsSupplier, isLoadingOptions: isLoadingSupplier } =
- useSelect(`/master-data/suppliers`, 'id', 'name');
- const { options: optionsKandang, isLoadingOptions: isLoadingKandang } =
- useSelect(`/master-data/kandangs`, 'id', 'name', '', {
- location_id: filterState.location_id,
- });
- const { options: optionsNonstock, isLoadingOptions: isLoadingNonstock } =
- useSelect(`/master-data/nonstocks`, 'id', 'name');
+ const {
+ setInputValue: setLocationInputValue,
+ options: locationOptions,
+ isLoadingOptions: isLoadingLocationOptions,
+ loadMore: loadMoreLocations,
+ } = useSelect(LocationApi.basePath, 'id', 'name');
+
+ const {
+ setInputValue: setSupplierInputValue,
+ options: supplierOptions,
+ isLoadingOptions: isLoadingSupplierOptions,
+ loadMore: loadMoreSuppliers,
+ } = useSelect(SupplierApi.basePath, 'id', 'name');
+
+ const {
+ setInputValue: setKandangInputValue,
+ options: kandangOptions,
+ isLoadingOptions: isLoadingKandangOptions,
+ loadMore: loadMoreKandangs,
+ } = useSelect(KandangApi.basePath, 'id', 'name');
+
+ const {
+ setInputValue: setNonstockInputValue,
+ options: nonstockOptions,
+ isLoadingOptions: isLoadingNonstockOptions,
+ loadMore: loadMoreNonstocks,
+ } = useSelect(NonstockApi.basePath, 'id', 'name');
const categoryOptions = useMemo(
() => [
@@ -86,31 +112,31 @@ const ReportExpenseTable = () => {
// Mendapatkan value option select dari filter state
const selectedLocation = useMemo(
() =>
- optionsLocation.find(
+ locationOptions.find(
(opt) => String(opt.value) === filterState.location_id
) || null,
- [optionsLocation, filterState.location_id]
+ [locationOptions, filterState.location_id]
);
const selectedSupplier = useMemo(
() =>
- optionsSupplier.find(
+ supplierOptions.find(
(opt) => String(opt.value) === filterState.supplier_id
) || null,
- [optionsSupplier, filterState.supplier_id]
+ [supplierOptions, filterState.supplier_id]
);
const selectedKandang = useMemo(
() =>
- optionsKandang.find(
+ kandangOptions.find(
(opt) => String(opt.value) === filterState.kandang_id
) || null,
- [optionsKandang, filterState.kandang_id]
+ [kandangOptions, filterState.kandang_id]
);
const selectedNonstock = useMemo(
() =>
- optionsNonstock.find(
+ nonstockOptions.find(
(opt) => String(opt.value) === filterState.nonstock_id
) || null,
- [optionsNonstock, filterState.nonstock_id]
+ [nonstockOptions, filterState.nonstock_id]
);
const selectedCategory = useMemo(
() =>
@@ -756,38 +782,46 @@ const ReportExpenseTable = () => {
{
const tabs = [
{
id: '1',
- label: 'Kontrol Pembayaran Customer',
-
- content: ,
- },
- {
- id: '2',
label: 'Rekapitulasi Hutang Ke Supplier',
content: ,
},
+ {
+ id: '2',
+ label: 'Kontrol Pembayaran Customer',
+
+ content: ,
+ },
];
return (
diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
index 88c556de..5adcb694 100644
--- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
+++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
@@ -136,41 +136,132 @@ const pdfStyles = StyleSheet.create({
backgroundColor: '#F0F0F0',
fontWeight: 'bold',
},
+ badge: {
+ backgroundColor: '#1f74bf',
+ color: '#FFFFFF',
+ padding: 2,
+ borderRadius: 2,
+ fontSize: 7,
+ fontWeight: 'bold',
+ alignSelf: 'center',
+ marginRight: 4,
+ },
+ badgeLunas: {
+ backgroundColor: '#1f74bf',
+ color: '#FFFFFF',
+ },
+ badgeBelumLunas: {
+ backgroundColor: '#F97316',
+ color: '#FFFFFF',
+ },
+ textError: {
+ color: '#DC2626',
+ },
+ parameterBadge: {
+ backgroundColor: '#F5F5F5',
+ color: '#333333',
+ padding: 4,
+ borderRadius: 4,
+ fontSize: 8,
+ marginRight: 8,
+ marginBottom: 4,
+ },
+ parameterContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginBottom: 8,
+ },
});
interface CustomerPaymentExportPDFParams {
data: CustomerPaymentReport[];
+ params?: {
+ customer_name?: string;
+ sales?: string;
+ start_date?: string;
+ end_date?: string;
+ filter_by?: string;
+ };
}
+const getParameterText = (
+ params?: CustomerPaymentExportPDFParams['params']
+) => {
+ const paramsText = [];
+
+ if (params?.customer_name) {
+ paramsText.push(`Customer: ${params.customer_name}`);
+ } else {
+ paramsText.push('Semua Customer');
+ }
+
+ if (params?.sales) {
+ paramsText.push(`Sales: ${params.sales}`);
+ }
+
+ if (params?.start_date && params?.end_date) {
+ const startDate = formatDate(params.start_date, 'DD MMM YYYY');
+ const endDate = formatDate(params.end_date, 'DD MMM YYYY');
+ paramsText.push(`Periode: ${startDate} - ${endDate}`);
+ } else if (params?.start_date) {
+ const startDate = formatDate(params.start_date, 'DD MMM YYYY');
+ paramsText.push(`Tanggal: ${startDate}`);
+ }
+
+ const currentDate = formatDate(new Date(), 'DD MMM YYYY HH:mm');
+ paramsText.push(`Dicetak: ${currentDate}`);
+
+ return paramsText;
+};
+
const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
return (
{params.data.map((customerReport, customerIndex) => (
- {/* Title and Customer Info */}
+ {/* Title and Parameters */}
Laporan > Kontrol Pembayaran Customer
+
+
+
+ Periode:{' '}
+ {params.params?.start_date
+ ? formatDate(params.params.start_date, 'DD MMM YYYY')
+ : '-'}{' '}
+ s.d{' '}
+ {params.params?.end_date
+ ? formatDate(params.params.end_date, 'DD MMM YYYY')
+ : '-'}
+
+
+
+ Filter Tanggal: Tanggal DO
+
+
+
+ Customer: {params.params?.customer_name || 'Semua Customer'}
+
+
+
+
+ Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
+
+
+
{customerReport.customer.name}
- {customerReport.customer.address || ''}
+ Alamat: {customerReport.customer.address || '-'}
- {customerReport.summary && (
-
- Total Saldo Piutang:{' '}
- {formatCurrency(
- customerReport.summary.total_accounts_receivable
- )}
-
- )}
{/* Table */}
@@ -181,10 +272,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
No
- Tgl DO/Bayar
+ Tanggal DO
- Tgl Realisasi
+ Tanggal Realisasi
Aging
@@ -193,16 +284,16 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
Referensi
- No. Polisi
+ No Polisi
Qty
- Berat (Kg)
+ Berat
- AVG
+ Rata-Rata
Harga Awal
@@ -214,7 +305,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
Harga Akhir
- PPN (%)
+ Pajak
Total
@@ -223,10 +314,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
Pembayaran
- Saldo Piutang
+ Saldo
- Ket
+ Keterangan
Pengambilan
@@ -301,10 +392,29 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
{formatCurrency(item.payment)}
- {formatCurrency(item.accounts_receivable)}
+
+ {formatCurrency(item.accounts_receivable)}
+
- {item.notes || '-'}
+ {item.notes ? (
+ {item.notes}
+ ) : (
+
+
+ {item.accounts_receivable === 0
+ ? 'Lunas'
+ : 'Belum Lunas'}
+
+
+ )}
{item.pickup_info || '-'}
@@ -378,7 +488,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
-
+
{formatCurrency(
customerReport.summary.total_accounts_receivable
)}
diff --git a/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx b/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx
index 7f6fa45b..083904a5 100644
--- a/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx
+++ b/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx
@@ -176,7 +176,7 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
No. PO
- Tgl Terima
+ Tgl Terima/Bayar
Tgl PO
@@ -191,19 +191,19 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
Gudang
- Tgl Jatuh Tempo
+ Jatuh Tempo
Status Jatuh Tempo
- Total Harga
+ Nominal Pembelian (Rp)
- Pembayaran
+ Pembayaran (Rp)
- Hutang
+ Sisa Saldo Hutang (Rp)
Status
@@ -213,6 +213,65 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
+ {/* Initial Balance Row */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {formatCurrency(supplierReport.initial_balance || 0)}
+
+
+
+
+
+
+
+
+
+
{/* Table Body */}
{supplierReport.rows.map((item, index) => (
{
- {formatCurrency(item.debt_price)}
+ {formatCurrency(item.balance)}
{item.status || '-'}
diff --git a/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx b/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx
index 58b07e30..3ba96a22 100644
--- a/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx
+++ b/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx
@@ -2,7 +2,7 @@
import * as XLSX from 'xlsx';
import { formatDate } from '@/lib/helper';
-import { DebtSupplier } from '@/types/api/report/debt-supplier';
+import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
interface DebtSupplierExportExcelParams {
data: DebtSupplier[];
@@ -21,12 +21,29 @@ export const generateDebtSupplierExcel = (
const supplierData = supplierReport.rows;
const supplierName = supplierReport.supplier.name || 'Unknown Supplier';
- const excelData: { [key: string]: string | number }[] = supplierData.map(
- (item, index) => ({
+ const excelData: { [key: string]: string | number }[] = [
+ {
+ No: '',
+ 'Nomor PR': '',
+ 'Nomor PO': '',
+ 'Tanggal Terima/Bayar': '',
+ 'Tanggal PO': '',
+ 'Aging (Hari)': '',
+ Area: '',
+ Gudang: '',
+ 'Jatuh Tempo': '',
+ 'Status Jatuh Tempo': '',
+ 'Nominal Pembelian (Rp)': '',
+ 'Pembayaran (Rp)': '',
+ 'Sisa Saldo Hutang (Rp)': supplierReport.initial_balance || 0,
+ Status: '',
+ 'Nomor Perjalanan': '',
+ },
+ ...supplierData.map((item, index) => ({
No: index + 1,
'Nomor PR': item.pr_number || '',
'Nomor PO': item.po_number || '',
- 'Tanggal Terima': item.received_date
+ 'Tanggal Terima/Bayar': item.received_date
? item.received_date != '-'
? formatDate(item.received_date, 'MM/DD/YYYY')
: '-'
@@ -39,35 +56,35 @@ export const generateDebtSupplierExcel = (
'Aging (Hari)': item.aging || 0,
Area: item.area?.name || '',
Gudang: item.warehouse?.name || '',
- 'Tanggal Jatuh Tempo': item.due_date
+ 'Jatuh Tempo': item.due_date
? item.due_date != '-'
? formatDate(item.due_date, 'MM/DD/YYYY')
: '-'
: '-',
'Status Jatuh Tempo': item.due_status || '',
- 'Total Harga': item.total_price || 0,
- 'Harga Pembayaran': item.payment_price || 0,
- 'Harga Hutang': item.debt_price || 0,
+ 'Nominal Pembelian (Rp)': item.total_price || 0,
+ 'Pembayaran (Rp)': item.payment_price || 0,
+ 'Sisa Saldo Hutang (Rp)': item.debt_price || 0,
Status: item.status || '',
'Nomor Perjalanan': item.travel_number || '',
- })
- );
+ })),
+ ];
if (supplierReport.total) {
excelData.push({
No: 'Total',
'Nomor PR': '',
'Nomor PO': '',
- 'Tanggal Terima': '',
+ 'Tanggal Terima/Bayar': '',
'Tanggal PO': '',
'Aging (Hari)': supplierReport.total.aging || 0,
Area: '',
Gudang: '',
- 'Tanggal Jatuh Tempo': '',
+ 'Jatuh Tempo': '',
'Status Jatuh Tempo': '',
- 'Total Harga': supplierReport.total.total_price || 0,
- 'Harga Pembayaran': supplierReport.total.payment_price || 0,
- 'Harga Hutang': supplierReport.total.debt_price || 0,
+ 'Nominal Pembelian (Rp)': supplierReport.total.total_price || 0,
+ 'Pembayaran (Rp)': supplierReport.total.payment_price || 0,
+ 'Sisa Saldo Hutang (Rp)': supplierReport.total.debt_price || 0,
Status: '',
'Nomor Perjalanan': '',
});
@@ -79,16 +96,16 @@ export const generateDebtSupplierExcel = (
{ wch: 5 }, // No
{ wch: 15 }, // Nomor PR
{ wch: 15 }, // Nomor PO
- { wch: 15 }, // Tanggal PR
+ { wch: 15 }, // Tanggal Terima/Bayar
{ wch: 15 }, // Tanggal PO
{ wch: 12 }, // Aging
{ wch: 15 }, // Area
{ wch: 15 }, // Gudang
- { wch: 18 }, // Tanggal Jatuh Tempo
+ { wch: 18 }, // Jatuh Tempo
{ wch: 18 }, // Status Jatuh Tempo
- { wch: 15 }, // Total Harga
- { wch: 15 }, // Harga Pembayaran
- { wch: 15 }, // Harga Hutang
+ { wch: 15 }, // Nominal Pembelian (Rp)
+ { wch: 15 }, // Pembayaran (Rp)
+ { wch: 15 }, // Sisa Saldo Hutang (Rp)
{ wch: 12 }, // Status
{ wch: 15 }, // Nomor Perjalanan
];
diff --git a/src/components/pages/report/finance/filter/DebtSupplierFilter.ts b/src/components/pages/report/finance/filter/DebtSupplierFilter.ts
new file mode 100644
index 00000000..1c1c2fac
--- /dev/null
+++ b/src/components/pages/report/finance/filter/DebtSupplierFilter.ts
@@ -0,0 +1,36 @@
+import { OptionType } from '@/components/input/SelectInput';
+import * as yup from 'yup';
+
+export type DebtSupplierFilterType = {
+ startDate: string | null | undefined;
+ endDate: string | null | undefined;
+ supplierIds: OptionType[] | null | undefined;
+ filterBy: OptionType | null | undefined;
+};
+
+export const DebtSupplierFilterSchema: yup.ObjectSchema =
+ yup.object({
+ startDate: yup.string().optional().notRequired(),
+ endDate: yup.string().optional().notRequired(),
+ supplierIds: yup
+ .array()
+ .of(
+ yup.object({
+ value: yup.mixed().required(),
+ label: yup.string().required(),
+ })
+ )
+ .optional()
+ .notRequired(),
+ filterBy: yup
+ .object({
+ value: yup.mixed().required(),
+ label: yup.string().required(),
+ })
+ .optional()
+ .notRequired(),
+ });
+
+export type DebtSupplierFilterValues = yup.InferType<
+ typeof DebtSupplierFilterSchema
+>;
diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
index 1d8d1993..adc5b375 100644
--- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
+++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
@@ -2,13 +2,16 @@ import { useState, useMemo, useCallback } from 'react';
import useSWR from 'swr';
import { Icon } from '@iconify/react';
import Card from '@/components/Card';
+import Badge from '@/components/Badge';
import SelectInput, {
useSelect,
OptionType,
} from '@/components/input/SelectInput';
+import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
import DateInput from '@/components/input/DateInput';
import { CustomerApi } from '@/services/api/master-data';
import { FinanceApi } from '@/services/api/report/finance-report';
+import { UserApi } from '@/services/api/user';
import Table from '@/components/Table';
import { ColumnDef } from '@tanstack/react-table';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
@@ -17,7 +20,6 @@ import {
CustomerPaymentSummary,
} from '@/types/api/report/customer-payment';
import { isResponseSuccess } from '@/lib/api-helper';
-import Pagination from '@/components/Pagination';
import Button from '@/components/Button';
import Dropdown from '@/components/Dropdown';
import MenuItem from '@/components/menu/MenuItem';
@@ -36,38 +38,74 @@ const CustomerPaymentTab = () => {
// ===== PAGINATION STATE =====
const [currentPage, setCurrentPage] = useState(1);
- const [pageSize, setPageSize] = useState(10);
+ const [pageSize] = useState(10);
// ===== SUBMISSION STATE =====
const [isSubmitted, setIsSubmitted] = useState(false);
// ===== FILTER STATE =====
- const [filterCustomer, setFilterCustomer] = useState([]);
- const [filterSales, setFilterSales] = useState([]);
+ const [filterCustomer, setFilterCustomer] = useState(
+ []
+ );
+ const [filterSales, setFilterSales] = useState([]);
const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState('');
- const [filterErrors, setFilterErrors] = useState>({});
const filterModal = useModal();
- const { options: customerOptions, isLoadingOptions: isLoadingCustomers } =
- useSelect(CustomerApi.basePath, 'id', 'name', 'search');
+ const {
+ options: customerOptions,
+ setInputValue: setCustomerInputValue,
+ isLoadingOptions: isLoadingCustomers,
+ loadMore: loadMoreCustomers,
+ hasMore: hasMoreCustomers,
+ } = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
- const salesOptions = useMemo(
- () => [
- { value: 'Sales A', label: 'Sales A' },
- { value: 'Sales B', label: 'Sales B' },
- { value: 'Sales C', label: 'Sales C' },
- // TODO: Fetch sales options from API
- ],
- []
- );
+ const {
+ options: salesOptions,
+ setInputValue: setSalesInputValue,
+ isLoadingOptions: isLoadingSales,
+ loadMore: loadMoreSales,
+ hasMore: hasMoreSales,
+ } = useSelect(UserApi.basePath, 'id', 'name', 'search');
const dataTypeOptions = useMemo(
() => [{ value: 'do_date', label: 'Tanggal Jual' }],
[]
);
+ const getPaymentStatusColor = (notes: string) => {
+ const normalizedValue = notes.toLowerCase();
+
+ if (normalizedValue === 'lunas') {
+ return 'bg-info/10 text-info border-info';
+ }
+
+ if (normalizedValue.includes('belum')) {
+ return 'bg-warning/10 text-warning border-warning';
+ }
+
+ return 'bg-gray-100 text-gray-600 border-gray-300';
+ };
+
+ const getPaymentStatusIndicatorColor = (notes: string) => {
+ const normalizedValue = notes.toLowerCase();
+
+ if (normalizedValue === 'lunas') {
+ return 'bg-info';
+ }
+
+ if (normalizedValue.includes('belum')) {
+ return 'bg-warning';
+ }
+
+ return 'bg-gray-400';
+ };
+
+ const getPaymentStatusText = (notes: string) => {
+ return notes;
+ };
+
// ===== FILTER HANDLERS =====
const handleResetFilters = useCallback(() => {
setIsSubmitted(false);
@@ -75,27 +113,48 @@ const CustomerPaymentTab = () => {
setFilterSales([]);
setFilterStartDate('');
setFilterEndDate('');
- setFilterErrors({});
}, []);
const handleApplyFilters = useCallback(() => {
- const errors: Record = {};
+ setIsSubmitted(true);
+ setCurrentPage(1);
+ filterModal.closeModal();
+ }, [filterModal]);
- if (!filterStartDate) {
- errors.start_date = 'Tanggal mulai wajib diisi';
- }
- if (!filterEndDate) {
- errors.end_date = 'Tanggal akhir wajib diisi';
+ // ===== ACTIVE FILTERS COUNT =====
+ const activeFiltersCount = useMemo(() => {
+ let count = 0;
+
+ // Date filter (start_date + end_date = 1 filter)
+ if (filterStartDate || filterEndDate) {
+ count += 1;
}
- setFilterErrors(errors);
-
- if (Object.keys(errors).length === 0) {
- setIsSubmitted(true);
- setCurrentPage(1);
- filterModal.closeModal();
+ // Customer filter
+ if (filterCustomer.length > 0) {
+ count += 1;
}
- }, [filterModal, filterStartDate, filterEndDate]);
+
+ // Sales filter
+ if (filterSales.length > 0) {
+ count += 1;
+ }
+
+ // Filter by (always count if submitted)
+ if (isSubmitted) {
+ count += 1;
+ }
+
+ return count;
+ }, [
+ filterStartDate,
+ filterEndDate,
+ filterCustomer,
+ filterSales,
+ isSubmitted,
+ ]);
+
+ const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING =====
const { data: customerPayment, isLoading } = useSWR(
@@ -106,7 +165,7 @@ const CustomerPaymentTab = () => {
filterCustomer.length > 0
? filterCustomer.map((v) => String(v.value)).join(',')
: undefined,
- sales:
+ sales_id:
filterSales.length > 0
? filterSales.map((v) => String(v.value)).join(',')
: undefined,
@@ -123,7 +182,7 @@ const CustomerPaymentTab = () => {
([, params]) =>
FinanceApi.getCustomerPaymentReport(
params.customer_id,
- params.sales,
+ params.sales_id,
params.filter_by,
params.start_date,
params.end_date,
@@ -140,11 +199,6 @@ const CustomerPaymentTab = () => {
[customerPayment]
);
- const meta =
- isResponseSuccess(customerPayment) && customerPayment?.meta
- ? customerPayment.meta
- : null;
-
// ===== EXPORT DATA FETCHER =====
const customerPaymentExport = useCallback(async (): Promise<
CustomerPaymentReport[] | null
@@ -154,7 +208,7 @@ const CustomerPaymentTab = () => {
filterCustomer.length > 0
? filterCustomer.map((v) => String(v.value)).join(',')
: undefined,
- sales:
+ sales_id:
filterSales.length > 0
? filterSales.map((v) => String(v.value)).join(',')
: undefined,
@@ -167,7 +221,7 @@ const CustomerPaymentTab = () => {
const response = await FinanceApi.getCustomerPaymentReport(
params.customer_id,
- params.sales,
+ params.sales_id,
params.filter_by,
params.start_date,
params.end_date,
@@ -218,7 +272,22 @@ const CustomerPaymentTab = () => {
return;
}
- await generateCustomerPaymentPDF({ data: allDataForExport });
+ await generateCustomerPaymentPDF({
+ data: allDataForExport,
+ params: {
+ customer_name:
+ filterCustomer.length > 0
+ ? filterCustomer.map((c) => c.label).join(', ')
+ : undefined,
+ sales:
+ filterSales.length > 0
+ ? filterSales.map((s) => s.label).join(', ')
+ : undefined,
+ start_date: filterStartDate || undefined,
+ end_date: filterEndDate || undefined,
+ filter_by: 'do_date',
+ },
+ });
toast.success('PDF berhasil dibuat dan diunduh.');
} catch {
toast.error('Gagal membuat PDF. Silakan coba lagi.');
@@ -227,27 +296,6 @@ const CustomerPaymentTab = () => {
}
}, [customerPaymentExport]);
- // ===== PAGINATION HANDLERS =====
- const handlePageChange = (page: number) => {
- setCurrentPage(page);
- };
-
- const handleRowChange = (pageSize: number) => {
- setPageSize(pageSize);
- };
-
- const handleNextPage = () => {
- if (meta && currentPage < meta.total_pages) {
- setCurrentPage(currentPage + 1);
- }
- };
-
- const handlePrevPage = () => {
- if (currentPage > 1) {
- setCurrentPage(currentPage - 1);
- }
- };
-
const getTableColumns = (
summary: CustomerPaymentSummary
): ColumnDef[] => {
@@ -435,7 +483,9 @@ const CustomerPaymentTab = () => {
accessorKey: 'accounts_receivable',
cell: (props) => {
const value = props.row.original.accounts_receivable;
- return {formatCurrency(value)}
;
+ return (
+ {formatCurrency(value)}
+ );
},
footer: () => (
@@ -449,7 +499,23 @@ const CustomerPaymentTab = () => {
accessorKey: 'notes',
cell: (props) => {
const value = props.row.original.notes;
- return value || '-';
+
+ if (!value) {
+ return '-';
+ }
+
+ return (
+
+ {getPaymentStatusText(value)}
+
+ );
},
},
{
@@ -481,14 +547,37 @@ const CustomerPaymentTab = () => {
className={{ wrapper: 'w-full', body: 'p-1!' }}
>
-
@@ -556,23 +639,16 @@ const CustomerPaymentTab = () => {
value={filterEndDate}
onChange={(e) => {
setFilterEndDate(e.target.value);
- setFilterErrors((prev) => ({ ...prev, end_date: '' }));
}}
className={{ wrapper: 'w-full' }}
/>
- {filterErrors.end_date && (
-
- {filterErrors.end_date}
-
- )}
- {
@@ -580,23 +656,27 @@ const CustomerPaymentTab = () => {
Array.isArray(val) ? val : val ? [val] : []
);
}}
+ onInputChange={setCustomerInputValue}
isLoading={isLoadingCustomers}
isClearable
+ onMenuScrollToBottom={loadMoreCustomers}
className={{ wrapper: 'w-full' }}
/>
- {
setFilterSales(Array.isArray(val) ? val : val ? [val] : []);
}}
+ onInputChange={setSalesInputValue}
+ isLoading={isLoadingSales}
isClearable
+ onMenuScrollToBottom={loadMoreSales}
className={{ wrapper: 'w-full' }}
/>
@@ -659,15 +739,18 @@ const CustomerPaymentTab = () => {
total_accounts_receivable: 0,
};
- const totalAccountsReceivable = summary.total_accounts_receivable;
const tableColumns = getTableColumns(summary);
return (
@@ -678,7 +761,7 @@ const CustomerPaymentTab = () => {
renderFooter={customerReport.rows.length > 0}
className={{
containerClassName: 'w-full',
- tableWrapperClassName: 'overflow-x-auto mt-4',
+ tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
@@ -700,20 +783,6 @@ const CustomerPaymentTab = () => {
})
)}
- {meta && data.length > 0 && (
-
- )}
);
};
diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
index 5a72ea3c..a2607e2b 100644
--- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
+++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
@@ -13,7 +13,11 @@ import Table from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import { SupplierApi } from '@/services/api/master-data';
-import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
+import {
+ DebtRow,
+ DebtSupplier,
+ DebtSupplierFilter,
+} from '@/types/api/report/debt-supplier';
import { generateDebtSupplierExcel } from '@/components/pages/report/finance/export/DebtSupplierExportXLSX';
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
import { Icon } from '@iconify/react';
@@ -21,8 +25,15 @@ import { ColumnDef } from '@tanstack/react-table';
import { useCallback, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import useSWR from 'swr';
-import Pagination from '@/components/Pagination';
import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
+import { useFormik } from 'formik';
+import {
+ DebtSupplierFilterSchema,
+ DebtSupplierFilterType,
+} from '@/components/pages/report/finance/filter/DebtSupplierFilter';
+import { getFilledFormikValuesCount } from '@/lib/formik-helper';
+import ButtonFilter from '@/components/helper/ButtonFilter';
+import { Supplier } from '@/types/api/master-data/supplier';
const DebtSupplierTab = () => {
// ===== STATE MANAGEMENT =====
@@ -30,26 +41,23 @@ const DebtSupplierTab = () => {
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
- // ===== PAGINATION STATE =====
- const [currentPage, setCurrentPage] = useState(1);
- const [pageSize, setPageSize] = useState(10);
-
// ===== SUBMISSION STATE =====
+ const [filterParams, setFilterParams] = useState
({
+ start_date: undefined,
+ end_date: undefined,
+ supplier_ids: undefined,
+ filter_by: undefined,
+ });
const [isSubmitted, setIsSubmitted] = useState(false);
- // ===== FILTER STATE =====
- const [filterSupplier, setFilterSupplier] = useState([]);
- const [filterStartDate, setFilterStartDate] = useState('');
- const [filterEndDate, setFilterEndDate] = useState('');
- const [filterDateType, setFilterDateType] = useState();
- const [filterErrors, setFilterErrors] = useState>({});
-
const filterModal = useModal();
- const { options: supplierOptions, isLoadingOptions: isLoadingSuppliers } =
- useSelect(SupplierApi.basePath, 'id', 'name', '', {
- limit: 'limit',
- });
+ const {
+ setInputValue: setSupplierInputValue,
+ options: supplierOptions,
+ isLoadingOptions: isLoadingSupplierOptions,
+ loadMore: loadMoreSuppliers,
+ } = useSelect(SupplierApi.basePath, 'id', 'name');
const dataTypeOptions = useMemo(
() => [
@@ -59,48 +67,51 @@ const DebtSupplierTab = () => {
[]
);
- // ===== FILTER HANDLERS =====
- const handleResetFilters = useCallback(() => {
- setIsSubmitted(false);
- setFilterSupplier([]);
- setFilterStartDate('');
- setFilterEndDate('');
- setFilterErrors({});
- }, []);
+ const handleFilterModalOpen = () => {
+ filterModal.openModal();
+ };
- const handleApplyFilters = useCallback(() => {
- const errors: Record = {};
-
- if (!filterStartDate) {
- errors.start_date = 'Tanggal mulai wajib diisi';
- }
- if (!filterEndDate) {
- errors.end_date = 'Tanggal akhir wajib diisi';
- }
-
- setFilterErrors(errors);
-
- if (Object.keys(errors).length === 0) {
- setIsSubmitted(true);
- setCurrentPage(1);
+ // ===== FORMIK SETUP =====
+ const formik = useFormik({
+ initialValues: {
+ startDate: null,
+ endDate: null,
+ supplierIds: null,
+ filterBy: null,
+ },
+ validationSchema: DebtSupplierFilterSchema,
+ onSubmit: (values) => {
+ setFilterParams({
+ start_date: values.startDate?.toString() || undefined,
+ end_date: values.endDate?.toString() || undefined,
+ supplier_ids:
+ values.supplierIds?.map((v) => String(v.value)).join(',') ||
+ undefined,
+ filter_by: values.filterBy?.value?.toString() || undefined,
+ });
filterModal.closeModal();
- }
- }, [filterModal, filterStartDate, filterEndDate]);
+ setIsSubmitted(true);
+ },
+ onReset: (values) => {
+ setFilterParams({
+ start_date: undefined,
+ end_date: undefined,
+ supplier_ids: undefined,
+ filter_by: undefined,
+ });
+ setIsSubmitted(false);
+ },
+ });
// ===== DATA FETCHING =====
const { data: debtSupplier, isLoading } = useSWR(
isSubmitted
? () => {
const params = {
- supplier_ids:
- filterSupplier.length > 0
- ? filterSupplier.map((v) => String(v.value)).join(',')
- : undefined,
- filter_by: filterDateType?.value,
- start_date: filterStartDate || undefined,
- end_date: filterEndDate || undefined,
- page: currentPage,
- limit: pageSize,
+ supplier_ids: filterParams.supplier_ids,
+ filter_by: filterParams.filter_by,
+ start_date: filterParams.start_date,
+ end_date: filterParams.end_date,
};
return ['debt-supplier-report', params];
@@ -109,11 +120,9 @@ const DebtSupplierTab = () => {
([, params]) =>
DebtSupplierApi.getDebtSupplierReport(
params.supplier_ids,
- params.filter_by?.toString(),
+ params.filter_by,
params.start_date,
- params.end_date,
- params.page,
- params.limit
+ params.end_date
)
);
@@ -135,13 +144,15 @@ const DebtSupplierTab = () => {
> => {
const params = {
supplier_ids:
- filterSupplier.length > 0
- ? filterSupplier.map((v) => String(v.value)).join(',')
+ formik.values.supplierIds && formik.values.supplierIds.length > 0
+ ? formik.values.supplierIds.map((v) => String(v.value)).join(',')
: undefined,
- filter_by: filterDateType?.value?.toString(),
- start_date: filterStartDate || undefined,
- end_date: filterEndDate || undefined,
- date_type: filterDateType ? filterDateType.value : undefined,
+ filter_by: formik.values.filterBy?.value?.toString() || undefined,
+ start_date: formik.values.startDate || undefined,
+ end_date: formik.values.endDate || undefined,
+ date_type: formik.values.filterBy
+ ? formik.values.filterBy.value
+ : undefined,
limit: 100,
page: 1,
};
@@ -150,15 +161,18 @@ const DebtSupplierTab = () => {
params.supplier_ids,
params.filter_by,
params.start_date,
- params.end_date,
- params.page,
- params.limit
+ params.end_date
);
return isResponseSuccess(response)
? (response.data as unknown as DebtSupplier[])
: null;
- }, [filterSupplier, filterStartDate, filterEndDate]);
+ }, [
+ formik.values.supplierIds,
+ formik.values.startDate,
+ formik.values.endDate,
+ formik.values.filterBy,
+ ]);
// ===== EXPORT HANDLERS =====
const handleExportExcel = useCallback(async () => {
@@ -207,37 +221,18 @@ const DebtSupplierTab = () => {
}
}, [debtSupplierExport]);
- // ===== PAGINATION HANDLERS =====
- const handlePageChange = (page: number) => {
- setCurrentPage(page);
- };
-
- const handleRowChange = (pageSize: number) => {
- setPageSize(pageSize);
- };
-
- const handleNextPage = () => {
- if (meta && currentPage < meta.total_pages) {
- setCurrentPage(currentPage + 1);
- }
- };
-
- const handlePrevPage = () => {
- if (currentPage > 1) {
- setCurrentPage(currentPage - 1);
- }
- };
-
const getTableColumns = (supplier: DebtSupplier): ColumnDef[] => [
{
id: 'no',
header: 'No',
- cell: (props) => props.row.index + 1,
+ enableSorting: false,
+ cell: (props) => props.row.index,
},
{
id: 'pr_number',
header: 'Nomor PR',
accessorKey: 'pr_number',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.pr_number;
return value || '-';
@@ -247,6 +242,7 @@ const DebtSupplierTab = () => {
id: 'po_number',
header: 'Nomor PO',
accessorKey: 'po_number',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.po_number;
return value || '-';
@@ -254,8 +250,9 @@ const DebtSupplierTab = () => {
},
{
id: 'received_date',
- header: 'Tanggal Terima',
+ header: 'Tanggal Terima/Bayar',
accessorKey: 'received_date',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.received_date;
return value
@@ -269,6 +266,7 @@ const DebtSupplierTab = () => {
id: 'po_date',
header: 'Tanggal PO',
accessorKey: 'po_date',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.po_date;
return value
@@ -282,6 +280,7 @@ const DebtSupplierTab = () => {
id: 'aging',
header: 'Aging',
accessorKey: 'aging',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.aging;
return {formatNumber(value)} Hari
;
@@ -295,6 +294,7 @@ const DebtSupplierTab = () => {
id: 'area',
header: 'Area',
accessorKey: 'area',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.area?.name;
return value || '-';
@@ -304,6 +304,7 @@ const DebtSupplierTab = () => {
id: 'warehouse',
header: 'Gudang',
accessorKey: 'warehouse',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.warehouse?.name;
return value || '-';
@@ -311,8 +312,9 @@ const DebtSupplierTab = () => {
},
{
id: 'due_date',
- header: 'Tanggal Jatuh Tempo',
+ header: 'Jatuh Tempo',
accessorKey: 'due_date',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.due_date;
return value
@@ -326,6 +328,7 @@ const DebtSupplierTab = () => {
id: 'due_status',
header: 'Status Jatuh Tempo',
accessorKey: 'due_status',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.due_status;
return value || '-';
@@ -333,8 +336,9 @@ const DebtSupplierTab = () => {
},
{
id: 'total_price',
- header: 'Total Harga',
+ header: 'Nominal Pembelian',
accessorKey: 'total_price',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.total_price;
return (
@@ -354,8 +358,9 @@ const DebtSupplierTab = () => {
},
{
id: 'payment_price',
- header: 'Harga Pembayaran',
+ header: 'Pembayaran',
accessorKey: 'payment_price',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.payment_price;
return (
@@ -374,11 +379,12 @@ const DebtSupplierTab = () => {
},
},
{
- id: 'debt_price',
- header: 'Harga Hutang',
- accessorKey: 'debt_price',
+ id: 'balance',
+ header: 'Sisa Saldo Hutang',
+ accessorKey: 'balance',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.debt_price;
+ const value = props.row.original.balance;
return (
{formatCurrency(value)}
@@ -398,6 +404,7 @@ const DebtSupplierTab = () => {
id: 'status',
header: 'Status',
accessorKey: 'status',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.status;
return value || '-';
@@ -407,6 +414,7 @@ const DebtSupplierTab = () => {
id: 'travel_number',
header: 'Nomor Perjalanan',
accessorKey: 'travel_number',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.travel_number;
return value || '-';
@@ -421,10 +429,11 @@ const DebtSupplierTab = () => {
className={{ wrapper: 'w-full', body: 'p-1!' }}
>
-
-
- Filter
-
+
{
collapsible={true}
>
0}
className={{
containerClassName: 'w-full',
@@ -493,26 +507,38 @@ const DebtSupplierTab = () => {
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
paginationClassName: 'hidden',
}}
+ renderCustomRow={(row) => {
+ if (row.index == 0) {
+ return (
+
+ |
+
+
+ {formatCurrency(row.original.balance)}
+
+ |
+ |
+
+ );
+ }
+ }}
/>
);
})
)}
- {meta && data.length > 0 && (
-
- )}
{/* Filter Modal */}
{
modalBox: 'p-0 rounded-2xl xl:max-w-4/12 max-w-sm',
}}
>
-
@@ -614,18 +650,15 @@ const DebtSupplierTab = () => {
Reset Filter
-
+
Apply Filter
-
+
>
);
diff --git a/src/components/pages/report/production-result/ProductionResultContent.tsx b/src/components/pages/report/production-result/ProductionResultContent.tsx
index ae6f744b..7820ff53 100644
--- a/src/components/pages/report/production-result/ProductionResultContent.tsx
+++ b/src/components/pages/report/production-result/ProductionResultContent.tsx
@@ -62,6 +62,7 @@ const ProductionResultContent = () => {
setInputValue: setAreaInputValue,
options: areaOptions,
isLoadingOptions: isLoadingAreaOptions,
+ loadMore: loadMoreAreas,
} = useSelect(AreaApi.basePath, 'id', 'name');
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -78,6 +79,7 @@ const ProductionResultContent = () => {
setInputValue: setLocationInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocationOptions,
+ loadMore: loadMoreLocations,
} = useSelect(LocationApi.basePath, 'id', 'name', 'search', {
area_id: selectedArea ? ((selectedArea as OptionType).value as string) : '',
});
@@ -94,6 +96,7 @@ const ProductionResultContent = () => {
setInputValue: setProjectFlockInputValue,
options: projectFlockOptions,
isLoadingOptions: isLoadingProjectFlockOptions,
+ loadMore: loadMoreProjectFlocks,
} = useSelect(
ProjectFlockApi.basePath,
'id',
@@ -120,6 +123,7 @@ const ProductionResultContent = () => {
setInputValue: setProjectFlockKandangInputValue,
options: projectFlockKandangOptions,
isLoadingOptions: isLoadingProjectFlockKandangOptions,
+ loadMore: loadMoreProjectFlockKandangs,
} = useSelect(
ProjectFlockKandangApi.basePath,
'id',
@@ -235,6 +239,7 @@ const ProductionResultContent = () => {
value={selectedArea}
onChange={areaChangeHandler}
onInputChange={setAreaInputValue}
+ onMenuScrollToBottom={loadMoreAreas}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -251,6 +256,7 @@ const ProductionResultContent = () => {
value={selectedLocation}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
+ onMenuScrollToBottom={loadMoreLocations}
isClearable
isDisabled={!selectedArea}
className={{
@@ -270,6 +276,7 @@ const ProductionResultContent = () => {
value={selectedProjectFlock}
onChange={projectFlockChangeHandler}
onInputChange={setProjectFlockInputValue}
+ onMenuScrollToBottom={loadMoreProjectFlocks}
isClearable
isDisabled={!selectedArea || !selectedLocation}
className={{
@@ -289,6 +296,7 @@ const ProductionResultContent = () => {
value={selectedProjectFlockKandang}
onChange={projectFlockKandangChangeHandler}
onInputChange={setProjectFlockKandangInputValue}
+ onMenuScrollToBottom={loadMoreProjectFlockKandangs}
isClearable
isDisabled={!selectedProjectFlock}
className={{
diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx
index 7d6f0951..eda88d8c 100644
--- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx
+++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx
@@ -58,18 +58,26 @@ const HppPerKandangTab = () => {
},
});
- const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
- AreaApi.basePath,
- 'id',
- 'name',
- 'search'
- );
+ const {
+ setInputValue: setAreaInputValue,
+ options: areaOptions,
+ isLoadingOptions: isLoadingAreas,
+ loadMore: loadMoreAreas,
+ } = useSelect(AreaApi.basePath, 'id', 'name', 'search');
- const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
- useSelect(LocationApi.basePath, 'id', 'name', 'search');
+ const {
+ setInputValue: setLocationInputValue,
+ options: locationOptions,
+ isLoadingOptions: isLoadingLocations,
+ loadMore: loadMoreLocations,
+ } = useSelect(LocationApi.basePath, 'id', 'name', 'search');
- const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
- useSelect(KandangApi.basePath, 'id', 'name', 'search');
+ const {
+ setInputValue: setKandangInputValue,
+ options: kandangOptions,
+ isLoadingOptions: isLoadingKandangs,
+ loadMore: loadMoreKandangs,
+ } = useSelect(KandangApi.basePath, 'id', 'name', 'search');
const showUnrecordedOptions: OptionType[] = [
{ value: 'false', label: 'Sembunyikan' },
@@ -810,6 +818,8 @@ const HppPerKandangTab = () => {
.includes(String(opt.value))
)}
onChange={areaChangeHandler}
+ onInputChange={setAreaInputValue}
+ onMenuScrollToBottom={loadMoreAreas}
isLoading={isLoadingAreas}
isClearable
/>
@@ -824,6 +834,8 @@ const HppPerKandangTab = () => {
.includes(String(opt.value))
)}
onChange={locationChangeHandler}
+ onInputChange={setLocationInputValue}
+ onMenuScrollToBottom={loadMoreLocations}
isLoading={isLoadingLocations}
isClearable
/>
@@ -838,6 +850,8 @@ const HppPerKandangTab = () => {
.includes(String(opt.value))
)}
onChange={kandangChangeHandler}
+ onInputChange={setKandangInputValue}
+ onMenuScrollToBottom={loadMoreKandangs}
isLoading={isLoadingKandangs}
isClearable
/>
diff --git a/src/config/constant.ts b/src/config/constant.ts
index 364f1824..d3832613 100644
--- a/src/config/constant.ts
+++ b/src/config/constant.ts
@@ -457,3 +457,14 @@ export const MARKETING_TYPE_OPTIONS = [
value: 'trading',
},
];
+
+export const MARKETING_DATE_FILTER_TYPE_OPTIONS = [
+ {
+ label: 'Tanggal Realisasi',
+ value: 'realization_date',
+ },
+ {
+ label: 'Tanggal SO',
+ value: 'so_date',
+ },
+];
diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts
index 9a0c9d2e..165bc8ee 100644
--- a/src/config/route-permission.ts
+++ b/src/config/route-permission.ts
@@ -5,22 +5,22 @@ export const ROUTE_PERMISSIONS: Record = {
'/dashboard/': ['lti.dashboard.list'],
// Daily Checklist
- // TODO: use real daily checklist permission name
- // '/daily-checklist/': ['lti.daily_checklist.list'],
- // '/daily-checklist/dashboard/': ['lti.daily_checklist.list'],
- // '/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'],
- // '/daily-checklist/list-daily-checklist/detail/': ['lti.daily_checklist.detail'],
- // '/daily-checklist/reports/': ['lti.daily_checklist.reports'],
- // '/daily-checklist/master-data/employee/': ['lti.dashboard.master_data.employee'],
- // '/daily-checklist/master-data/activity/': ['lti.dashboard.master_data.activity'],
- '/daily-checklist/dashboard/': ['lti.dashboard.list'],
- '/daily-checklist/daily-checklist/': ['lti.dashboard.list'],
- '/daily-checklist/list-daily-checklist/': ['lti.dashboard.list'],
- '/daily-checklist/list-daily-checklist/detail/': ['lti.dashboard.list'],
- '/daily-checklist/reports/': ['lti.dashboard.list'],
- '/daily-checklist/master-data/employee/': ['lti.dashboard.list'],
- '/daily-checklist/master-data/activity/': ['lti.dashboard.list'],
- '/daily-checklist/master-data/configuration/': ['lti.dashboard.list'],
+ '/daily-checklist/dashboard/': ['lti.daily_checklist.dashboard.list'],
+ '/daily-checklist/daily-checklist/': ['lti.daily_checklist.create'],
+ '/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'],
+ '/daily-checklist/list-daily-checklist/detail/': [
+ 'lti.daily_checklist.detail',
+ ],
+ '/daily-checklist/reports/': ['lti.daily_checklist.reports'],
+ '/daily-checklist/master-data/employee/': [
+ 'lti.daily_checklist.master_data.employee',
+ ],
+ '/daily-checklist/master-data/activity/': [
+ 'lti.daily_checklist.master_data.activity',
+ ],
+ '/daily-checklist/master-data/configuration/': [
+ 'lti.daily_checklist.master_data.configuration',
+ ],
// Production
// Production - Project Flock
diff --git a/src/lib/formik-helper.ts b/src/lib/formik-helper.ts
index 9c457e86..17c6bc7d 100644
--- a/src/lib/formik-helper.ts
+++ b/src/lib/formik-helper.ts
@@ -1,4 +1,4 @@
-import { FormikErrors } from 'formik';
+import { FormikErrors, FormikValues } from 'formik';
export type ErrorMessage = {
key: string;
@@ -69,3 +69,66 @@ export function getUniqueFormikErrors(errors: FormikErrors): string[] {
export function getAllFormikErrors(errors: FormikErrors): ErrorMessage[] {
return parseFormikErrors(errors);
}
+
+/**
+ * Check if a value is considered "filled" (not empty)
+ * @param value - Value to check
+ * @returns True if value is filled, false otherwise
+ */
+function isValueFilled(value: unknown): boolean {
+ // Check for null or undefined
+ if (value === null || value === undefined) {
+ return false;
+ }
+
+ // Check for empty string
+ if (typeof value === 'string' && value.trim() === '') {
+ return false;
+ }
+
+ // Check for empty array
+ if (Array.isArray(value) && value.length === 0) {
+ return false;
+ }
+
+ // Check for empty object (but not Date or other special objects)
+ if (
+ typeof value === 'object' &&
+ !Array.isArray(value) &&
+ !(value instanceof Date) &&
+ Object.keys(value).length === 0
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Count the number of filled (non-empty) values in Formik values object
+ * @param values - Formik values object
+ * @returns Number of filled values
+ * @example
+ * const values = {
+ * name: 'John',
+ * email: '',
+ * age: null,
+ * tags: ['tag1', 'tag2'],
+ * emptyArray: [],
+ * };
+ * getFilledFormikValuesCount(values); // Returns 2 (name and tags)
+ */
+export function getFilledFormikValuesCount(
+ values: T
+): number {
+ let count = 0;
+
+ Object.keys(values).forEach((key) => {
+ const value = values[key];
+ if (isValueFilled(value)) {
+ count++;
+ }
+ });
+
+ return count;
+}
diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts
index 892fc88e..b2ba2b8f 100644
--- a/src/services/api/closing.ts
+++ b/src/services/api/closing.ts
@@ -131,10 +131,11 @@ export class ClosingApiService extends BaseApiService {
}
async getOverhead(
- id: number
+ id: number,
+ kandangId?: number
): Promise | undefined> {
try {
- const path = `${this.basePath}/${id}/overhead`;
+ const path = `${this.basePath}/${id}${kandangId ? `/${kandangId}` : ''}/overhead`;
return await httpClient>(path, {
method: 'GET',
});
@@ -147,10 +148,11 @@ export class ClosingApiService extends BaseApiService {
}
async getFinance(
- id: number
+ id: number,
+ kandangId?: number
): Promise | undefined> {
try {
- const path = `${this.basePath}/${id}/keuangan`;
+ const path = `${this.basePath}/${id}${kandangId ? `/${kandangId}` : ''}/keuangan`;
return await httpClient>(path, {
method: 'GET',
});
diff --git a/src/services/api/report/debt-supplier.ts b/src/services/api/report/debt-supplier.ts
index dad46d18..706c873e 100644
--- a/src/services/api/report/debt-supplier.ts
+++ b/src/services/api/report/debt-supplier.ts
@@ -15,9 +15,7 @@ export class DebtSupplierApiService extends BaseApiService<
supplier_ids?: string,
filter_by?: string,
start_date?: string,
- end_date?: string,
- page?: number,
- limit?: number
+ end_date?: string
): Promise | undefined> {
return await this.customRequest>(
`debt-supplier`,
@@ -28,8 +26,6 @@ export class DebtSupplierApiService extends BaseApiService<
filter_by: filter_by,
start_date: start_date,
end_date: end_date,
- page: page,
- limit: limit,
},
}
);
diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts
index e8ec52c8..9fa4f37c 100644
--- a/src/services/api/report/finance-report.ts
+++ b/src/services/api/report/finance-report.ts
@@ -14,7 +14,7 @@ export class FinanceApiService extends BaseApiService<
async getCustomerPaymentReport(
customer_id?: string,
- sales?: string,
+ sales_id?: string,
filter_by?: 'do_date',
start_date?: string,
end_date?: string,
@@ -27,7 +27,7 @@ export class FinanceApiService extends BaseApiService<
method: 'GET',
params: {
customer_id: customer_id,
- sales: sales,
+ sales_id: sales_id,
filter_by: filter_by,
start_date: start_date,
end_date: end_date,
diff --git a/src/services/api/report/marketing-sale.ts b/src/services/api/report/marketing-sale.ts
index bb9c1f49..92c59b2c 100644
--- a/src/services/api/report/marketing-sale.ts
+++ b/src/services/api/report/marketing-sale.ts
@@ -44,9 +44,7 @@ export class MarketingSaleReportService extends BaseApiService<
}
}
-export const SaleReportApi = new MarketingSaleReportService(
- 'reports/marketings'
-);
+export const SaleReportApi = new MarketingSaleReportService('reports');
// export const SaleReportApi = new MarketingSaleReportService(
// 'http://localhost:4010/api/reports/marketings'
diff --git a/src/types/api/master-data/product.d.ts b/src/types/api/master-data/product.d.ts
index e82f857e..7fd2c7c1 100644
--- a/src/types/api/master-data/product.d.ts
+++ b/src/types/api/master-data/product.d.ts
@@ -1,20 +1,20 @@
import { BaseMetadata } from '@/types/api/api-general';
import { Uom } from '@/types/api/master-data/uom';
import { ProductCategory } from '@/types/api/master-data/product-category';
-import { Supplier } from '@/types/api/master-data/supplier';
+import { BaseSupplier, Supplier } from '@/types/api/master-data/supplier';
export type BaseProduct = {
id: number;
name: string;
brand: string;
- sku: string;
+ sku?: string;
product_price: number;
selling_price?: number;
tax?: number;
- expiry_period: number;
+ expiry_period?: number;
uom: Uom;
product_category: ProductCategory;
- suppliers: Supplier[];
+ suppliers: (BaseSupplier & { price: number })[];
flags: string[];
};
@@ -23,14 +23,17 @@ export type Product = BaseMetadata & BaseProduct;
export type CreateProductPayload = {
name: string;
brand: string;
- sku: string;
+ sku?: string;
uom_id: number;
product_category_id: number;
product_price: number;
- selling_price: number;
- tax: number;
- expiry_period: number;
- supplier_ids: number[];
+ selling_price?: number;
+ tax?: number;
+ expiry_period?: number;
+ suppliers: {
+ supplier_id: number;
+ price: number;
+ }[];
flags: string[];
};
diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts
index 1728516a..e30abe4c 100644
--- a/src/types/api/production/recording.d.ts
+++ b/src/types/api/production/recording.d.ts
@@ -1,34 +1,52 @@
import { BaseApproval, BaseMetadata, User } from '@/types/api/api-general';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
+import { Warehouse } from '@/types/api/master-data/warehouse';
+
+export type ProductionStandard = {
+ id: number;
+ week: number;
+ name: string;
+ hen_day_std: number;
+ hen_house_std: number;
+ feed_intake_std: number;
+ max_depletion_std: number;
+ egg_mass_std: number;
+ egg_weight_std: number;
+};
+
+export type FCR = {
+ id: number;
+ name: string;
+ fcr_std: number;
+};
+
+export type ProjectFlock = {
+ project_flock_kandang_id: number;
+ flock_name: string;
+ project_flock_category: 'GROWING' | 'LAYING';
+ period: number;
+ production_standart: ProductionStandard;
+ fcr: FCR;
+ total_chick_qty: number;
+};
export type ProductionMetrics = {
total_depletion_qty: number;
cum_depletion_rate: number;
cum_intake: number;
fcr_value: number;
- fcr_std?: number;
- total_chick_qty: number;
hen_day?: number;
hen_house?: number;
feed_intake?: number;
- feed_intake_std?: number;
egg_mass?: number;
egg_weight?: number;
- hen_day_std?: number;
- hen_house_std?: number;
- egg_mass_std?: number;
- egg_weight_std?: number;
- daily_gain?: number;
- avg_daily_gain?: number;
- cum_depletion?: number;
};
export type BaseRecording = {
id: number;
- project_flock_kandang_id: number;
+ project_flock: ProjectFlock;
record_datetime: string;
day: number;
- project_flock_category?: 'GROWING' | 'LAYING';
} & ProductionMetrics;
export type RecordingDepletion = {
@@ -68,6 +86,8 @@ export type Recording = BaseMetadata &
BaseRecording & {
approval?: BaseApproval;
created_user: User;
+ warehouse?: Warehouse;
+ product_category?: 'GROWING' | 'LAYING';
depletions?: RecordingDepletion[];
stocks?: RecordingStock[];
eggs?: RecordingEgg[];
@@ -81,6 +101,7 @@ export type NextDayRecording = {
export type CreateGrowingRecordingPayload = {
project_flock_kandang_id: number;
+ record_date: string;
stocks?: {
product_warehouse_id: number;
qty: number;
diff --git a/src/types/api/report/debt-supplier.d.ts b/src/types/api/report/debt-supplier.d.ts
index 46849599..c00db7df 100644
--- a/src/types/api/report/debt-supplier.d.ts
+++ b/src/types/api/report/debt-supplier.d.ts
@@ -33,3 +33,11 @@ export interface DebtRow {
travel_number: string;
balance: number;
}
+
+// Filter Param
+export interface DebtSupplierFilter {
+ start_date?: string;
+ end_date?: string;
+ supplier_ids?: string;
+ filter_by?: string;
+}