Removed PositEasy Referenecs

This commit is contained in:
Sidharth Prabhu
2026-04-17 12:09:49 +05:30
parent 90926256ca
commit e4bc89a6ca
13 changed files with 766 additions and 6 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
<title>RIT Canteen Dashboard</title>
</head>
<body>
<div id="root"></div>

View File

@@ -12,6 +12,8 @@
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.38.0",
"jspdf": "^4.2.1",
"jspdf-autotable": "^5.0.7",
"lucide-react": "^1.7.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
@@ -244,6 +246,15 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -1350,6 +1361,19 @@
"undici-types": "~7.16.0"
}
},
"node_modules/@types/pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
"license": "MIT"
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
@@ -1370,6 +1394,13 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
@@ -1804,6 +1835,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.15",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.15.tgz",
@@ -1893,6 +1934,26 @@
],
"license": "CC-BY-4.0"
},
"node_modules/canvg": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
"license": "MIT",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1966,6 +2027,18 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/core-js": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz",
"integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1981,6 +2054,16 @@
"node": ">= 8"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -2160,6 +2243,16 @@
"node": ">=8"
}
},
"node_modules/dompurify": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz",
"integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optional": true,
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.331",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
@@ -2425,6 +2518,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-png": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
"license": "MIT",
"dependencies": {
"@types/pako": "^2.0.3",
"iobuffer": "^5.3.2",
"pako": "^2.1.0"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -2443,6 +2547,12 @@
}
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -2620,6 +2730,20 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
"optional": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2676,6 +2800,12 @@
"node": ">=12"
}
},
"node_modules/iobuffer": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
"license": "MIT"
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2783,6 +2913,32 @@
"node": ">=6"
}
},
"node_modules/jspdf": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz",
"integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
"fast-png": "^6.2.0",
"fflate": "^0.8.1"
},
"optionalDependencies": {
"canvg": "^3.0.11",
"core-js": "^3.6.0",
"dompurify": "^3.3.1",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/jspdf-autotable": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.7.tgz",
"integrity": "sha512-2wr7H6liNDBYNwt25hMQwXkEWFOEopgKIvR1Eukuw6Zmprm/ZcnmLTQEjW7Xx3FCbD3v7pflLcnMAv/h1jFDQw==",
"license": "MIT",
"peerDependencies": {
"jspdf": "^2 || ^3 || ^4"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -3238,6 +3394,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3271,6 +3433,13 @@
"node": ">=8"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT",
"optional": true
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -3347,6 +3516,16 @@
"node": ">=6"
}
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"license": "MIT",
"optional": true,
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/react": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
@@ -3481,6 +3660,13 @@
"redux": "^5.0.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT",
"optional": true
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
@@ -3497,6 +3683,16 @@
"node": ">=4"
}
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
"optional": true,
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/rolldown": {
"version": "1.0.0-rc.13",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz",
@@ -3593,6 +3789,16 @@
"node": ">=0.10.0"
}
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -3619,6 +3825,16 @@
"node": ">=8"
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/tailwind-merge": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
@@ -3650,6 +3866,16 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@@ -3800,6 +4026,16 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
"optional": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/victory-vendor": {
"version": "37.3.6",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",

View File

@@ -14,6 +14,8 @@
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.38.0",
"jspdf": "^4.2.1",
"jspdf-autotable": "^5.0.7",
"lucide-react": "^1.7.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",

View File

