mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-22 06:15:47 +00:00
refactor(FE): Add Excel export functionality for daily marketing report
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
'use client';
|
||||
|
||||
import ExcelJS from 'exceljs';
|
||||
import {
|
||||
formatCurrency,
|
||||
formatNumber,
|
||||
formatDate,
|
||||
formatVechicleNumber,
|
||||
} from '@/lib/helper';
|
||||
import { DailyMarketingRow, SalesSummary } from '@/types/api/report/marketing';
|
||||
|
||||
interface DailyMarketingExportExcelParams {
|
||||
data: DailyMarketingRow[];
|
||||
summaryTotal?: SalesSummary;
|
||||
period?: string;
|
||||
}
|
||||
|
||||
export const generateDailyMarketingExcel = async (
|
||||
params: DailyMarketingExportExcelParams
|
||||
): Promise<void> => {
|
||||
if (!params.data || params.data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
|
||||
// ===== DAILY MARKETING WORKSHEET =====
|
||||
const columns = [
|
||||
{ header: 'No', key: 'no', width: 5 },
|
||||
{ header: 'Tanggal Jual', key: 'soDate', width: 15 },
|
||||
{ header: 'Tanggal Realisasi', key: 'realizationDate', width: 18 },
|
||||
{ header: 'Aging', key: 'aging', width: 10 },
|
||||
{ header: 'Gudang', key: 'warehouse', width: 25 },
|
||||
{ header: 'Pelanggan', key: 'customer', width: 25 },
|
||||
{ header: 'No. DO', key: 'doNumber', width: 15 },
|
||||
{ header: 'Sales/Marketing', key: 'sales', width: 20 },
|
||||
{ header: 'No. Polisi', key: 'vehicleNumber', width: 15 },
|
||||
{ header: 'Marketing Type', key: 'marketingType', width: 15 },
|
||||
{ header: 'Produk', key: 'product', width: 20 },
|
||||
{ header: 'Kuantitas', key: 'qty', width: 12 },
|
||||
{ header: 'Bobot Rata-Rata (Kg)', key: 'averageWeight', width: 20 },
|
||||
{ header: 'Bobot Total (Kg)', key: 'totalWeight', width: 18 },
|
||||
{ header: 'Harga Jual (Rp)', key: 'salesPrice', width: 18 },
|
||||
{ header: 'HPP (Rp)', key: 'hppPrice', width: 15 },
|
||||
{ header: 'Total (Rp)', key: 'salesAmount', width: 20 },
|
||||
];
|
||||
|
||||
const worksheet = workbook.addWorksheet('Laporan Marketing Harian');
|
||||
worksheet.columns = columns;
|
||||
|
||||
// Add data rows
|
||||
params.data.forEach((item: DailyMarketingRow, index: number) => {
|
||||
worksheet.addRow({
|
||||
no: index + 1,
|
||||
soDate: formatDate(item.so_date, 'DD-MMM-YYYY'),
|
||||
realizationDate: formatDate(item.realization_date, 'DD-MMM-YYYY'),
|
||||
aging: `${item.aging_days} hari`,
|
||||
warehouse: item.warehouse?.name || '',
|
||||
customer: item.customer?.name || '',
|
||||
doNumber: item.do_number || '',
|
||||
sales: item.sales?.name || '',
|
||||
vehicleNumber: formatVechicleNumber(item.vehicle_number),
|
||||
marketingType: item.marketing_type || '',
|
||||
product: item.product?.name || '',
|
||||
qty: formatNumber(item.qty || 0),
|
||||
averageWeight: formatNumber(item.average_weight_kg || 0),
|
||||
totalWeight: formatNumber(item.total_weight_kg || 0),
|
||||
salesPrice: formatCurrency(item.sales_price_per_kg || 0),
|
||||
hppPrice: formatCurrency(item.hpp_price_per_kg || 0),
|
||||
salesAmount: formatCurrency(item.sales_amount || 0),
|
||||
});
|
||||
});
|
||||
|
||||
// Add TOTAL row if summary data is available
|
||||
if (params.summaryTotal) {
|
||||
worksheet.addRow({
|
||||
no: 'TOTAL',
|
||||
soDate: 'ALL',
|
||||
realizationDate: '-',
|
||||
aging: '-',
|
||||
warehouse: '-',
|
||||
customer: '-',
|
||||
doNumber: '-',
|
||||
sales: '-',
|
||||
vehicleNumber: '-',
|
||||
marketingType: '-',
|
||||
product: '-',
|
||||
qty: formatNumber(params.summaryTotal.total_qty || 0),
|
||||
averageWeight: formatNumber(params.summaryTotal.average_weight_kg || 0),
|
||||
totalWeight: formatNumber(params.summaryTotal.total_weight_kg || 0),
|
||||
salesPrice: formatNumber(params.summaryTotal.average_sales_price || 0),
|
||||
hppPrice: formatCurrency(params.summaryTotal.total_hpp_price_per_kg || 0),
|
||||
salesAmount: formatCurrency(params.summaryTotal.total_sales_amount || 0),
|
||||
});
|
||||
}
|
||||
|
||||
worksheet.columns.forEach((column) => {
|
||||
if (column.width && column.width < 10) {
|
||||
column.width = 10;
|
||||
}
|
||||
});
|
||||
|
||||
const currentDate = new Date().toISOString().split('T')[0];
|
||||
const filename = params.period
|
||||
? `laporan-marketing-harian-${params.period}.xlsx`
|
||||
: `laporan-marketing-harian-${currentDate}.xlsx`;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
formatNumber,
|
||||
formatDate,
|
||||
formatVechicleNumber,
|
||||
formatTitleCase,
|
||||
} from '@/lib/helper';
|
||||
import {
|
||||
DailyMarketingRow,
|
||||
@@ -23,9 +24,8 @@ import {
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import Button from '@/components/Button';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import MenuItem from '@/components/menu/MenuItem';
|
||||
import Menu from '@/components/menu/Menu';
|
||||
import DailyMarketingReportPDF from '@/components/pages/report/marketing/export/DailyMarketingExportPDF';
|
||||
import { generateDailyMarketingExcel } from '@/components/pages/report/marketing/export/DailyMarketingExportXLSX';
|
||||
import { pdf } from '@react-pdf/renderer';
|
||||
import toast from 'react-hot-toast';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -336,31 +336,23 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
const handleExportExcel = useCallback(async () => {
|
||||
setIsExcelExportLoading(true);
|
||||
try {
|
||||
const queryString = new URLSearchParams();
|
||||
const allDataForExport = await dailyMarketingsExport();
|
||||
|
||||
if (searchValue) queryString.set('search', searchValue);
|
||||
if (filterParams.area_id)
|
||||
queryString.set('area_id', filterParams.area_id);
|
||||
if (filterParams.location_id)
|
||||
queryString.set('location_id', filterParams.location_id);
|
||||
if (filterParams.warehouse_id)
|
||||
queryString.set('warehouse_id', filterParams.warehouse_id);
|
||||
if (filterParams.customer_id)
|
||||
queryString.set('customer_id', filterParams.customer_id);
|
||||
if (filterParams.start_date)
|
||||
queryString.set('start_date', filterParams.start_date);
|
||||
if (filterParams.end_date)
|
||||
queryString.set('end_date', filterParams.end_date);
|
||||
if (filterParams.filter_by)
|
||||
queryString.set('filter_by', filterParams.filter_by);
|
||||
if (filterParams.marketing_type)
|
||||
queryString.set('marketing_type', filterParams.marketing_type);
|
||||
if (filterParams.sort_by)
|
||||
queryString.set('sort_by', filterParams.sort_by);
|
||||
if (!allDataForExport || allDataForExport.length === 0) {
|
||||
toast.error('Tidak ada data untuk diekspor.');
|
||||
return;
|
||||
}
|
||||
|
||||
await MarketingReportApi.exportDailyMarketingToExcel(
|
||||
`?${queryString.toString()}`
|
||||
);
|
||||
const period =
|
||||
filterParams.start_date && filterParams.end_date
|
||||
? `${formatDate(filterParams.start_date, 'DD-MMM-YYYY')}_to_${formatDate(filterParams.end_date, 'DD-MMM-YYYY')}`
|
||||
: undefined;
|
||||
|
||||
await generateDailyMarketingExcel({
|
||||
data: allDataForExport,
|
||||
summaryTotal: summaryTotal,
|
||||
period: period,
|
||||
});
|
||||
|
||||
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||
} catch {
|
||||
@@ -368,7 +360,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
} finally {
|
||||
setIsExcelExportLoading(false);
|
||||
}
|
||||
}, [filterParams, searchValue]);
|
||||
}, [filterParams, dailyMarketingsExport, summaryTotal]);
|
||||
|
||||
const handleExportPDF = useCallback(async () => {
|
||||
setIsPdfExportLoading(true);
|
||||
@@ -588,6 +580,11 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
||||
id: 'marketing_type',
|
||||
header: 'Marketing Type',
|
||||
accessorKey: 'marketing_type',
|
||||
cell: (props) => (
|
||||
<span className='text-nowrap'>
|
||||
{formatTitleCase(props.row.original.marketing_type || '-')}
|
||||
</span>
|
||||
),
|
||||
footer: () => <div className='font-semibold text-gray-900'>-</div>,
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user