diff --git a/src/screens/DailySalesReportPage.tsx b/src/screens/DailySalesReportPage.tsx index c6ff3b0..70f10d8 100644 --- a/src/screens/DailySalesReportPage.tsx +++ b/src/screens/DailySalesReportPage.tsx @@ -45,95 +45,143 @@ export function DailySalesReportPage() { fetchSOs(); }, []); - const downloadPDF = () => { + const generatePdfDoc = () => { const doc = new jsPDF({ orientation: 'landscape' }); const pageWidth = doc.internal.pageSize.getWidth(); const centerX = pageWidth / 2; - const rightX = pageWidth - 14; + const rightMargin = pageWidth - 14; - // Headers - doc.setFontSize(16); - doc.setFont('helvetica', 'bold'); - doc.text('Krishna Flour Mills (Bangalore) Pvt. Limited', centerX, 15, { align: 'center' }); + const drawPageHeader = () => { + // Headers + doc.setFontSize(16); + doc.setFont('helvetica', 'bold'); + doc.text('Krishna Flour Mills (Bangalore) Pvt. Limited', centerX, 15, { align: 'center' }); - doc.setFontSize(11); - doc.setFont('helvetica', 'normal'); - doc.text('19, Platform Road, Bengaluru - 560 020', centerX, 22, { align: 'center' }); + doc.setFontSize(11); + doc.setFont('helvetica', 'normal'); + doc.text('19, Platform Road, Bengaluru - 560 020', centerX, 22, { align: 'center' }); - doc.setFontSize(12); - doc.setFont('helvetica', 'bold'); - doc.text('DAILY ORDER REPORT', centerX, 30, { align: 'center' }); + doc.setFontSize(12); + doc.setFont('helvetica', 'bold'); + doc.text('DAILY ORDER REPORT', centerX, 30, { align: 'center' }); + + // Simple underline for DAILY ORDER REPORT + const textWidth = doc.getTextWidth('DAILY ORDER REPORT'); + doc.setLineWidth(0.5); + doc.line(centerX - textWidth / 2, 31, centerX + textWidth / 2, 31); - // Simple underline for DAILY ORDER REPORT - const textWidth = doc.getTextWidth('DAILY ORDER REPORT'); - doc.setLineWidth(0.5); - doc.line(centerX - textWidth / 2, 31, centerX + textWidth / 2, 31); + // Meta Info + doc.setFontSize(10); + doc.text(`Distributor Name : ${distributor}`, 14, 40); + doc.text(`Date : ${date}`, rightMargin, 40, { align: 'right' }); + const selectedSo = soOptions.find(o => o.value === soEmail); + doc.text(`SO Name : ${selectedSo ? selectedSo.label : soEmail}`, rightMargin, 45, { align: 'right' }); + }; - // Meta Info - doc.setFontSize(10); - doc.text(`Distributor Name : ${distributor}`, 14, 40); - doc.text(`Date : ${date}`, rightX, 40, { align: 'right' }); - const selectedSo = soOptions.find(o => o.value === soEmail); - doc.text(`SO Name : ${selectedSo ? selectedSo.label : soEmail}`, rightX, 45, { align: 'right' }); + // Split headers into chunks to avoid jspdf-autotable's horizontal page break bugs + // This allows us to keep the 2-tier header perfectly intact and break at heading boundaries. + const MAX_SKUS_PER_CHUNK = 14; + const headerChunks: any[][] = []; + let currentChunk: any[] = []; + let currentSkuCount = 0; - // Table - autoTable(doc, { - html: '#report-table', - startY: 50, - theme: 'grid', - styles: { fontSize: 8, textColor: [0, 0, 0], lineColor: [0, 0, 0], lineWidth: 0.2 }, - headStyles: { fillColor: [243, 244, 246], fontStyle: 'bold', halign: 'center' }, - columnStyles: { - 0: { cellWidth: 12 } - }, + reportData?.response?.headers?.forEach((h: any) => { + const skus = h.skus || []; + if (currentSkuCount + skus.length > MAX_SKUS_PER_CHUNK && currentSkuCount > 0) { + headerChunks.push(currentChunk); + currentChunk = []; + currentSkuCount = 0; + } + currentChunk.push(h); + currentSkuCount += skus.length; + }); + if (currentChunk.length > 0) { + headerChunks.push(currentChunk); + } + + if (headerChunks.length === 0) { + drawPageHeader(); // Fallback if no data + } + + headerChunks.forEach((chunkHeaders, index) => { + if (index === 0) { + drawPageHeader(); + } else { + doc.addPage(); + drawPageHeader(); + } + + const headRow1: any[] = [ + { content: 'Sl No', rowSpan: 2, styles: { halign: 'center', valign: 'middle' } }, + { content: 'Store', rowSpan: 2, styles: { halign: 'center', valign: 'middle' } } + ]; + const headRow2: any[] = []; + + chunkHeaders.forEach((h: any) => { + headRow1.push({ content: h.br_code, colSpan: h.skus?.length || 1, styles: { halign: 'center' } }); + h.skus?.forEach((sku: string) => { + headRow2.push({ content: sku, styles: { halign: 'center' } }); + }); + }); + + const isLastChunk = index === headerChunks.length - 1; + if (isLastChunk) { + headRow1.push({ content: 'Total', rowSpan: 2, styles: { halign: 'center', valign: 'middle' } }); + } + + const body = reportData?.response?.rows?.map((row: any) => { + const rowData = [row.sl_no, row.store]; + chunkHeaders.flatMap((h: any) => h.skus || []).forEach((sku: string) => { + const val = row.products?.[sku]; + rowData.push(val === '0' || val === 0 ? '' : val || ''); + }); + if (isLastChunk) { + const val = row.total; + rowData.push(val === '0' || val === 0 ? '' : val || ''); + } + return rowData; + }) || []; + + if (reportData?.response?.total_row) { + const totalRowData = [reportData.response.total_row.sl_no, reportData.response.total_row.store]; + chunkHeaders.flatMap((h: any) => h.skus || []).forEach((sku: string) => { + const val = reportData.response.total_row.products?.[sku]; + totalRowData.push(val === '0' || val === 0 ? '' : val || ''); + }); + if (isLastChunk) { + const val = reportData.response.total_row.total; + totalRowData.push(val === '0' || val === 0 ? '' : val || ''); + } + body.push(totalRowData); + } + + autoTable(doc, { + head: [headRow1, headRow2], + body: body, + startY: 50, + theme: 'grid', + horizontalPageBreak: false, + styles: { fontSize: 6, textColor: [0, 0, 0], lineColor: [0, 0, 0], lineWidth: 0.2, cellPadding: 1, minCellWidth: 10 }, + headStyles: { fillColor: [243, 244, 246], fontStyle: 'bold', halign: 'center' }, + didParseCell: (data) => { + if (data.section === 'body' && reportData?.response?.total_row && data.row.index === body.length - 1) { + data.cell.styles.fontStyle = 'bold'; + data.cell.styles.fillColor = [230, 230, 230]; + } + } + }); }); + return doc; + }; + + const downloadPDF = () => { + const doc = generatePdfDoc(); doc.save(`Daily_Sales_Report_${date || 'Draft'}.pdf`); }; const printReport = async () => { - const doc = new jsPDF({ orientation: 'landscape' }); - const pageWidth = doc.internal.pageSize.getWidth(); - const centerX = pageWidth / 2; - const rightX = pageWidth - 14; - - // Headers - doc.setFontSize(16); - doc.setFont('helvetica', 'bold'); - doc.text('Krishna Flour Mills (Bangalore) Pvt. Limited', centerX, 15, { align: 'center' }); - - doc.setFontSize(11); - doc.setFont('helvetica', 'normal'); - doc.text('19, Platform Road, Bengaluru - 560 020', centerX, 22, { align: 'center' }); - - doc.setFontSize(12); - doc.setFont('helvetica', 'bold'); - doc.text('DAILY ORDER REPORT', centerX, 30, { align: 'center' }); - - // Simple underline for DAILY ORDER REPORT - const textWidth = doc.getTextWidth('DAILY ORDER REPORT'); - doc.setLineWidth(0.5); - doc.line(centerX - textWidth / 2, 31, centerX + textWidth / 2, 31); - - // Meta Info - doc.setFontSize(10); - doc.text(`Distributor Name : ${distributor}`, 14, 40); - doc.text(`Date : ${date}`, rightX, 40, { align: 'right' }); - const selectedSo = soOptions.find(o => o.value === soEmail); - doc.text(`SO Name : ${selectedSo ? selectedSo.label : soEmail}`, rightX, 45, { align: 'right' }); - - // Table - autoTable(doc, { - html: '#report-table', - startY: 50, - theme: 'grid', - styles: { fontSize: 8, textColor: [0, 0, 0], lineColor: [0, 0, 0], lineWidth: 0.2 }, - headStyles: { fillColor: [243, 244, 246], fontStyle: 'bold', halign: 'center' }, - columnStyles: { - 0: { cellWidth: 12 } - }, - }); - + const doc = generatePdfDoc(); const blob = doc.output('blob'); // Fallback for PC / unsupported browsers @@ -275,10 +323,10 @@ export function DailySalesReportPage() {