reports page added

This commit is contained in:
suryacp23 2026-07-17 13:13:40 +05:30
parent 9be169396e
commit 3861605df1
5 changed files with 268 additions and 6 deletions

View File

@ -8,6 +8,7 @@ import { StoresPage } from './screens/StoresPage'
import { DailyLogsPage } from './screens/DailyLogsPage' import { DailyLogsPage } from './screens/DailyLogsPage'
import { AllDailyLogsPage } from './screens/AllDailyLogsPage' import { AllDailyLogsPage } from './screens/AllDailyLogsPage'
import { AnalyticsPage } from './screens/AnalyticsPage' import { AnalyticsPage } from './screens/AnalyticsPage'
import { DailySalesReportPage } from './screens/DailySalesReportPage'
function App() { function App() {
return ( return (
<AuthProvider> <AuthProvider>
@ -33,6 +34,7 @@ function App() {
<Route path="/analytics" element={<AnalyticsPage />} /> <Route path="/analytics" element={<AnalyticsPage />} />
<Route path="/sales-report" element={<DailySalesReportPage />} />
</Route> </Route>
<Route path="*" element={<Navigate to="/daily" replace />} /> <Route path="*" element={<Navigate to="/daily" replace />} />
</Routes> </Routes>

View File

@ -175,3 +175,31 @@ export const WORKFLOWS = {
store: STORE, store: STORE,
dailyReports: DAILY_REPORTS, dailyReports: DAILY_REPORTS,
} as const; } as const;
// --- Daily Sales Report Constants ---
export const SALES_REPORT_ROUTES = [
'Krishna',
'East-1',
'East-2',
'East-3',
'East-4',
'West-1',
'West-2',
'West-3',
'West-4'
];
export const SALES_REPORT_DISTRIBUTORS = [
'Banashree Multi Millet Flour',
'Shiva nandi Enterprises',
'BPK & Co',
'Gaviranga',
'Bhagwan Enterprises',
'Shreshtaa Trading Co',
'Rajarajeshwari Enterprises',
'Byraveshwara Trading Co'
];
export const SALES_REPORT_SO_NAMES = [
'Surya C'
];

View File

@ -27,7 +27,7 @@ export function ConsoleLayout() {
return ( return (
<div className="h-[100dvh] overflow-hidden bg-app flex flex-col"> <div className="h-[100dvh] overflow-hidden bg-app flex flex-col">
<header className="sticky top-0 z-20 shrink-0 flex items-center justify-between gap-3 px-4 h-14 pt-[env(safe-area-inset-top)] top-navbar"> <header className="print:hidden sticky top-0 z-20 shrink-0 flex items-center justify-between gap-3 px-4 h-14 pt-[env(safe-area-inset-top)] top-navbar">
<div className="flex items-center gap-3 flex-1"> <div className="flex items-center gap-3 flex-1">
<button <button
type="button" type="button"
@ -51,10 +51,10 @@ export function ConsoleLayout() {
</main> </main>
<nav <nav
className="fixed bottom-0 inset-x-0 z-20 h-16 pb-[env(safe-area-inset-bottom)] bg-card border-t border-border-subtle grid shadow-[0_-2px_16px_rgba(11,27,59,0.06)]" className="print:hidden fixed bottom-0 inset-x-0 z-20 h-16 pb-[env(safe-area-inset-bottom)] bg-card border-t border-border-subtle grid shadow-[0_-2px_16px_rgba(11,27,59,0.06)]"
style={{ gridTemplateColumns: `repeat(${SCREENS.filter(t => t.key !== 'analytics' && t.key !== 'all-daily').length}, minmax(0, 1fr))` }} style={{ gridTemplateColumns: `repeat(${SCREENS.filter(t => t.key !== 'analytics' && t.key !== 'all-daily' && t.key !== 'sales-report').length}, minmax(0, 1fr))` }}
> >
{SCREENS.filter(t => t.key !== 'analytics' && t.key !== 'all-daily').map((t) => { {SCREENS.filter(t => t.key !== 'analytics' && t.key !== 'all-daily' && t.key !== 'sales-report').map((t) => {
const Icon = t.icon; const Icon = t.icon;
return ( return (
<NavLink <NavLink

View File

@ -0,0 +1,230 @@
import { useState, type FormEvent } from 'react';
import { Card, Input, Select } from '../components/reusable';
import { Button } from '../components/buttons';
import {
SALES_REPORT_ROUTES,
SALES_REPORT_DISTRIBUTORS,
SALES_REPORT_SO_NAMES
} from '../api/config';
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 printTable = () => {
const tableHtml = document.getElementById('report-table')?.outerHTML;
if (!tableHtml) return;
const printWindow = window.open('', '', 'height=600,width=800');
if (!printWindow) return;
printWindow.document.write(`
<html>
<head>
<title>Daily Sales Report</title>
<style>
body { font-family: sans-serif; padding: 20px; margin: 0; }
.header-block { text-align: center; margin-bottom: 20px; }
.header-block h1 { margin: 0; font-size: 18px; font-weight: bold; }
.header-block h2 { margin: 4px 0 12px; font-size: 14px; font-weight: normal; }
.header-block h3 { margin: 0 0 20px; font-size: 16px; font-weight: bold; text-decoration: underline; }
.meta-info { display: flex; justify-content: space-between; font-size: 14px; margin-bottom: 16px; font-weight: bold; }
.meta-right { text-align: right; line-height: 1.5; }
table { width: 100%; border-collapse: collapse; font-size: 12px; border: 1px solid black; }
th, td { border: 1px solid black; padding: 6px; text-align: left; }
th { font-weight: bold; background-color: #f3f4f6; text-align: center; }
.text-center { text-align: center; }
.align-bottom { vertical-align: bottom; }
</style>
</head>
<body>
<div class="header-block">
<h1>Krishna Flour Mills (Bangalore) Pvt. Limited</h1>
<h2>19, Platform Road, Bengaluru - 560 020</h2>
<h3>DAILY ORDER REPORT</h3>
</div>
<div class="meta-info">
<div>Distributor Name : ${distributor}</div>
<div class="meta-right">
<div>Date : ${date}</div>
<div>SO Name : ${soName}</div>
</div>
</div>
${tableHtml}
</body>
</html>
`);
printWindow.document.close();
printWindow.focus();
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 250);
};
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',
'sec-ch-ua-platform': '"Android"',
},
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">
<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)}
required
/>
<Select
label="Route"
value={route}
onChange={(e) => setRoute(e.target.value)}
options={[{ value: '', label: 'Select Route' }, ...SALES_REPORT_ROUTES]}
required
/>
<Select
label="Distributor"
value={distributor}
onChange={(e) => setDistributor(e.target.value)}
options={[{ value: '', label: 'Select Distributor' }, ...SALES_REPORT_DISTRIBUTORS]}
required
/>
<Select
label="SO Name"
value={soName}
onChange={(e) => setSoName(e.target.value)}
options={[{ value: '', label: 'Select SO Name' }, ...SALES_REPORT_SO_NAMES]}
required
/>
{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-2 flex justify-center print:hidden">
<Button type="button" onClick={printTable} full>Print / Save PDF</Button>
</div>
</Card>
)}
</div>
);
}

View File

@ -1,4 +1,4 @@
import { Store, Phone, ShoppingCart, ClipboardList, Home, type LucideIcon } from 'lucide-react'; import { Store, Phone, ShoppingCart, ClipboardList, Home, FileText, type LucideIcon } from 'lucide-react';
import { import {
OrdersView, OrdersView,
CallsView, CallsView,
@ -15,9 +15,10 @@ import {
} from '../components/dv'; } from '../components/dv';
import { AnalyticsPage } from './AnalyticsPage'; import { AnalyticsPage } from './AnalyticsPage';
import { DailySalesReportPage } from './DailySalesReportPage';
import { BarChart2 } from 'lucide-react'; import { BarChart2 } from 'lucide-react';
export type ScreenKey = 'orders' | 'calls' | 'stores' | 'daily' | 'analytics' | 'all-daily'; export type ScreenKey = 'orders' | 'calls' | 'stores' | 'daily' | 'analytics' | 'all-daily' | 'sales-report';
export interface ScreenDef { export interface ScreenDef {
key: ScreenKey; key: ScreenKey;
@ -31,6 +32,7 @@ export interface ScreenDef {
export const SCREENS: ScreenDef[] = [ export const SCREENS: ScreenDef[] = [
{ key: 'analytics', label: 'Analytics', icon: BarChart2, View: AnalyticsPage as any }, { key: 'analytics', label: 'Analytics', icon: BarChart2, View: AnalyticsPage as any },
{ key: 'sales-report', label: 'Sales Report', icon: FileText, View: DailySalesReportPage as any },
{ key: 'daily', label: 'Home', icon: Home, View: DailyLogsView, Detail: DailyLogDetail, noun: 'Daily Log' }, { key: 'daily', label: 'Home', icon: Home, View: DailyLogsView, Detail: DailyLogDetail, noun: 'Daily Log' },
{ key: 'orders', label: 'Orders', icon: ShoppingCart, View: OrdersView, Detail: OrderDetail, noun: 'Order' }, { key: 'orders', label: 'Orders', icon: ShoppingCart, View: OrdersView, Detail: OrderDetail, noun: 'Order' },
{ key: 'calls', label: 'Calls', icon: Phone, View: CallsView, Detail: CallDetail, noun: 'Call' }, { key: 'calls', label: 'Calls', icon: Phone, View: CallsView, Detail: CallDetail, noun: 'Call' },