@@ -20,6 +20,7 @@ import Bills from './pages/Bills.tsx';
import PurchaseSummary from './pages/PurchaseSummary.tsx';
import IntentDashboard from './pages/IntentDashboard.tsx';
import IntentList from './pages/IntentList.tsx';
import Reports from './pages/Reports.tsx';
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
@@ -84,7 +85,7 @@ function App() {
<Route path="expense/category" element={<PlaceholderPage title="Expense Category" />} />
{/* Others */}
<Route path="reports" element={<PlaceholderPage title="Reports" />} />
<Route path="reports" element={<Reports />} />
<Route path="table" element={<PlaceholderPage title="Table Management" />} />
<Route path="wallet" element={<PlaceholderPage title="Wallet" />} />
<Route path="feedback" element={<PlaceholderPage title="Feedback" />} />

View File

@@ -1,10 +1,12 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Target,
ChevronRight,
ShoppingBag,
ArrowRight,
Wallet
Wallet,
BarChart3
} from 'lucide-react';
import {
AreaChart,
@@ -21,6 +23,7 @@ import {
import { motion } from 'framer-motion';
const Dashboard = () => {
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState('Sales');
const [timeRange, setTimeRange] = useState('Today');
const [customDates, setCustomDates] = useState({ from: '', to: '' });
@@ -136,6 +139,14 @@ const Dashboard = () => {
</div>
<div className="flex items-center gap-3">
<button
onClick={() => navigate('/reports')}
className="flex items-center gap-2 bg-indigo-50 text-indigo-600 px-4 py-2 rounded-xl text-xs font-black uppercase tracking-widest hover:bg-indigo-600 hover:text-white transition-all shadow-sm active:scale-95"
>
<BarChart3 size={16} />
View Full Report
</button>
<div className="relative group">
<select title="Filter by store type or category" className="appearance-none bg-white border border-slate-200 rounded-xl px-4 py-2 pr-10 text-xs font-bold text-slate-600 outline-none focus:border-[#0f4475] transition-all cursor-pointer shadow-sm">
<option>Store Type: All</option>

View File

@@ -0,0 +1,384 @@
import React, { useState, useEffect } from 'react';
import {
Download,
Calendar,
TrendingUp,
ShoppingBag,
Users,
ArrowUpRight,
ArrowDownRight,
FileText,
Filter,
RefreshCw,
Package,
PieChart as PieChartIcon,
IndianRupee,
BarChart3
} from 'lucide-react';
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
Cell,
PieChart,
Pie
} from 'recharts';
import { format, startOfMonth, endOfMonth, startOfToday, endOfToday, subMonths } from 'date-fns';
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
interface ReportData {
totalSales: number;
totalOrders: number;
totalPurchases: number;
topVendor: string;
topSellingItems: Array<{
name: string;
quantity: number;
revenue: number;
}>;
vendorSummary: Array<{
name: string;
amount: number;
orderCount: number;
}>;
storeOverview: Array<{
name: string;
sale: number;
orders: number;
}>;
}
const Reports: React.FC = () => {
const [dateRange, setDateRange] = useState({
from: startOfMonth(new Date()),
to: endOfMonth(new Date())
});
const [loading, setLoading] = useState(false);
const [data, setData] = useState<ReportData | null>(null);
const fetchData = async () => {
setLoading(true);
try {
const fromStr = dateRange.from.toISOString();
const toStr = dateRange.to.toISOString();
const response = await fetch(`http://${window.location.hostname}:8080/api/reports/monthly?from=${fromStr}&to=${toStr}`);
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching report data:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [dateRange]);
const downloadPDF = () => {
if (!data) return;
const doc = new jsPDF();
const title = `Sales Report (${format(dateRange.from, 'dd MMM')} - ${format(dateRange.to, 'dd MMM yyyy')})`;
// Header
doc.setFontSize(20);
doc.setTextColor(30, 41, 59);
doc.text('RIT CANTEEN BUSINESS REPORT', 105, 15, { align: 'center' });
doc.setFontSize(12);
doc.setTextColor(100, 116, 139);
doc.text(title, 105, 25, { align: 'center' });
// Summary Section
doc.setDrawColor(226, 232, 240);
doc.line(20, 30, 190, 30);
doc.setFontSize(14);
doc.setTextColor(30, 41, 59);
doc.text('Financial Summary', 20, 40);
const summaryData = [
['Metric', 'Value'],
['Total Sales Revenue', `INR ${data.totalSales.toLocaleString()}`],
['Total Orders Fulfilled', data.totalOrders.toString()],
['Total Inventory Purchases', `INR ${data.totalPurchases.toLocaleString()}`],
['Net Performance', `INR ${(data.totalSales - data.totalPurchases).toLocaleString()}`],
['Top Performing Vendor', data.topVendor]
];
autoTable(doc, {
startY: 45,
head: [summaryData[0]],
body: summaryData.slice(1),
theme: 'striped',
headStyles: { fillColor: [79, 70, 229] }
});
// Top Selling Items
const finalY1 = (doc as any).lastAutoTable.finalY;
doc.text('Top Selling Items', 20, finalY1 + 15);
autoTable(doc, {
startY: finalY1 + 20,
head: [['Product Name', 'Quantity Sold', 'Revenue']],
body: data.topSellingItems.map(item => [item.name, item.quantity, `INR ${item.revenue.toLocaleString()}`]),
theme: 'grid'
});
// Vendor Summary
const finalY2 = (doc as any).lastAutoTable.finalY;
doc.text('Vendor Performance', 20, finalY2 + 15);
autoTable(doc, {
startY: finalY2 + 20,
head: [['Vendor Name', 'Total Orders', 'Spend Amount']],
body: data.vendorSummary.map(v => [v.name, v.orderCount, `INR ${v.amount.toLocaleString()}`]),
theme: 'striped'
});
doc.save(`Report_${format(dateRange.from, 'yyyyMMdd')}_${format(dateRange.to, 'yyyyMMdd')}.pdf`);
};
const COLORS_CHART = ['#4f46e5', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4'];
return (
<div className="p-8 bg-[#f8fafc] min-h-screen">
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold text-slate-900 tracking-tight">Business Intelligence</h1>
<p className="text-slate-500 mt-1">Robust insights and performance analytics</p>
</div>
<div className="flex gap-4">
<div className="flex bg-white rounded-xl shadow-sm border border-slate-200 p-1">
<button
onClick={() => setDateRange({ from: startOfMonth(new Date()), to: endOfMonth(new Date()) })}
className={`px-4 py-2 text-sm font-medium rounded-lg transition-all ${
format(dateRange.from, 'MM-yyyy') === format(new Date(), 'MM-yyyy')
? 'bg-indigo-50 text-indigo-600' : 'text-slate-600 hover:bg-slate-50'
}`}
>
This Month
</button>
<button
onClick={() => setDateRange({ from: startOfMonth(subMonths(new Date(), 1)), to: endOfMonth(subMonths(new Date(), 1)) })}
className={`px-4 py-2 text-sm font-medium rounded-lg transition-all ${
format(dateRange.from, 'MM-yyyy') === format(subMonths(new Date(), 1), 'MM-yyyy')
? 'bg-indigo-50 text-indigo-600' : 'text-slate-600 hover:bg-slate-50'
}`}
>
Last Month
</button>
</div>
<button
onClick={downloadPDF}
className="flex items-center gap-2 bg-indigo-600 text-white px-5 py-2.5 rounded-xl font-semibold hover:bg-indigo-700 transition-all shadow-md shadow-indigo-200 active:scale-95"
>
<Download size={18} />
Export PDF
</button>
</div>
</div>
{!data ? (
<div className="flex flex-col items-center justify-center py-32 bg-white rounded-3xl border border-slate-100 shadow-sm">
<RefreshCw size={48} className="text-indigo-200 animate-spin mb-4" />
<p className="text-slate-400 font-medium">Aggregating business intelligence...</p>
</div>
) : (
<div className="space-y-8">
{/* Key Metrics Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm hover:shadow-md transition-shadow group">
<div className="flex justify-between items-start mb-4">
<div className="p-3 bg-indigo-50 text-indigo-600 rounded-2xl group-hover:bg-indigo-600 group-hover:text-white transition-all">
<IndianRupee size={24} />
</div>
<span className="flex items-center gap-1 text-emerald-500 bg-emerald-50 px-2 py-1 rounded-lg text-xs font-bold">
<ArrowUpRight size={14} /> 12.5%
</span>
</div>
<h3 className="text-slate-500 text-sm font-bold uppercase tracking-wider">Total Sales</h3>
<p className="text-3xl font-black text-slate-900 mt-1">{data.totalSales.toLocaleString()}</p>
<p className="text-xs text-slate-400 mt-2 font-medium">For the selected period</p>
</div>
<div className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm hover:shadow-md transition-shadow group">
<div className="flex justify-between items-start mb-4">
<div className="p-3 bg-emerald-50 text-emerald-600 rounded-2xl group-hover:bg-emerald-600 group-hover:text-white transition-all">
<ShoppingBag size={24} />
</div>
<span className="flex items-center gap-1 text-emerald-500 bg-emerald-50 px-2 py-1 rounded-lg text-xs font-bold">
<ArrowUpRight size={14} /> 8.2%
</span>
</div>
<h3 className="text-slate-500 text-sm font-bold uppercase tracking-wider">Total Orders</h3>
<p className="text-3xl font-black text-slate-900 mt-1">{data.totalOrders}</p>
<p className="text-xs text-slate-400 mt-2 font-medium">{Math.round(data.totalOrders/30)} orders avg/day</p>
</div>
<div className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm hover:shadow-md transition-shadow group">
<div className="flex justify-between items-start mb-4">
<div className="p-3 bg-amber-50 text-amber-600 rounded-2xl group-hover:bg-amber-600 group-hover:text-white transition-all">
<Package size={24} />
</div>
<span className="flex items-center gap-1 text-rose-500 bg-rose-50 px-2 py-1 rounded-lg text-xs font-bold">
<ArrowDownRight size={14} /> 2.1%
</span>
</div>
<h3 className="text-slate-500 text-sm font-bold uppercase tracking-wider">Spendings</h3>
<p className="text-3xl font-black text-slate-900 mt-1">{data.totalPurchases.toLocaleString()}</p>
<p className="text-xs text-slate-400 mt-2 font-medium">Inventory & Vendor bills</p>
</div>
<div className="bg-white p-6 rounded-3xl border border-slate-100 shadow-sm hover:shadow-md transition-shadow group">
<div className="flex justify-between items-start mb-4">
<div className="p-3 bg-purple-50 text-purple-600 rounded-2xl group-hover:bg-purple-600 group-hover:text-white transition-all">
<Users size={24} />
</div>
</div>
<h3 className="text-slate-500 text-sm font-bold uppercase tracking-wider">Top Vendor</h3>
<p className="text-xl font-black text-slate-900 mt-2 truncate">{data.topVendor}</p>
<p className="text-xs text-slate-400 mt-1 font-medium truncate">Primary supplier this month</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Top Selling Products Chart */}
<div className="bg-white p-8 rounded-[2rem] border border-slate-100 shadow-sm">
<div className="flex items-center gap-3 mb-8">
<div className="p-2.5 bg-indigo-50 text-indigo-600 rounded-xl">
<TrendingUp size={20} />
</div>
<h3 className="text-lg font-bold text-slate-900">Highly Sold Items</h3>
</div>
<div className="h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data.topSellingItems} layout="vertical" margin={{ left: 40 }}>
<CartesianGrid strokeDasharray="3 3" horizontal={true} vertical={false} stroke="#f1f5f9" />
<XAxis type="number" hide />
<YAxis
dataKey="name"
type="category"
axisLine={false}
tickLine={false}
width={120}
tick={{ fontSize: 12, fontWeight: 600, fill: '#64748b' }}
/>
<Tooltip
cursor={{ fill: '#f8fafc' }}
contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }}
/>
<Bar dataKey="quantity" radius={[0, 8, 8, 0]} barSize={32}>
{data.topSellingItems.map((_, index) => (
<Cell key={`cell-${index}`} fill={COLORS_CHART[index % COLORS_CHART.length]} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Vendor Contribution Pie Chart */}
<div className="bg-white p-8 rounded-[2rem] border border-slate-100 shadow-sm">
<div className="flex items-center gap-3 mb-8">
<div className="p-2.5 bg-emerald-50 text-emerald-600 rounded-xl">
<PieChartIcon size={20} />
</div>
<h3 className="text-lg font-bold text-slate-900">Vendor Expenditure Share</h3>
</div>
<div className="flex flex-col items-center">
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data.vendorSummary}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={100}
paddingAngle={5}
dataKey="amount"
nameKey="name"
>
{data.vendorSummary.map((_, index) => (
<Cell key={`cell-${index}`} fill={COLORS_CHART[index % COLORS_CHART.length]} />
))}
</Pie>
<Tooltip
contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }}
/>
</PieChart>
</ResponsiveContainer>
</div>
<div className="grid grid-cols-2 gap-x-12 gap-y-4 mt-4">
{data.vendorSummary.slice(0, 4).map((vendor, index) => (
<div key={index} className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: COLORS_CHART[index % COLORS_CHART.length] }} />
<span className="text-xs font-bold text-slate-600 truncate max-w-[120px]">{vendor.name}</span>
<span className="text-xs text-slate-400 font-medium">{vendor.amount.toLocaleString()}</span>
</div>
))}
</div>
</div>
</div>
</div>
{/* Detailed Performance Table */}
<div className="bg-white rounded-[2rem] border border-slate-100 shadow-sm overflow-hidden">
<div className="p-8 border-b border-slate-50 flex justify-between items-center">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-purple-50 text-purple-600 rounded-xl">
<BarChart3 size={20} />
</div>
<h3 className="text-lg font-bold text-slate-900">Store-wise Performance</h3>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50/50">
<th className="px-8 py-4 text-xs font-bold text-slate-400 uppercase tracking-widest">Stall / Store Name</th>
<th className="px-8 py-4 text-xs font-bold text-slate-400 uppercase tracking-widest">Orders Fulfilled</th>
<th className="px-8 py-4 text-xs font-bold text-slate-400 uppercase tracking-widest text-right">Revenue Generated</th>
<th className="px-8 py-4 text-xs font-bold text-slate-400 uppercase tracking-widest text-right">Avg Order Value</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-50">
{data.storeOverview.map((store, index) => (
<tr key={index} className="hover:bg-slate-50/50 transition-colors">
<td className="px-8 py-6">
<div className="font-bold text-slate-700">{store.name}</div>
<div className="text-[10px] text-indigo-500 font-bold uppercase tracking-wider mt-0.5">Primary Location</div>
</td>
<td className="px-8 py-6">
<div className="text-slate-600 font-semibold">{store.orders} Orders</div>
</td>
<td className="px-8 py-6 text-right">
<div className="text-indigo-600 font-black">{store.sale.toLocaleString()}</div>
</td>
<td className="px-8 py-6 text-right">
<div className="text-slate-500 font-medium">{store.orders > 0 ? (store.sale / store.orders).toFixed(0) : 0}</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
</div>
);
};
export default Reports;