Removed PositEasy Referenecs
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
package com.rit.canteen.sales.controller;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.service.ReportService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/reports")
|
||||||
|
public class ReportController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ReportService reportService;
|
||||||
|
|
||||||
|
@GetMapping("/monthly")
|
||||||
|
public Map<String, Object> getMonthlyReport(
|
||||||
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime from,
|
||||||
|
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime to) {
|
||||||
|
return reportService.getMonthlyReport(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,4 +44,11 @@ public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecific
|
|||||||
"GROUP BY HOUR(o.createdAt) " +
|
"GROUP BY HOUR(o.createdAt) " +
|
||||||
"ORDER BY HOUR(o.createdAt)")
|
"ORDER BY HOUR(o.createdAt)")
|
||||||
List<Object[]> getHourlySales(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
|
List<Object[]> getHourlySales(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
|
||||||
|
|
||||||
|
@Query("SELECT i.productName, SUM(i.quantity), SUM(i.price * i.quantity) " +
|
||||||
|
"FROM Order o JOIN o.items i " +
|
||||||
|
"WHERE o.createdAt >= :start AND o.createdAt <= :end " +
|
||||||
|
"GROUP BY i.productName " +
|
||||||
|
"ORDER BY SUM(i.quantity) DESC")
|
||||||
|
List<Object[]> getTopSellingItems(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ public interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Lo
|
|||||||
@org.springframework.data.jpa.repository.Query("SELECT COUNT(p) FROM PurchaseOrder p WHERE p.status != 'PAID'")
|
@org.springframework.data.jpa.repository.Query("SELECT COUNT(p) FROM PurchaseOrder p WHERE p.status != 'PAID'")
|
||||||
long countUnpaidBills();
|
long countUnpaidBills();
|
||||||
|
|
||||||
|
@org.springframework.data.jpa.repository.Query("SELECT SUM(p.amount) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end")
|
||||||
|
java.math.BigDecimal getTotalPurchaseAmountInRange(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
|
||||||
|
|
||||||
|
@org.springframework.data.jpa.repository.Query("SELECT p.vendor.name, SUM(p.amount), COUNT(p) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end GROUP BY p.vendor.name")
|
||||||
|
List<Object[]> getVendorSummary(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
|
||||||
|
|
||||||
|
@org.springframework.data.jpa.repository.Query("SELECT p.date, SUM(p.amount) FROM PurchaseOrder p WHERE p.date >= :start AND p.date <= :end GROUP BY p.date ORDER BY p.date ASC")
|
||||||
|
List<Object[]> getPurchaseTrendInRange(@org.springframework.data.repository.query.Param("start") java.time.LocalDateTime start, @org.springframework.data.repository.query.Param("end") java.time.LocalDateTime end);
|
||||||
|
|
||||||
@org.springframework.data.jpa.repository.Query("SELECT p.date, SUM(p.amount) FROM PurchaseOrder p GROUP BY p.date ORDER BY p.date ASC")
|
@org.springframework.data.jpa.repository.Query("SELECT p.date, SUM(p.amount) FROM PurchaseOrder p GROUP BY p.date ORDER BY p.date ASC")
|
||||||
List<Object[]> getPurchaseTrend();
|
List<Object[]> getPurchaseTrend();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.rit.canteen.sales.service;
|
||||||
|
|
||||||
|
import com.rit.canteen.sales.repository.OrderRepository;
|
||||||
|
import com.rit.canteen.sales.repository.PurchaseOrderRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ReportService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderRepository orderRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PurchaseOrderRepository purchaseOrderRepository;
|
||||||
|
|
||||||
|
public Map<String, Object> getMonthlyReport(LocalDateTime from, LocalDateTime to) {
|
||||||
|
Map<String, Object> report = new HashMap<>();
|
||||||
|
|
||||||
|
// 1. Sales Summary
|
||||||
|
BigDecimal totalSales = orderRepository.getRevenuePerPeriod(from, to);
|
||||||
|
report.put("totalSales", totalSales != null ? totalSales : BigDecimal.ZERO);
|
||||||
|
report.put("totalOrders", orderRepository.countByCreatedAtBetween(from, to));
|
||||||
|
|
||||||
|
// 2. Top Selling Items
|
||||||
|
List<Object[]> topItemsData = orderRepository.getTopSellingItems(from, to);
|
||||||
|
List<Map<String, Object>> topItems = new ArrayList<>();
|
||||||
|
for (int i = 0; i < Math.min(topItemsData.size(), 10); i++) {
|
||||||
|
Object[] row = topItemsData.get(i);
|
||||||
|
Map<String, Object> item = new HashMap<>();
|
||||||
|
item.put("name", row[0]);
|
||||||
|
item.put("quantity", row[1]);
|
||||||
|
item.put("revenue", row[2]);
|
||||||
|
topItems.add(item);
|
||||||
|
}
|
||||||
|
report.put("topSellingItems", topItems);
|
||||||
|
|
||||||
|
// 3. Purchase Summary
|
||||||
|
BigDecimal totalPurchases = purchaseOrderRepository.getTotalPurchaseAmountInRange(from, to);
|
||||||
|
report.put("totalPurchases", totalPurchases != null ? totalPurchases : BigDecimal.ZERO);
|
||||||
|
|
||||||
|
// 4. Vendor Summary
|
||||||
|
List<Object[]> vendorData = purchaseOrderRepository.getVendorSummary(from, to);
|
||||||
|
List<Map<String, Object>> vendorSummary = new ArrayList<>();
|
||||||
|
String topVendor = "N/A";
|
||||||
|
BigDecimal maxAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
for (Object[] row : vendorData) {
|
||||||
|
Map<String, Object> vendor = new HashMap<>();
|
||||||
|
String name = (String) row[0];
|
||||||
|
BigDecimal amount = (BigDecimal) row[1];
|
||||||
|
vendor.put("name", name);
|
||||||
|
vendor.put("amount", amount);
|
||||||
|
vendor.put("orderCount", row[2]);
|
||||||
|
vendorSummary.add(vendor);
|
||||||
|
|
||||||
|
if (amount.compareTo(maxAmount) > 0) {
|
||||||
|
maxAmount = amount;
|
||||||
|
topVendor = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report.put("vendorSummary", vendorSummary);
|
||||||
|
report.put("topVendor", topVendor);
|
||||||
|
|
||||||
|
// 5. Store/Stall Overview
|
||||||
|
List<Object[]> storeData = orderRepository.getStoreOverview(from, to);
|
||||||
|
List<Map<String, Object>> storeOverview = new ArrayList<>();
|
||||||
|
for (Object[] row : storeData) {
|
||||||
|
Map<String, Object> store = new HashMap<>();
|
||||||
|
store.put("name", row[0] != null ? row[0] : "Unknown Stall");
|
||||||
|
store.put("sale", row[1] != null ? row[1] : 0);
|
||||||
|
store.put("orders", row[2] != null ? row[2] : 0);
|
||||||
|
storeOverview.add(store);
|
||||||
|
}
|
||||||
|
report.put("storeOverview", storeOverview);
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>frontend</title>
|
<title>RIT Canteen Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
236
frontend/package-lock.json
generated
236
frontend/package-lock.json
generated
@@ -12,6 +12,8 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
|
"jspdf": "^4.2.1",
|
||||||
|
"jspdf-autotable": "^5.0.7",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
@@ -244,6 +246,15 @@
|
|||||||
"node": ">=6.0.0"
|
"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": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
@@ -1350,6 +1361,19 @@
|
|||||||
"undici-types": "~7.16.0"
|
"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": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.14",
|
"version": "19.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
@@ -1370,6 +1394,13 @@
|
|||||||
"@types/react": "^19.2.0"
|
"@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": {
|
"node_modules/@types/use-sync-external-store": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
@@ -1804,6 +1835,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.10.15",
|
"version": "2.10.15",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.15.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.15.tgz",
|
||||||
@@ -1893,6 +1934,26 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"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": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@@ -1966,6 +2027,18 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"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": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -1981,6 +2054,16 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/csstype": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
@@ -2160,6 +2243,16 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.331",
|
"version": "1.5.331",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
|
||||||
@@ -2425,6 +2518,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"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": {
|
"node_modules/file-entry-cache": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||||
@@ -2620,6 +2730,20 @@
|
|||||||
"hermes-estree": "0.25.1"
|
"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": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -2676,6 +2800,12 @@
|
|||||||
"node": ">=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": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -2783,6 +2913,32 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@@ -3238,6 +3394,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -3271,6 +3433,13 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -3347,6 +3516,16 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/react": {
|
||||||
"version": "19.2.4",
|
"version": "19.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||||
@@ -3481,6 +3660,13 @@
|
|||||||
"redux": "^5.0.0"
|
"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": {
|
"node_modules/reselect": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
@@ -3497,6 +3683,16 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/rolldown": {
|
||||||
"version": "1.0.0-rc.13",
|
"version": "1.0.0-rc.13",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz",
|
||||||
@@ -3593,6 +3789,16 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/strip-json-comments": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
@@ -3619,6 +3825,16 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/tailwind-merge": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
|
||||||
@@ -3650,6 +3866,16 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"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": {
|
"node_modules/tiny-invariant": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
"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"
|
"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": {
|
"node_modules/victory-vendor": {
|
||||||
"version": "37.3.6",
|
"version": "37.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
|
"jspdf": "^4.2.1",
|
||||||
|
"jspdf-autotable": "^5.0.7",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import Bills from './pages/Bills.tsx';
|
|||||||
import PurchaseSummary from './pages/PurchaseSummary.tsx';
|
import PurchaseSummary from './pages/PurchaseSummary.tsx';
|
||||||
import IntentDashboard from './pages/IntentDashboard.tsx';
|
import IntentDashboard from './pages/IntentDashboard.tsx';
|
||||||
import IntentList from './pages/IntentList.tsx';
|
import IntentList from './pages/IntentList.tsx';
|
||||||
|
import Reports from './pages/Reports.tsx';
|
||||||
|
|
||||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||||
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
|
const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
|
||||||
@@ -84,7 +85,7 @@ function App() {
|
|||||||
<Route path="expense/category" element={<PlaceholderPage title="Expense Category" />} />
|
<Route path="expense/category" element={<PlaceholderPage title="Expense Category" />} />
|
||||||
|
|
||||||
{/* Others */}
|
{/* Others */}
|
||||||
<Route path="reports" element={<PlaceholderPage title="Reports" />} />
|
<Route path="reports" element={<Reports />} />
|
||||||
<Route path="table" element={<PlaceholderPage title="Table Management" />} />
|
<Route path="table" element={<PlaceholderPage title="Table Management" />} />
|
||||||
<Route path="wallet" element={<PlaceholderPage title="Wallet" />} />
|
<Route path="wallet" element={<PlaceholderPage title="Wallet" />} />
|
||||||
<Route path="feedback" element={<PlaceholderPage title="Feedback" />} />
|
<Route path="feedback" element={<PlaceholderPage title="Feedback" />} />
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Target,
|
Target,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Wallet
|
Wallet,
|
||||||
|
BarChart3
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
AreaChart,
|
AreaChart,
|
||||||
@@ -21,6 +23,7 @@ import {
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [activeTab, setActiveTab] = useState('Sales');
|
const [activeTab, setActiveTab] = useState('Sales');
|
||||||
const [timeRange, setTimeRange] = useState('Today');
|
const [timeRange, setTimeRange] = useState('Today');
|
||||||
const [customDates, setCustomDates] = useState({ from: '', to: '' });
|
const [customDates, setCustomDates] = useState({ from: '', to: '' });
|
||||||
@@ -136,6 +139,14 @@ const Dashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<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">
|
<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">
|
<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>
|
<option>Store Type: All</option>
|
||||||
|
|||||||
384
frontend/src/pages/Reports.tsx
Normal file
384
frontend/src/pages/Reports.tsx
Normal 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;
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>ordering_site</title>
|
<title>RIT Canteen</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ const LoginScreen: React.FC = () => {
|
|||||||
|
|
||||||
<div className="login-footer-info">
|
<div className="login-footer-info">
|
||||||
<p>© 2026 Rajalakshmi Institute of Technology</p>
|
<p>© 2026 Rajalakshmi Institute of Technology</p>
|
||||||
<p>Canteen Automation System</p>
|
<p>RIT Canteen Management System</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@echo off
|
@echo off
|
||||||
git init
|
git init
|
||||||
git remote add origin https://github.com/Canteen-Automation/Positeasy-Clone.git
|
git remote add origin https://github.com/Canteen-Automation/RIT-Canteen.git
|
||||||
git checkout -b Abiram
|
git checkout -b Abiram
|
||||||
git add .
|
git add .
|
||||||
git commit -m "Pushing code to Abiram branch"
|
git commit -m "Pushing code to Abiram branch"
|
||||||
|
|||||||
Reference in New Issue
Block a user