mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +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,
|
formatNumber,
|
||||||
formatDate,
|
formatDate,
|
||||||
formatVechicleNumber,
|
formatVechicleNumber,
|
||||||
|
formatTitleCase,
|
||||||
} from '@/lib/helper';
|
} from '@/lib/helper';
|
||||||
import {
|
import {
|
||||||
DailyMarketingRow,
|
DailyMarketingRow,
|
||||||
@@ -23,9 +24,8 @@ import {
|
|||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Dropdown from '@/components/Dropdown';
|
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 DailyMarketingReportPDF from '@/components/pages/report/marketing/export/DailyMarketingExportPDF';
|
||||||
|
import { generateDailyMarketingExcel } from '@/components/pages/report/marketing/export/DailyMarketingExportXLSX';
|
||||||
import { pdf } from '@react-pdf/renderer';
|
import { pdf } from '@react-pdf/renderer';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -336,31 +336,23 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
const handleExportExcel = useCallback(async () => {
|
const handleExportExcel = useCallback(async () => {
|
||||||
setIsExcelExportLoading(true);
|
setIsExcelExportLoading(true);
|
||||||
try {
|
try {
|
||||||
const queryString = new URLSearchParams();
|
const allDataForExport = await dailyMarketingsExport();
|
||||||
|
|
||||||
if (searchValue) queryString.set('search', searchValue);
|
if (!allDataForExport || allDataForExport.length === 0) {
|
||||||
if (filterParams.area_id)
|
toast.error('Tidak ada data untuk diekspor.');
|
||||||
queryString.set('area_id', filterParams.area_id);
|
return;
|
||||||
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);
|
|
||||||
|
|
||||||
await MarketingReportApi.exportDailyMarketingToExcel(
|
const period =
|
||||||
`?${queryString.toString()}`
|
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.');
|
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -368,7 +360,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsExcelExportLoading(false);
|
setIsExcelExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [filterParams, searchValue]);
|
}, [filterParams, dailyMarketingsExport, summaryTotal]);
|
||||||
|
|
||||||
const handleExportPDF = useCallback(async () => {
|
const handleExportPDF = useCallback(async () => {
|
||||||
setIsPdfExportLoading(true);
|
setIsPdfExportLoading(true);
|
||||||
@@ -588,6 +580,11 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
id: 'marketing_type',
|
id: 'marketing_type',
|
||||||
header: 'Marketing Type',
|
header: 'Marketing Type',
|
||||||
accessorKey: '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>,
|
footer: () => <div className='font-semibold text-gray-900'>-</div>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user