mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
fix(FE): adding color to negative value excel and change select UI
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
OptionProps,
|
||||
GroupBase,
|
||||
components as ReactSelectComponents,
|
||||
} from 'react-select';
|
||||
import SelectInput, { OptionType, SelectInputProps } from './SelectInput';
|
||||
import { cn } from '@/lib/helper';
|
||||
|
||||
interface SelectInputRadioProps<T = OptionType>
|
||||
extends Omit<SelectInputProps<T>, 'closeMenuOnSelect' | 'optionComponent'> {
|
||||
closeMenuOnSelect?: boolean;
|
||||
}
|
||||
|
||||
const RadioOption = <
|
||||
T extends OptionType,
|
||||
IsMulti extends boolean,
|
||||
Group extends GroupBase<T>,
|
||||
>(
|
||||
props: OptionProps<T, IsMulti, Group>
|
||||
) => {
|
||||
const { isSelected, label, innerRef, innerProps, className } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={innerRef}
|
||||
{...innerProps}
|
||||
className={cn(
|
||||
'flex items-center gap-2 px-3 py-2 cursor-pointer',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type='radio'
|
||||
checked={isSelected}
|
||||
onChange={() => null}
|
||||
className='radio radio-sm radio-primary pointer-events-none'
|
||||
/>
|
||||
<label className='cursor-pointer flex-1 select-none'>{label}</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectInputRadio = <T extends OptionType>(
|
||||
props: SelectInputRadioProps<T>
|
||||
) => {
|
||||
const { closeMenuOnSelect = true, className, ...restProps } = props;
|
||||
|
||||
const customComponents = useMemo(() => {
|
||||
return {
|
||||
Option: RadioOption as typeof ReactSelectComponents.Option,
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SelectInput<T>
|
||||
{...restProps}
|
||||
closeMenuOnSelect={closeMenuOnSelect}
|
||||
className={{
|
||||
...className,
|
||||
select: cn(className?.select, 'select-radio'),
|
||||
}}
|
||||
components={customComponents}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectInputRadio;
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import * as XLSX from 'xlsx';
|
||||
import ExcelJS from 'exceljs';
|
||||
import { formatDate } from '@/lib/helper';
|
||||
import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
|
||||
|
||||
@@ -8,115 +8,174 @@ interface DebtSupplierExportExcelParams {
|
||||
data: DebtSupplier[];
|
||||
}
|
||||
|
||||
export const generateDebtSupplierExcel = (
|
||||
export const generateDebtSupplierExcel = async (
|
||||
params: DebtSupplierExportExcelParams
|
||||
): void => {
|
||||
): Promise<void> => {
|
||||
if (!params.data || params.data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workbook = XLSX.utils.book_new();
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
|
||||
params.data.forEach((supplierReport) => {
|
||||
const columns = [
|
||||
{ header: 'No', key: 'no', width: 5 },
|
||||
{ header: 'Nomor PR', key: 'prNumber', width: 14 },
|
||||
{ header: 'Nomor PO', key: 'poNumber', width: 14 },
|
||||
{ header: 'Tanggal Terima/Bayar', key: 'receivedDate', width: 20 },
|
||||
{ header: 'Tanggal PO', key: 'poDate', width: 10 },
|
||||
{ header: 'Aging (Hari)', key: 'aging', width: 10 },
|
||||
{ header: 'Area', key: 'area', width: 15 },
|
||||
{ header: 'Gudang', key: 'warehouse', width: 15 },
|
||||
{ header: 'Jatuh Tempo', key: 'dueDate', width: 12 },
|
||||
{ header: 'Status Jatuh Tempo', key: 'dueStatus', width: 20 },
|
||||
{ header: 'Nominal Pembelian (Rp)', key: 'totalPrice', width: 20 },
|
||||
{ header: 'Pembayaran (Rp)', key: 'paymentPrice', width: 15 },
|
||||
{ header: 'Sisa Saldo Hutang (Rp)', key: 'balance', width: 20 },
|
||||
{ header: 'Status', key: 'status', width: 12 },
|
||||
{ header: 'Nomor Perjalanan', key: 'travelNumber', width: 15 },
|
||||
];
|
||||
|
||||
for (const supplierReport of params.data) {
|
||||
const supplierData = supplierReport.rows;
|
||||
const supplierName = supplierReport.supplier.name || 'Unknown Supplier';
|
||||
|
||||
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/Bayar': item.received_date
|
||||
const worksheet = workbook.addWorksheet(supplierName.substring(0, 31));
|
||||
worksheet.columns = columns;
|
||||
|
||||
// Add initial balance row
|
||||
const initialRow = worksheet.addRow({
|
||||
no: '',
|
||||
prNumber: '',
|
||||
poNumber: '',
|
||||
receivedDate: '',
|
||||
poDate: '',
|
||||
aging: '',
|
||||
area: '',
|
||||
warehouse: '',
|
||||
dueDate: '',
|
||||
dueStatus: '',
|
||||
totalPrice: '',
|
||||
paymentPrice: '',
|
||||
balance: supplierReport.initial_balance || 0,
|
||||
status: '',
|
||||
travelNumber: '',
|
||||
});
|
||||
|
||||
// Apply red color if initial balance is negative
|
||||
const initialBalanceCell = initialRow.getCell('balance');
|
||||
if (
|
||||
typeof supplierReport.initial_balance === 'number' &&
|
||||
supplierReport.initial_balance < 0
|
||||
) {
|
||||
initialBalanceCell.font = { color: { argb: 'FFFF0000' } };
|
||||
}
|
||||
|
||||
// Add data rows
|
||||
supplierData.forEach((item, index) => {
|
||||
const row = worksheet.addRow({
|
||||
no: index + 1,
|
||||
prNumber: item.pr_number || '',
|
||||
poNumber: item.po_number || '',
|
||||
receivedDate: item.received_date
|
||||
? item.received_date != '-'
|
||||
? formatDate(item.received_date, 'MM/DD/YYYY')
|
||||
: '-'
|
||||
: '-',
|
||||
'Tanggal PO': item.po_date
|
||||
poDate: item.po_date
|
||||
? item.po_date != '-'
|
||||
? formatDate(item.po_date, 'MM/DD/YYYY')
|
||||
: '-'
|
||||
: '-',
|
||||
'Aging (Hari)': item.aging || 0,
|
||||
Area: item.area?.name || '',
|
||||
Gudang: item.warehouse?.name || '',
|
||||
'Jatuh Tempo': item.due_date
|
||||
aging: item.aging || 0,
|
||||
area: item.area?.name || '',
|
||||
warehouse: item.warehouse?.name || '',
|
||||
dueDate: item.due_date
|
||||
? item.due_date != '-'
|
||||
? formatDate(item.due_date, 'MM/DD/YYYY')
|
||||
: '-'
|
||||
: '-',
|
||||
'Status Jatuh Tempo': item.due_status || '',
|
||||
'Nominal Pembelian (Rp)': item.total_price || 0,
|
||||
'Pembayaran (Rp)': item.payment_price || 0,
|
||||
'Sisa Saldo Hutang (Rp)': item.balance || 0,
|
||||
Status: item.status || '',
|
||||
'Nomor Perjalanan': item.travel_number || '',
|
||||
})),
|
||||
];
|
||||
|
||||
if (supplierReport.total) {
|
||||
excelData.push({
|
||||
No: 'Total',
|
||||
'Nomor PR': '',
|
||||
'Nomor PO': '',
|
||||
'Tanggal Terima/Bayar': '',
|
||||
'Tanggal PO': '',
|
||||
'Aging (Hari)': supplierReport.total.aging || 0,
|
||||
Area: '',
|
||||
Gudang: '',
|
||||
'Jatuh Tempo': '',
|
||||
'Status Jatuh Tempo': '',
|
||||
'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': '',
|
||||
dueStatus: item.due_status || '',
|
||||
totalPrice: item.total_price || 0,
|
||||
paymentPrice: item.payment_price || 0,
|
||||
balance: item.balance || 0,
|
||||
status: item.status || '',
|
||||
travelNumber: item.travel_number || '',
|
||||
});
|
||||
|
||||
// Apply red color for negative values
|
||||
const totalPriceCell = row.getCell('totalPrice');
|
||||
if (typeof item.total_price === 'number' && item.total_price < 0) {
|
||||
totalPriceCell.font = { color: { argb: 'FFFF0000' } };
|
||||
}
|
||||
|
||||
const paymentPriceCell = row.getCell('paymentPrice');
|
||||
if (typeof item.payment_price === 'number' && item.payment_price < 0) {
|
||||
paymentPriceCell.font = { color: { argb: 'FFFF0000' } };
|
||||
}
|
||||
|
||||
const balanceCell = row.getCell('balance');
|
||||
if (typeof item.balance === 'number' && item.balance < 0) {
|
||||
balanceCell.font = { color: { argb: 'FFFF0000' } };
|
||||
}
|
||||
});
|
||||
|
||||
// Add total row
|
||||
if (supplierReport.total) {
|
||||
const totalRow = worksheet.addRow({
|
||||
no: 'Total',
|
||||
prNumber: '',
|
||||
poNumber: '',
|
||||
receivedDate: '',
|
||||
poDate: '',
|
||||
aging: supplierReport.total.aging || 0,
|
||||
area: '',
|
||||
warehouse: '',
|
||||
dueDate: '',
|
||||
dueStatus: '',
|
||||
totalPrice: supplierReport.total.total_price || 0,
|
||||
paymentPrice: supplierReport.total.payment_price || 0,
|
||||
balance: supplierReport.total.debt_price || 0,
|
||||
status: '',
|
||||
travelNumber: '',
|
||||
});
|
||||
|
||||
// Apply red color for negative totals
|
||||
const totalPriceCell = totalRow.getCell('totalPrice');
|
||||
if (
|
||||
typeof supplierReport.total.total_price === 'number' &&
|
||||
supplierReport.total.total_price < 0
|
||||
) {
|
||||
totalPriceCell.font = { color: { argb: 'FFFF0000' } };
|
||||
}
|
||||
|
||||
const paymentPriceCell = totalRow.getCell('paymentPrice');
|
||||
if (
|
||||
typeof supplierReport.total.payment_price === 'number' &&
|
||||
supplierReport.total.payment_price < 0
|
||||
) {
|
||||
paymentPriceCell.font = { color: { argb: 'FFFF0000' } };
|
||||
}
|
||||
|
||||
const balanceCell = totalRow.getCell('balance');
|
||||
if (
|
||||
typeof supplierReport.total.debt_price === 'number' &&
|
||||
supplierReport.total.debt_price < 0
|
||||
) {
|
||||
balanceCell.font = { color: { argb: 'FFFF0000' } };
|
||||
}
|
||||
}
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(excelData);
|
||||
|
||||
const colWidths = [
|
||||
{ wch: 5 }, // No
|
||||
{ wch: 10 }, // Nomor PR
|
||||
{ wch: 10 }, // Nomor PO
|
||||
{ wch: 20 }, // Tanggal Terima/Bayar
|
||||
{ wch: 10 }, // Tanggal PO
|
||||
{ wch: 10 }, // Aging
|
||||
{ wch: 15 }, // Area
|
||||
{ wch: 15 }, // Gudang
|
||||
{ wch: 12 }, // Jatuh Tempo
|
||||
{ wch: 20 }, // Status Jatuh Tempo
|
||||
{ wch: 20 }, // Nominal Pembelian (Rp)
|
||||
{ wch: 15 }, // Pembayaran (Rp)
|
||||
{ wch: 20 }, // Sisa Saldo Hutang (Rp)
|
||||
{ wch: 12 }, // Status
|
||||
{ wch: 15 }, // Nomor Perjalanan
|
||||
];
|
||||
worksheet['!cols'] = colWidths;
|
||||
|
||||
const sheetName =
|
||||
supplierName.length > 31 ? supplierName.substring(0, 31) : supplierName;
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
||||
});
|
||||
}
|
||||
|
||||
const filename = `laporan-hutang-supplier-${formatDate(new Date(), 'YYYY-MM-DD-HHmm')}.xlsx`;
|
||||
|
||||
XLSX.writeFile(workbook, filename);
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
@@ -35,6 +35,8 @@ import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||
import Badge from '@/components/Badge';
|
||||
import { Color } from '@/types/theme';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||
|
||||
const dueStatus: Record<string, Color> = {
|
||||
'Sudah Jatuh Tempo': 'error',
|
||||
@@ -671,7 +673,7 @@ const DebtSupplierTab = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SelectInput
|
||||
<SelectInputCheckbox
|
||||
label='Supplier'
|
||||
placeholder='Pilih Supplier'
|
||||
isMulti
|
||||
@@ -696,7 +698,7 @@ const DebtSupplierTab = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SelectInput
|
||||
<SelectInputRadio
|
||||
label='Filter Berdasarkan'
|
||||
placeholder='Pilih Filter Berdasarkan'
|
||||
options={dataTypeOptions}
|
||||
|
||||
Reference in New Issue
Block a user