277 lines
11 KiB
TypeScript
277 lines
11 KiB
TypeScript
import { useState, type FormEvent } from 'react';
|
|
import { Card, Input, Select } from '../components/reusable';
|
|
import { Button } from '../components/buttons/Button';
|
|
import {
|
|
SALES_REPORT_ROUTES,
|
|
ROUTE_WISE_DISTRIBUTORS,
|
|
SALES_REPORT_SO_NAMES
|
|
} from '../api/config';
|
|
import { Download, Printer } from 'lucide-react';
|
|
import jsPDF from 'jspdf';
|
|
import autoTable from 'jspdf-autotable';
|
|
|
|
export function DailySalesReportPage() {
|
|
const [date, setDate] = useState('');
|
|
const [route, setRoute] = useState('');
|
|
const [distributor, setDistributor] = useState('');
|
|
const [soName, setSoName] = useState('');
|
|
|
|
const [busy, setBusy] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [reportData, setReportData] = useState<any>(null);
|
|
|
|
const downloadPDF = () => {
|
|
const doc = new jsPDF();
|
|
|
|
// Headers
|
|
doc.setFontSize(16);
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('Krishna Flour Mills (Bangalore) Pvt. Limited', 105, 15, { align: 'center' });
|
|
|
|
doc.setFontSize(11);
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.text('19, Platform Road, Bengaluru - 560 020', 105, 22, { align: 'center' });
|
|
|
|
doc.setFontSize(12);
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('DAILY ORDER REPORT', 105, 30, { align: 'center' });
|
|
|
|
// Simple underline for DAILY ORDER REPORT
|
|
const textWidth = doc.getTextWidth('DAILY ORDER REPORT');
|
|
doc.setLineWidth(0.5);
|
|
doc.line(105 - textWidth / 2, 31, 105 + textWidth / 2, 31);
|
|
|
|
// Meta Info
|
|
doc.setFontSize(10);
|
|
doc.text(`Distributor Name : ${distributor}`, 14, 40);
|
|
doc.text(`Date : ${date}`, 196, 40, { align: 'right' });
|
|
doc.text(`SO Name : ${soName}`, 196, 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' },
|
|
});
|
|
|
|
doc.save(`Daily_Sales_Report_${date || 'Draft'}.pdf`);
|
|
};
|
|
|
|
const printReport = async () => {
|
|
const doc = new jsPDF();
|
|
|
|
// Headers
|
|
doc.setFontSize(16);
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('Krishna Flour Mills (Bangalore) Pvt. Limited', 105, 15, { align: 'center' });
|
|
|
|
doc.setFontSize(11);
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.text('19, Platform Road, Bengaluru - 560 020', 105, 22, { align: 'center' });
|
|
|
|
doc.setFontSize(12);
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('DAILY ORDER REPORT', 105, 30, { align: 'center' });
|
|
|
|
// Simple underline for DAILY ORDER REPORT
|
|
const textWidth = doc.getTextWidth('DAILY ORDER REPORT');
|
|
doc.setLineWidth(0.5);
|
|
doc.line(105 - textWidth / 2, 31, 105 + textWidth / 2, 31);
|
|
|
|
// Meta Info
|
|
doc.setFontSize(10);
|
|
doc.text(`Distributor Name : ${distributor}`, 14, 40);
|
|
doc.text(`Date : ${date}`, 196, 40, { align: 'right' });
|
|
doc.text(`SO Name : ${soName}`, 196, 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' },
|
|
});
|
|
|
|
const blob = doc.output('blob');
|
|
|
|
// Fallback for PC / unsupported browsers
|
|
const blobUrl = URL.createObjectURL(blob);
|
|
const iframe = document.createElement('iframe');
|
|
iframe.style.display = 'none';
|
|
iframe.src = blobUrl;
|
|
document.body.appendChild(iframe);
|
|
|
|
iframe.onload = () => {
|
|
setTimeout(() => {
|
|
iframe.contentWindow?.focus();
|
|
iframe.contentWindow?.print();
|
|
}, 500);
|
|
};
|
|
};
|
|
|
|
async function submit(e: FormEvent) {
|
|
e.preventDefault();
|
|
setBusy(true);
|
|
setError(null);
|
|
setReportData(null);
|
|
try {
|
|
const res = await fetch('https://sandbox.getzino.in/api/papi2/daily-sales-report', {
|
|
method: 'POST',
|
|
headers: {
|
|
'TemplateID': '157',
|
|
'Accept': 'application/json, text/plain, */*',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
date,
|
|
route,
|
|
distributor,
|
|
so_name: soName,
|
|
})
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`Error: ${res.status} ${res.statusText}`);
|
|
}
|
|
|
|
const data = await res.json();
|
|
setReportData(data);
|
|
} catch (err) {
|
|
setError((err as Error).message ?? 'Failed to fetch report');
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-5 p-6 pb-20">
|
|
<h1 className="m-0 text-xl font-extrabold text-strong tracking-[-0.01em] print:hidden">Daily Sales Report</h1>
|
|
|
|
<Card className="print:hidden">
|
|
<form onSubmit={submit} className="flex flex-col gap-4">
|
|
<Input
|
|
label="Date"
|
|
type="date"
|
|
value={date}
|
|
onChange={(e) => setDate(e.target.value)}
|
|
/>
|
|
<Select
|
|
label="Route"
|
|
value={route}
|
|
onChange={(e) => {
|
|
setRoute(e.target.value);
|
|
setDistributor('');
|
|
}}
|
|
options={[{ value: '', label: 'Select Route' }, ...SALES_REPORT_ROUTES.map(r => ({ value: r, label: r }))]}
|
|
/>
|
|
<Select
|
|
label="Distributor"
|
|
value={distributor}
|
|
onChange={(e) => setDistributor(e.target.value)}
|
|
options={[{ value: '', label: 'Select Distributor' }, ...(route ? (ROUTE_WISE_DISTRIBUTORS[route] || []) : []).map(d => ({ value: d, label: d }))]}
|
|
/>
|
|
<Select
|
|
label="SO Name"
|
|
value={soName}
|
|
onChange={(e) => setSoName(e.target.value)}
|
|
options={[{ value: '', label: 'Select SO Name' }, ...SALES_REPORT_SO_NAMES.map(s => ({ value: s, label: s }))]}
|
|
/>
|
|
|
|
{error && <div className="text-xs text-ruby-600 font-medium">{error}</div>}
|
|
|
|
<Button type="submit" full disabled={busy}>
|
|
{busy ? 'Fetching...' : 'Get Report'}
|
|
</Button>
|
|
</form>
|
|
</Card>
|
|
|
|
{reportData?.response && (
|
|
<Card className="overflow-x-auto p-0 pb-2 border-0 shadow-none print:m-0 print:p-0">
|
|
<div className="p-4 pb-2 print:hidden">
|
|
<h2 className="text-lg font-bold text-strong m-0">Report Details</h2>
|
|
</div>
|
|
<div className="w-full overflow-x-auto p-4 pt-2">
|
|
<div className="min-w-max">
|
|
<div className="flex flex-col items-center text-center gap-1">
|
|
<h1 className="m-0 text-lg font-bold text-strong">Krishna Flour Mills (Bangalore) Pvt. Limited</h1>
|
|
<h2 className="m-0 text-sm font-normal text-strong">19, Platform Road, Bengaluru - 560 020</h2>
|
|
<h3 className="m-0 text-base font-bold text-strong underline mt-1 mb-4">DAILY ORDER REPORT</h3>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-start text-sm font-bold text-strong mb-2">
|
|
<div>Distributor Name : {distributor}</div>
|
|
<div className="text-right flex flex-col gap-1.5">
|
|
<div>Date : {date}</div>
|
|
<div>SO Name : {soName}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<table id="report-table" className="w-full text-sm text-left border border-black border-collapse mt-2">
|
|
<thead>
|
|
<tr className="border-b border-black bg-black/5">
|
|
<th className="py-2 px-3 font-bold text-black align-bottom border-r border-black" rowSpan={2}>Sl No</th>
|
|
<th className="py-2 px-3 font-bold text-black align-bottom" rowSpan={2}>Store</th>
|
|
{reportData.response.headers?.map((h: any) => (
|
|
<th key={h.br_code} colSpan={h.skus?.length || 1} className="py-1 px-3 font-bold text-black text-center border-l border-black">
|
|
{h.br_code}
|
|
</th>
|
|
))}
|
|
<th className="py-2 px-3 font-bold text-black align-bottom border-l border-black" rowSpan={2}>Total</th>
|
|
</tr>
|
|
<tr className="border-b border-black bg-black/5">
|
|
{reportData.response.headers?.flatMap((h: any) => h.skus || []).map((sku: string, idx: number) => (
|
|
<th key={`${sku}-${idx}`} className="py-1 px-3 font-bold text-black text-center border-l border-black whitespace-nowrap">
|
|
{sku}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{reportData.response.rows?.map((row: any) => (
|
|
<tr key={row.sl_no} className="border-b border-black hover:bg-black/5 transition-colors">
|
|
<td className="py-2 px-3 text-strong border-r border-black">{row.sl_no}</td>
|
|
<td className="py-2 px-3 text-strong whitespace-nowrap">{row.store}</td>
|
|
{reportData.response.headers?.flatMap((h: any) => h.skus || []).map((sku: string, idx: number) => (
|
|
<td key={`${sku}-${idx}`} className="py-2 px-3 text-strong text-center border-l border-black">
|
|
{row.products?.[sku] ?? 0}
|
|
</td>
|
|
))}
|
|
<td className="py-2 px-3 text-strong font-bold text-center border-l border-black bg-black/5">{row.total ?? 0}</td>
|
|
</tr>
|
|
))}
|
|
{reportData.response.total_row && (
|
|
<tr className="border-b border-black bg-black/10 font-bold">
|
|
<td className="py-2 px-3 text-strong border-r border-black">{reportData.response.total_row.sl_no}</td>
|
|
<td className="py-2 px-3 text-strong whitespace-nowrap text-right">{reportData.response.total_row.store}</td>
|
|
{reportData.response.headers?.flatMap((h: any) => h.skus || []).map((sku: string, idx: number) => (
|
|
<td key={`total-${sku}-${idx}`} className="py-2 px-3 text-strong text-center border-l border-black">
|
|
{reportData.response.total_row.products?.[sku] ?? 0}
|
|
</td>
|
|
))}
|
|
<td className="py-2 px-3 text-strong text-center border-l border-black">{reportData.response.total_row.total ?? 0}</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
{(!reportData.response.rows || reportData.response.rows.length === 0) && (
|
|
<div className="py-8 text-center text-faint">No records found.</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="p-4 pt-4 pb-6 flex gap-4 print:hidden">
|
|
<Button type="button" onClick={downloadPDF} variant="secondary" iconLeft={<Download size={18} />} full>
|
|
Download PDF
|
|
</Button>
|
|
<Button type="button" onClick={printReport} iconLeft={<Printer size={18} />} full>
|
|
Print PDF
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|