[] = [
+ {
+ id: 'no',
+ header: 'No',
+ cell: (props) => props.row.index + 1,
+ footer: () => Total
,
+ },
+ {
+ id: 'do_date_or_payment_date',
+ header: 'Tanggal DO/Bayar',
+ accessorKey: 'do_date',
+ cell: (props) => {
+ const value = props.row.original.do_date;
+ return formatDate(value, 'DD MMM YYYY');
+ },
+ },
+ {
+ id: 'realization_date',
+ header: 'Tanggal Realisasi',
+ accessorKey: 'realization_date',
+ cell: (props) => {
+ const value = props.row.original.realization_date;
+ return formatDate(value, 'DD MMM YYYY');
+ },
+ },
+ {
+ id: 'aging',
+ header: 'Aging',
+ accessorKey: 'aging',
+ cell: (props) => {
+ const value = props.row.original.aging;
+ return {formatNumber(value)} hari
;
+ },
+ },
+ {
+ id: 'reference',
+ header: 'Referensi',
+ accessorKey: 'reference',
+ cell: (props) => {
+ const value = props.row.original.reference;
+ return value || '-';
+ },
+ },
+ {
+ id: 'vehicle_plate',
+ header: 'Nomor Polisi',
+ accessorKey: 'vehicle_plate',
+ cell: (props) => {
+ const value = props.row.original.vehicle_plate;
+ return value || '-';
+ },
+ },
+ {
+ id: 'qty',
+ header: 'Ekor/Qty',
+ accessorKey: 'qty',
+ cell: (props) => {
+ const value = props.row.original.qty;
+ return {formatNumber(value)}
;
+ },
+ footer: () => (
+
+ {formatNumber(summary.total_qty) || '-'}
+
+ ),
+ },
+ {
+ id: 'weight',
+ header: 'Berat (Kg)',
+ accessorKey: 'weight',
+ cell: (props) => {
+ const value = props.row.original.weight;
+ return {formatNumber(value)}
;
+ },
+ footer: () => (
+
+ {formatNumber(summary.total_weight) || '-'}
+
+ ),
+ },
+ {
+ id: 'average_weight',
+ header: 'AVG',
+ accessorKey: 'average_weight',
+ cell: (props) => {
+ const value = props.row.original.average_weight;
+ return {formatNumber(value)}
;
+ },
+ footer: () => (
+ -
+ ),
+ },
+ {
+ id: 'price',
+ header: 'Harga Awal',
+ accessorKey: 'price',
+ cell: (props) => {
+ const value = props.row.original.price;
+ return {formatCurrency(value)}
;
+ },
+ footer: () => (
+
+ {formatCurrency(summary.total_initial_amount) || '-'}
+
+ ),
+ },
+ {
+ id: 'credit_note',
+ header: 'CN',
+ accessorKey: 'credit_note',
+ cell: (props) => {
+ const value = props.row.original.credit_note;
+ return {formatCurrency(value)}
;
+ },
+ footer: () => (
+
+ {formatCurrency(summary.total_credit_note) || '-'}
+
+ ),
+ },
+ {
+ id: 'final_price',
+ header: 'Harga Akhir',
+ accessorKey: 'final_price',
+ cell: (props) => {
+ const value = props.row.original.final_price;
+ return {formatCurrency(value)}
;
+ },
+ footer: () => (
+
+ {formatCurrency(summary.total_final_amount) || '-'}
+
+ ),
+ },
+ {
+ id: 'ppn',
+ header: 'PPN (%)',
+ accessorKey: 'ppn',
+ cell: (props) => {
+ const value = props.row.original.ppn;
+ return {formatNumber(value)}%
;
+ },
+ footer: () => (
+ -
+ ),
+ },
+ {
+ id: 'total',
+ header: 'Total',
+ accessorKey: 'total',
+ cell: (props) => {
+ const value = props.row.original.total;
+ return {formatCurrency(value)}
;
+ },
+ footer: () => (
+
+ {formatCurrency(summary.total_grand_amount) || '-'}
+
+ ),
+ },
+ {
+ id: 'payment',
+ header: 'Pembayaran',
+ accessorKey: 'payment',
+ cell: (props) => {
+ const value = props.row.original.payment;
+ return {formatCurrency(value)}
;
+ },
+ footer: () => (
+
+ {formatCurrency(summary.total_payment) || '-'}
+
+ ),
+ },
+ {
+ id: 'accounts_receivable',
+ header: 'Saldo Piutang',
+ accessorKey: 'accounts_receivable',
+ cell: (props) => {
+ const value = props.row.original.accounts_receivable;
+ return {formatCurrency(value)}
;
+ },
+ footer: () => (
+
+ {formatCurrency(summary.total_accounts_receivable) || '-'}
+
+ ),
+ },
+ {
+ id: 'notes',
+ header: 'Keterangan',
+ accessorKey: 'notes',
+ cell: (props) => {
+ const value = props.row.original.notes;
+ return value || '-';
+ },
+ },
+ {
+ id: 'pickup_info',
+ header: 'Pengambilan',
+ accessorKey: 'pickup_info',
+ cell: (props) => {
+ const value = props.row.original.pickup_info;
+ return value || '-';
+ },
+ },
+ {
+ id: 'sales_marketing',
+ header: 'Sales/Marketing',
+ accessorKey: 'sales_marketing',
+ cell: (props) => {
+ const value = props.row.original.sales_marketing;
+ return value || '-';
+ },
+ },
+ ];
+ return tableColumns;
+ };
+
+ return (
+
+
+
+
+
+
+
+ Export
+
+ }
+ align='end'
+ >
+
+
+
+
+ {/* Filter Modal */}
+
+
+ {/* Modal Header */}
+
+
+
+
Filter Data
+
+
+
+
+
+
+
{
+ setFilterStartDate(e.target.value);
+ setFilterErrors((prev) => ({ ...prev, start_date: '' }));
+ }}
+ className={{ wrapper: 'w-full' }}
+ />
+ {filterErrors.start_date && (
+
+ {filterErrors.start_date}
+
+ )}
+
+
+
+
{
+ setFilterEndDate(e.target.value);
+ setFilterErrors((prev) => ({ ...prev, end_date: '' }));
+ }}
+ className={{ wrapper: 'w-full' }}
+ />
+ {filterErrors.end_date && (
+
+ {filterErrors.end_date}
+
+ )}
+
+
+
+
+ {
+ setFilterCustomer(
+ Array.isArray(val) ? val : val ? [val] : []
+ );
+ }}
+ isLoading={isLoadingCustomers}
+ isClearable
+ className={{ wrapper: 'w-full' }}
+ />
+
+
+
+ {
+ setFilterSales(Array.isArray(val) ? val : val ? [val] : []);
+ }}
+ isClearable
+ className={{ wrapper: 'w-full' }}
+ />
+
+
+
+
+
+
+
+ {/* Action Buttons */}
+
+
+
+
+
+
+
+ {!isSubmitted ? (
+
+ Silakan klik tombol Filter untuk mengatur filter dan menampilkan
+ data.
+
+ ) : isLoading ? (
+
+
+
+ ) : data.length === 0 ? (
+
+ Tidak ada data yang dapat ditampilkan...
+
+ ) : (
+ data.map((customerReport) => {
+ const summary = customerReport.summary || {
+ total_qty: 0,
+ total_weight: 0,
+ total_initial_amount: 0,
+ total_credit_note: 0,
+ total_final_amount: 0,
+ total_ppn: 0,
+ total_grand_amount: 0,
+ total_payment: 0,
+ total_accounts_receivable: 0,
+ };
+
+ const totalAccountsReceivable = summary.total_accounts_receivable;
+ const tableColumns = getTableColumns(summary);
+
+ return (
+
+ 0}
+ className={{
+ containerClassName: 'w-full',
+ tableWrapperClassName: 'overflow-x-auto mt-4',
+ tableClassName: 'w-full table-auto text-sm',
+ headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
+ headerColumnClassName:
+ 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
+ bodyRowClassName:
+ 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
+ bodyColumnClassName:
+ 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
+ tableFooterClassName:
+ 'bg-gray-100 font-semibold border border-gray-200',
+ footerRowClassName: 'border-t-2 border-gray-300',
+ footerColumnClassName:
+ 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
+ paginationClassName: 'hidden',
+ }}
+ />
+
+ );
+ })
+ )}
+
+ {meta && data.length > 0 && (
+
+ )}
+
+ );
+};
+
+export default CustomerPaymentTab;
diff --git a/src/config/constant.ts b/src/config/constant.ts
index f7f2255e..77e210a2 100644
--- a/src/config/constant.ts
+++ b/src/config/constant.ts
@@ -127,6 +127,10 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
link: '/report',
icon: 'mdi:chart-box-outline',
submenu: [
+ {
+ text: 'Keuangan',
+ link: '/report/finance',
+ },
{
text: 'Logistik & Persediaan',
link: '/report/logistic-stock',
diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts
index ca720f28..10a66f8c 100644
--- a/src/config/route-permission.ts
+++ b/src/config/route-permission.ts
@@ -117,6 +117,7 @@ export const ROUTE_PERMISSIONS: Record = {
'/report/expense/': ['lti.repport.expense.list'],
'/report/marketing/': ['lti.repport.delivery.list'],
'/report/production-result/': ['lti.repport.production_result.list'],
+ '/report/finance/': ['lti.repport.finance.list'],
// Inventory
'/inventory/adjustment/': ['lti.inventory.list'],
diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts
new file mode 100644
index 00000000..e0acb6b0
--- /dev/null
+++ b/src/services/api/report/finance-report.ts
@@ -0,0 +1,45 @@
+import { BaseApiService } from '@/services/api/base';
+import { BaseApiResponse } from '@/types/api/api-general';
+import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
+
+export class FinanceApiService extends BaseApiService<
+ CustomerPaymentReport,
+ unknown,
+ unknown
+> {
+ constructor(basePath: string) {
+ super(basePath);
+ }
+
+ async getCustomerPaymentReport(
+ customer_id?: string,
+ sales?: string,
+ filter_by?: 'do_date',
+ start_date?: string,
+ end_date?: string,
+ page?: number,
+ limit?: number
+ ): Promise | undefined> {
+ return await this.customRequest>(
+ `customer-payment`,
+ {
+ method: 'GET',
+ params: {
+ customer_id: customer_id,
+ sales: sales,
+ filter_by: filter_by,
+ start_date: start_date,
+ end_date: end_date,
+ page: page,
+ limit: limit,
+ },
+ }
+ );
+ }
+}
+
+export const FinanceApi = new FinanceApiService('reports');
+
+// export const FinanceApi = new FinanceApiService(
+// 'http://localhost:4010/api/reports/finance'
+// );
diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts
new file mode 100644
index 00000000..776d640d
--- /dev/null
+++ b/src/types/api/report/customer-payment.d.ts
@@ -0,0 +1,47 @@
+import { BaseMetadata } from '@/types/api/api-general';
+import { BaseCustomer } from '@/types/api/master-data/customer';
+import { BaseProduct } from '@/types/api/master-data/product';
+
+export type CustomerPaymentRow = {
+ no: number;
+ do_date: string;
+ payment_date: string;
+ realization_date: string;
+ aging: number;
+ reference: string;
+ vehicle_plate: string;
+ qty: number;
+ weight: number;
+ average_weight: number;
+ price: number;
+ credit_note: number;
+ final_price: number;
+ ppn: number;
+ total: number;
+ payment: number;
+ accounts_receivable: number;
+ notes: string;
+ pickup_info: string;
+ sales_marketing: string;
+ product?: BaseProduct;
+};
+
+export type CustomerPaymentSummary = {
+ total_qty: number;
+ total_weight: number;
+ total_initial_amount: number;
+ total_credit_note: number;
+ total_final_amount: number;
+ total_ppn: number;
+ total_grand_amount: number;
+ total_payment: number;
+ total_accounts_receivable: number;
+};
+
+export type CustomerPaymentReport = BaseMetadata & {
+ customer: BaseCustomer;
+ customer_npwp: string;
+ customer_address: string;
+ rows: CustomerPaymentRow[];
+ summary: CustomerPaymentSummary;
+};