Dashboard made robust

This commit is contained in:
Sidharth Prabhu
2026-04-21 01:10:45 +05:30
parent ee98c1c45e
commit 7b9473266f
13 changed files with 104 additions and 61 deletions

View File

@@ -16,4 +16,9 @@ public class BackendApplication {
SpringApplication.run(BackendApplication.class, args); SpringApplication.run(BackendApplication.class, args);
} }
@jakarta.annotation.PostConstruct
public void init() {
java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("Asia/Kolkata"));
System.out.println("[TIMEZONE] Global JVM TimeZone set to: " + java.util.TimeZone.getDefault().getID());
}
} }

View File

@@ -128,6 +128,10 @@ public class OrderController {
@PostMapping @PostMapping
@org.springframework.transaction.annotation.Transactional @org.springframework.transaction.annotation.Transactional
public ResponseEntity<?> placeOrder(@RequestBody Order order) { public ResponseEntity<?> placeOrder(@RequestBody Order order) {
System.out.println("[REVENUE-TRACE] Incoming Place Order Request -> User: " + order.getUserId() +
" | Total: " + order.getTotalAmount() +
" | Items: " + (order.getItems() != null ? order.getItems().size() : 0));
// 1. Pre-validation and linking // 1. Pre-validation and linking
if (order.getItems() == null || order.getItems().isEmpty()) { if (order.getItems() == null || order.getItems().isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("success", false, "message", "Order must have items")); return ResponseEntity.badRequest().body(Map.of("success", false, "message", "Order must have items"));
@@ -164,6 +168,9 @@ public class OrderController {
// 3. Complete Order Details // 3. Complete Order Details
for (OrderItem item : order.getItems()) { for (OrderItem item : order.getItems()) {
item.setOrder(order); item.setOrder(order);
if (item.getStallName() == null || item.getStallName().isEmpty() || item.getStallName().equals("Unknown Stall")) {
item.setStallName("RIT Canteen");
}
} }
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
@@ -189,7 +196,16 @@ public class OrderController {
// 5. Final Save // 5. Final Save
Order savedOrder = orderRepository.save(order); Order savedOrder = orderRepository.save(order);
System.out.println("Placed Order: " + savedOrder.getId() + " -> Display ID: #" + displayId); System.out.println("[REVENUE-TRACE] Saved Order: " + savedOrder.getOrderNumber() +
" | Display ID: #" + savedOrder.getDisplayOrderId() +
" | CreatedAt: " + savedOrder.getCreatedAt() +
" | Payment: " + savedOrder.getPaymentMethod());
if (savedOrder.getItems() != null) {
savedOrder.getItems().forEach(item ->
System.out.println("[REVENUE-TRACE] Saved Item: " + item.getProductName() +
" | Stall: " + item.getStallName()));
}
return ResponseEntity.ok(Map.of( return ResponseEntity.ok(Map.of(
"success", true, "success", true,

View File

@@ -3,16 +3,16 @@ package com.rit.canteen.sales.model;
public class TrendingItem { public class TrendingItem {
private String name; private String name;
private String category; private String category;
private long qty; private long orderCount;
private String image; private String imageUrl;
public TrendingItem() {} public TrendingItem() {}
public TrendingItem(String name, String category, long qty, String image) { public TrendingItem(String name, String category, long orderCount, String imageUrl) {
this.name = name; this.name = name;
this.category = category; this.category = category;
this.qty = qty; this.orderCount = orderCount;
this.image = image; this.imageUrl = imageUrl;
} }
public String getName() { return name; } public String getName() { return name; }
@@ -21,9 +21,9 @@ public class TrendingItem {
public String getCategory() { return category; } public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; } public void setCategory(String category) { this.category = category; }
public long getQty() { return qty; } public long getOrderCount() { return orderCount; }
public void setQty(long qty) { this.qty = qty; } public void setOrderCount(long orderCount) { this.orderCount = orderCount; }
public String getImage() { return image; } public String getImageUrl() { return imageUrl; }
public void setImage(String image) { this.image = image; } public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
} }

View File

@@ -35,8 +35,9 @@ public class DashboardService {
private TokenTransactionRepository tokenTransactionRepository; private TokenTransactionRepository tokenTransactionRepository;
public GeneralDashboardData getGeneralDashboardData(LocalDateTime from, LocalDateTime to) { public GeneralDashboardData getGeneralDashboardData(LocalDateTime from, LocalDateTime to) {
if (from == null) from = LocalDate.now().atStartOfDay(); java.time.ZoneId zone = java.time.ZoneId.of("Asia/Kolkata");
if (to == null) to = LocalDate.now().atTime(LocalTime.MAX); if (from == null) from = java.time.LocalDate.now(zone).atStartOfDay();
if (to == null) to = java.time.LocalDate.now(zone).atTime(java.time.LocalTime.MAX);
System.out.println("[DIAGNOSTIC] Final timestamp range for service logic: " + from + " to " + to); System.out.println("[DIAGNOSTIC] Final timestamp range for service logic: " + from + " to " + to);
DashboardStats stats = getDashboardStats(from, to); DashboardStats stats = getDashboardStats(from, to);
@@ -47,11 +48,17 @@ public class DashboardService {
List<Map<String, Object>> storeOverview = new ArrayList<>(); List<Map<String, Object>> storeOverview = new ArrayList<>();
for (Object[] row : storeData) { for (Object[] row : storeData) {
Map<String, Object> store = new HashMap<>(); Map<String, Object> store = new HashMap<>();
store.put("name", row[0] != null ? row[0] : "Unknown Stall"); String stallName = (row[0] != null && !row[0].toString().equals("Unknown Stall")) ? row[0].toString() : "RIT Canteen";
store.put("sale", row[1] != null ? row[1] : 0); BigDecimal saleVal = row[1] != null ? (BigDecimal) row[1] : BigDecimal.ZERO;
store.put("orders", row[2] != null ? row[2] : 0); long orderCount = row[2] != null ? ((Number) row[2]).longValue() : 0;
store.put("taxes", 0); // Placeholder
store.put("purchase", 0); // Placeholder System.out.println("[REVENUE-TRACE] Store Overview Result -> Stall: " + stallName + " | Sales: " + saleVal + " | Orders: " + orderCount);
store.put("name", stallName);
store.put("sale", saleVal);
store.put("orders", orderCount);
store.put("taxes", 0);
store.put("purchase", 0);
storeOverview.add(store); storeOverview.add(store);
} }
@@ -86,7 +93,7 @@ public class DashboardService {
String imageData = (String) row[3]; String imageData = (String) row[3];
// Format image data for frontend // Format image data for frontend
String imageUrl = imageData != null ? (imageData.startsWith("http") ? imageData : (imageData.startsWith("data:") ? imageData : "data:image/png;base64," + imageData)) : "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=200&q=80"; String imageUrl = imageData != null ? (imageData.startsWith("http") ? imageData : (imageData.startsWith("data:") ? imageData : "data:image/png;base64," + imageData)) : null;
trendingItems.add(new TrendingItem(name, category, qty, imageUrl)); trendingItems.add(new TrendingItem(name, category, qty, imageUrl));
} }
@@ -103,7 +110,7 @@ public class DashboardService {
? BigDecimal.valueOf(stats.getTotalSales()).divide(BigDecimal.valueOf(stats.getActiveOrders()), 2, RoundingMode.HALF_UP) ? BigDecimal.valueOf(stats.getTotalSales()).divide(BigDecimal.valueOf(stats.getActiveOrders()), 2, RoundingMode.HALF_UP)
: BigDecimal.ZERO; : BigDecimal.ZERO;
Map<String, String> avgInsight = new HashMap<>(); Map<String, String> avgInsight = new HashMap<>();
avgInsight.put("text", "" + avg + " average order value! Either everyone's hungry or just living large 🔥😋"); avgInsight.put("text", "R" + avg + " average order value! Either everyone's hungry or just living large 🔥😋");
avgInsight.put("color", "bg-emerald-50 text-emerald-600 border-emerald-100"); avgInsight.put("color", "bg-emerald-50 text-emerald-600 border-emerald-100");
insights.add(avgInsight); insights.add(avgInsight);
@@ -113,7 +120,7 @@ public class DashboardService {
insights.add(customerInsight); insights.add(customerInsight);
Map<String, String> revenueInsight = new HashMap<>(); Map<String, String> revenueInsight = new HashMap<>();
revenueInsight.put("text", "RIT Canteen clocked " + String.format("%,d", stats.getTotalSales()) + " — ka-ching! That's called business booming 💸📈"); revenueInsight.put("text", "RIT Canteen clocked R" + String.format("%,d", stats.getTotalSales()) + " — ka-ching! That's called business booming 💸📈");
revenueInsight.put("color", "bg-blue-50 text-blue-600 border-blue-100"); revenueInsight.put("color", "bg-blue-50 text-blue-600 border-blue-100");
insights.add(revenueInsight); insights.add(revenueInsight);
} else { } else {
@@ -128,11 +135,13 @@ public class DashboardService {
} }
public DashboardStats getDashboardStats(LocalDateTime from, LocalDateTime to) { public DashboardStats getDashboardStats(LocalDateTime from, LocalDateTime to) {
LocalDateTime startOfToday = LocalDate.now().atStartOfDay(); System.out.println("[REVENUE-TRACE] Dashboard Request Range: " + from + " to " + to);
LocalDateTime endOfToday = LocalDate.now().atTime(LocalTime.MAX); java.time.ZoneId zone = java.time.ZoneId.of("Asia/Kolkata");
LocalDateTime startOfToday = LocalDate.now(zone).atStartOfDay();
LocalDateTime endOfToday = LocalDate.now(zone).atTime(LocalTime.MAX);
LocalDateTime startOfYesterday = LocalDate.now().minusDays(1).atStartOfDay(); LocalDateTime startOfYesterday = LocalDate.now(zone).minusDays(1).atStartOfDay();
LocalDateTime endOfYesterday = LocalDate.now().minusDays(1).atTime(LocalTime.MAX); LocalDateTime endOfYesterday = LocalDate.now(zone).minusDays(1).atTime(LocalTime.MAX);
System.out.println("[DIAGNOSTIC] Fetching Total Revenue..."); System.out.println("[DIAGNOSTIC] Fetching Total Revenue...");
BigDecimal totalRevenueRaw = tokenTransactionRepository.sumByType(com.rit.canteen.sales.model.TokenTransaction.TransactionType.TOPUP); BigDecimal totalRevenueRaw = tokenTransactionRepository.sumByType(com.rit.canteen.sales.model.TokenTransaction.TransactionType.TOPUP);
@@ -147,6 +156,8 @@ public class DashboardService {
int activeOrders = (int) orderRepository.countByCreatedAtBetween(from, to); int activeOrders = (int) orderRepository.countByCreatedAtBetween(from, to);
int dailyCustomers = (int) orderRepository.countUniqueUsersInRange(from, to); int dailyCustomers = (int) orderRepository.countUniqueUsersInRange(from, to);
System.out.println("[REVENUE-TRACE] Active Orders in Range: " + activeOrders + " | Unique Customers: " + dailyCustomers);
BigDecimal todayRevenue = tokenTransactionRepository.sumByTypeInRange(com.rit.canteen.sales.model.TokenTransaction.TransactionType.TOPUP, startOfToday, endOfToday); BigDecimal todayRevenue = tokenTransactionRepository.sumByTypeInRange(com.rit.canteen.sales.model.TokenTransaction.TransactionType.TOPUP, startOfToday, endOfToday);
BigDecimal yesterdayRevenue = tokenTransactionRepository.sumByTypeInRange(com.rit.canteen.sales.model.TokenTransaction.TransactionType.TOPUP, startOfYesterday, endOfYesterday); BigDecimal yesterdayRevenue = tokenTransactionRepository.sumByTypeInRange(com.rit.canteen.sales.model.TokenTransaction.TransactionType.TOPUP, startOfYesterday, endOfYesterday);
System.out.println("[DIAGNOSTIC] Today vs Yesterday: " + todayRevenue + " / " + yesterdayRevenue); System.out.println("[DIAGNOSTIC] Today vs Yesterday: " + todayRevenue + " / " + yesterdayRevenue);

View File

@@ -9,6 +9,7 @@ spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.jdbc.time_zone=Asia/Kolkata
# Master Account Credentials # Master Account Credentials
app.master.username=admin app.master.username=admin

View File

@@ -213,7 +213,7 @@ const ArchivedOrders: React.FC = () => {
</div> </div>
<div className="text-right"> <div className="text-right">
<div className="text-[10px] text-slate-400 font-medium uppercase mb-0.5">Grand Total</div> <div className="text-[10px] text-slate-400 font-medium uppercase mb-0.5">Grand Total</div>
<div className="text-lg font-black text-slate-900 leading-none">R{order.totalAmount.toFixed(2)}</div> <div className="text-lg font-black text-slate-900 leading-none">{order.totalAmount.toFixed(2)}</div>
</div> </div>
</div> </div>
@@ -325,8 +325,8 @@ const ArchivedOrders: React.FC = () => {
</div> </div>
</div> </div>
<div className="text-right"> <div className="text-right">
<div className="text-sm font-black text-slate-900">R{(item.price * item.quantity).toLocaleString()}</div> <div className="text-sm font-black text-slate-900">{(item.price * item.quantity).toLocaleString()}</div>
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-tighter">@ R{item.price}</div> <div className="text-[10px] font-bold text-slate-400 uppercase tracking-tighter">@ {item.price}</div>
</div> </div>
</div> </div>
))} ))}
@@ -377,7 +377,7 @@ const ArchivedOrders: React.FC = () => {
</div> </div>
<div className="text-right flex flex-col gap-1 min-w-[250px]"> <div className="text-right flex flex-col gap-1 min-w-[250px]">
<div className="text-[10px] text-slate-500 uppercase font-black tracking-widest mb-1">Final Settlement</div> <div className="text-[10px] text-slate-500 uppercase font-black tracking-widest mb-1">Final Settlement</div>
<div className="text-4xl font-black text-emerald-400 leading-none mb-1">R{selectedOrder.totalAmount.toLocaleString()}</div> <div className="text-4xl font-black text-emerald-400 leading-none mb-1">{selectedOrder.totalAmount.toLocaleString()}</div>
<div className="text-[10px] text-slate-500 font-bold uppercase tracking-[0.2em]">Transaction Fully Reconciled</div> <div className="text-[10px] text-slate-500 font-bold uppercase tracking-[0.2em]">Transaction Fully Reconciled</div>
</div> </div>
</div> </div>

View File

@@ -279,13 +279,13 @@ const Customers: React.FC = () => {
<CircleDollarSign size={16} /> <CircleDollarSign size={16} />
</div> </div>
<span className="text-sm font-black text-[#231651]"> <span className="text-sm font-black text-[#231651]">
R{user.ritzTokenBalance?.toLocaleString() || '0'} {user.ritzTokenBalance?.toLocaleString() || '0'}
</span> </span>
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<span className="text-sm font-bold text-[#231651]"> <span className="text-sm font-bold text-[#231651]">
R{user.ritzTokenBalance?.toLocaleString() || '0'} {user.ritzTokenBalance?.toLocaleString() || '0'}
</span> </span>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
@@ -364,7 +364,7 @@ const Customers: React.FC = () => {
RIT STUDENT RIT STUDENT
</span> </span>
<span className="px-2 py-0.5 bg-indigo-50 text-indigo-600 text-[10px] font-bold rounded-lg border border-indigo-100 uppercase tracking-wider"> <span className="px-2 py-0.5 bg-indigo-50 text-indigo-600 text-[10px] font-bold rounded-lg border border-indigo-100 uppercase tracking-wider">
R{user.ritzTokenBalance?.toLocaleString() || '0'} TOKENS {user.ritzTokenBalance?.toLocaleString() || '0'} TOKENS
</span> </span>
</div> </div>

View File

@@ -224,7 +224,7 @@ const Dashboard = () => {
</defs> </defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" /> <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#f1f5f9" />
<XAxis dataKey="time" axisLine={false} tickLine={false} tick={{fill: '#94a3b8', fontSize: 10, fontWeight: 700}} /> <XAxis dataKey="time" axisLine={false} tickLine={false} tick={{fill: '#94a3b8', fontSize: 10, fontWeight: 700}} />
<YAxis axisLine={false} tickLine={false} tick={{fill: '#94a3b8', fontSize: 10, fontWeight: 700}} tickFormatter={(v) => v >= 1000 ? `R${v/1000}k` : `R${v}`} /> <YAxis axisLine={false} tickLine={false} tick={{fill: '#94a3b8', fontSize: 10, fontWeight: 700}} tickFormatter={(v) => v >= 1000 ? `${v/1000}k` : `${v}`} />
<Tooltip contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }} /> <Tooltip contentStyle={{ borderRadius: '12px', border: 'none', boxShadow: '0 10px 15px -3px rgb(0 0 0 / 0.1)' }} />
<Area type="monotone" dataKey="value" stroke="#f43f5e" strokeWidth={3} fillOpacity={1} fill="url(#colorSalesMain)" /> <Area type="monotone" dataKey="value" stroke="#f43f5e" strokeWidth={3} fillOpacity={1} fill="url(#colorSalesMain)" />
</AreaChart> </AreaChart>
@@ -262,9 +262,9 @@ const Dashboard = () => {
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center"> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
<h2 className="text-3xl font-black text-slate-800 tracking-tighter">R{(stats.periodRevenue || 0).toLocaleString()}</h2> <h2 className="text-3xl font-black text-slate-800 tracking-tighter">{(stats.periodRevenue || 0).toLocaleString()}</h2>
<p className="text-[10px] font-black text-slate-400 uppercase tracking-widest mt-1">{timeRange === 'Today' ? 'Today' : timeRange}</p> <p className="text-[10px] font-black text-slate-400 uppercase tracking-widest mt-1">{timeRange === 'Today' ? 'Today' : timeRange}</p>
<p className="text-[8px] font-bold text-slate-300 uppercase tracking-widest mt-0.5">Total: R{(stats.totalSales || 0).toLocaleString()}</p> <p className="text-[8px] font-bold text-slate-300 uppercase tracking-widest mt-0.5">Total: {(stats.totalSales || 0).toLocaleString()}</p>
</div> </div>
<div className="flex gap-6 mt-2"> <div className="flex gap-6 mt-2">
{pieData.map(item => ( {pieData.map(item => (
@@ -365,12 +365,16 @@ const Dashboard = () => {
<div className="absolute top-4 right-4 bg-white/80 backdrop-blur-md px-2.5 py-1 rounded-lg text-[10px] font-black text-slate-900 border border-slate-100 z-10 shadow-sm"> <div className="absolute top-4 right-4 bg-white/80 backdrop-blur-md px-2.5 py-1 rounded-lg text-[10px] font-black text-slate-900 border border-slate-100 z-10 shadow-sm">
TOP #{idx+1} TOP #{idx+1}
</div> </div>
<div className="w-full h-32 rounded-2xl mb-5 overflow-hidden bg-slate-50"> <div className="w-full h-32 rounded-2xl mb-5 overflow-hidden bg-slate-50 flex items-center justify-center">
{item.imageUrl ? (
<img <img
src={item.imageUrl} src={item.imageUrl}
alt={item.name} alt={item.name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700 opacity-90 group-hover:opacity-100" className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700 opacity-90 group-hover:opacity-100"
/> />
) : (
<div className="w-full h-full bg-slate-50/50" />
)}
</div> </div>
<div className="flex justify-between items-start mb-2"> <div className="flex justify-between items-start mb-2">
<div> <div>
@@ -401,7 +405,9 @@ const Dashboard = () => {
key={idx} key={idx}
className={`p-5 rounded-2xl border transition-all cursor-default ${insight.color || 'border-slate-100 bg-slate-50/50 hover:bg-white hover:shadow-md hover:border-transparent'}`} className={`p-5 rounded-2xl border transition-all cursor-default ${insight.color || 'border-slate-100 bg-slate-50/50 hover:bg-white hover:shadow-md hover:border-transparent'}`}
> >
<p className="text-[11px] font-black text-slate-600 leading-relaxed uppercase tracking-tight">{insight.text || insight}</p> <p className="text-[11px] font-black text-slate-600 leading-relaxed uppercase tracking-tight">
{(insight.text || insight).replace(/R(?=[0-9])/g, '')}
</p>
</motion.div> </motion.div>
))} ))}
</div> </div>
@@ -429,7 +435,7 @@ const Dashboard = () => {
<div className="grid grid-cols-4 gap-4"> <div className="grid grid-cols-4 gap-4">
<div className="space-y-1.5"> <div className="space-y-1.5">
<p className="text-[9px] font-black text-rose-500 uppercase tracking-widest opacity-60">Gross Sale</p> <p className="text-[9px] font-black text-rose-500 uppercase tracking-widest opacity-60">Gross Sale</p>
<p className="text-lg font-black text-slate-800 tracking-tighter">R{Number(store.sale).toLocaleString()}</p> <p className="text-lg font-black text-slate-800 tracking-tighter">{Number(store.sale).toLocaleString()}</p>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
<p className="text-[9px] font-black text-blue-500 uppercase tracking-widest opacity-60">Volume</p> <p className="text-[9px] font-black text-blue-500 uppercase tracking-widest opacity-60">Volume</p>

View File

@@ -465,7 +465,7 @@ const Orders: React.FC = () => {
</div> </div>
<div className="text-right"> <div className="text-right">
<div className="text-[10px] text-slate-400 font-medium uppercase mb-0.5">Grand Total</div> <div className="text-[10px] text-slate-400 font-medium uppercase mb-0.5">Grand Total</div>
<div className="text-lg font-black text-slate-900 leading-none">R{order.totalAmount.toFixed(2)}</div> <div className="text-lg font-black text-slate-900 leading-none">{order.totalAmount.toFixed(2)}</div>
</div> </div>
</div> </div>
@@ -628,9 +628,9 @@ const Orders: React.FC = () => {
</div> </div>
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
<div className="text-right"> <div className="text-right">
<div className="text-sm font-black text-slate-900">R{(item.price * item.quantity).toLocaleString()}</div> <div className="text-sm font-black text-slate-900">{(item.price * item.quantity).toLocaleString()}</div>
<div className="text-[10px] font-bold text-slate-400"> <div className="text-[10px] font-bold text-slate-400">
{item.quantity} x <span className="text-indigo-400 font-black">R{item.price}</span> {item.quantity} x <span className="text-indigo-400 font-black">{item.price}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -700,7 +700,7 @@ const Orders: React.FC = () => {
<div className="text-right flex flex-col gap-1 min-w-[250px]"> <div className="text-right flex flex-col gap-1 min-w-[250px]">
<div className="text-[10px] text-indigo-400 uppercase font-black tracking-widest mb-1">Active Grand Total</div> <div className="text-[10px] text-indigo-400 uppercase font-black tracking-widest mb-1">Active Grand Total</div>
<div className="text-4xl font-black text-emerald-400 leading-none mb-1">R{selectedOrder.totalAmount.toLocaleString()}</div> <div className="text-4xl font-black text-emerald-400 leading-none mb-1">{selectedOrder.totalAmount.toLocaleString()}</div>
<div className="text-[10px] text-white/40 font-bold uppercase tracking-[0.2em]">Transaction Pending Approval</div> <div className="text-[10px] text-white/40 font-bold uppercase tracking-[0.2em]">Transaction Pending Approval</div>
</div> </div>
</div> </div>
@@ -784,7 +784,7 @@ const Orders: React.FC = () => {
</div> </div>
<div> <div>
<div className="font-bold text-slate-800 text-sm">{item.productName}</div> <div className="font-bold text-slate-800 text-sm">{item.productName}</div>
<div className="text-[10px] font-black text-indigo-400 uppercase">R{item.price} each</div> <div className="text-[10px] font-black text-indigo-400 uppercase">{item.price} each</div>
</div> </div>
</div> </div>
@@ -805,7 +805,7 @@ const Orders: React.FC = () => {
</button> </button>
</div> </div>
<div className="text-right min-w-[80px]"> <div className="text-right min-w-[80px]">
<div className="text-sm font-black text-slate-900 leading-none mb-1">R{(item.price * item.quantity).toFixed(2)}</div> <div className="text-sm font-black text-slate-900 leading-none mb-1">{(item.price * item.quantity).toFixed(2)}</div>
<button <button
onClick={() => removeItem(idx)} onClick={() => removeItem(idx)}
className="text-[10px] font-black text-rose-400 hover:text-rose-600 uppercase tracking-widest transition-colors flex items-center gap-1 active:scale-95" className="text-[10px] font-black text-rose-400 hover:text-rose-600 uppercase tracking-widest transition-colors flex items-center gap-1 active:scale-95"
@@ -848,7 +848,7 @@ const Orders: React.FC = () => {
<div className="font-bold text-slate-800 text-sm group-hover:text-indigo-600 transition-colors">{product.name}</div> <div className="font-bold text-slate-800 text-sm group-hover:text-indigo-600 transition-colors">{product.name}</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{product.category}</div> <div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">{product.category}</div>
</div> </div>
<div className="font-black text-emerald-600 text-xs">R{product.price}</div> <div className="font-black text-emerald-600 text-xs">{product.price}</div>
</button> </button>
)) ))
) : editSearchQuery ? ( ) : editSearchQuery ? (
@@ -866,7 +866,7 @@ const Orders: React.FC = () => {
<div className="mt-8 pt-8 border-t border-slate-200"> <div className="mt-8 pt-8 border-t border-slate-200">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">New Order Total</span> <span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">New Order Total</span>
<span className="text-2xl font-black text-emerald-600 tracking-tighter">R{editTotal.toFixed(2)}</span> <span className="text-2xl font-black text-emerald-600 tracking-tighter">{editTotal.toFixed(2)}</span>
</div> </div>
<button <button
onClick={saveOrderEdits} onClick={saveOrderEdits}

View File

@@ -395,7 +395,7 @@ const Products = () => {
})()} })()}
</div> </div>
</td> </td>
<td className="px-6 py-4 text-sm font-bold text-[#1e293b]">R{product.price}</td> <td className="px-6 py-4 text-sm font-bold text-[#1e293b]">{product.price}</td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-bold w-fit ${product.active ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600'}`}> <span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-bold w-fit ${product.active ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600'}`}>

View File

@@ -105,10 +105,10 @@ const Reports: React.FC = () => {
const summaryData = [ const summaryData = [
['Metric', 'Value'], ['Metric', 'Value'],
['Total Sales Revenue', `INR ${data.totalSales.toLocaleString()}`], ['Total Sales Revenue', ` ${data.totalSales.toLocaleString()}`],
['Total Orders Fulfilled', data.totalOrders.toString()], ['Total Orders Fulfilled', data.totalOrders.toString()],
['Total Inventory Purchases', `INR ${data.totalPurchases.toLocaleString()}`], ['Total Inventory Purchases', ` ${data.totalPurchases.toLocaleString()}`],
['Net Performance', `INR ${(data.totalSales - data.totalPurchases).toLocaleString()}`], ['Net Performance', ` ${(data.totalSales - data.totalPurchases).toLocaleString()}`],
['Top Performing Vendor', data.topVendor] ['Top Performing Vendor', data.topVendor]
]; ];
@@ -126,7 +126,7 @@ const Reports: React.FC = () => {
autoTable(doc, { autoTable(doc, {
startY: finalY1 + 20, startY: finalY1 + 20,
head: [['Product Name', 'Quantity Sold', 'Revenue']], head: [['Product Name', 'Quantity Sold', 'Revenue']],
body: data.topSellingItems.map(item => [item.name, item.quantity, `INR ${item.revenue.toLocaleString()}`]), body: data.topSellingItems.map(item => [item.name, item.quantity, ` ${item.revenue.toLocaleString()}`]),
theme: 'grid' theme: 'grid'
}); });
@@ -136,7 +136,7 @@ const Reports: React.FC = () => {
autoTable(doc, { autoTable(doc, {
startY: finalY2 + 20, startY: finalY2 + 20,
head: [['Vendor Name', 'Total Orders', 'Spend Amount']], head: [['Vendor Name', 'Total Orders', 'Spend Amount']],
body: data.vendorSummary.map(v => [v.name, v.orderCount, `INR ${v.amount.toLocaleString()}`]), body: data.vendorSummary.map(v => [v.name, v.orderCount, ` ${v.amount.toLocaleString()}`]),
theme: 'striped' theme: 'striped'
}); });

View File

@@ -639,7 +639,7 @@ const Stalls: React.FC = () => {
</div> </div>
<div> <div>
<p className="font-bold text-[#1e293b] text-sm">{product.name}</p> <p className="font-bold text-[#1e293b] text-sm">{product.name}</p>
<p className="text-[10px] font-bold text-[#94a3b8] uppercase tracking-wider">R{product.price} {product.category}</p> <p className="text-[10px] font-bold text-[#94a3b8] uppercase tracking-wider">{product.price} {product.category}</p>
</div> </div>
</div> </div>
<div className={`w-6 h-6 rounded flex items-center justify-center border-2 transition-all ${isSelected ? 'bg-[#231651] border-[#231651]' : 'border-slate-200 bg-white'}`}> <div className={`w-6 h-6 rounded flex items-center justify-center border-2 transition-all ${isSelected ? 'bg-[#231651] border-[#231651]' : 'border-slate-200 bg-white'}`}>

View File

@@ -358,8 +358,12 @@ const StoreDashboard = () => {
<tr key={idx} className="group cursor-pointer hover:bg-slate-50/50 transition-all"> <tr key={idx} className="group cursor-pointer hover:bg-slate-50/50 transition-all">
<td className="py-4"> <td className="py-4">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl overflow-hidden bg-slate-100 border border-slate-50 group-hover:scale-105 transition-transform"> <div className="w-12 h-12 rounded-xl overflow-hidden bg-slate-100 border border-slate-50 group-hover:scale-105 transition-transform flex items-center justify-center">
<img src={item.image} alt={item.name} className="w-full h-full object-cover" /> {item.imageUrl ? (
<img src={item.imageUrl} alt={item.name} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full bg-slate-100" />
)}
</div> </div>
<div className="space-y-0.5"> <div className="space-y-0.5">
<p className="text-[9px] font-black text-[#0f4475] uppercase tracking-widest brightness-110">{item.category}</p> <p className="text-[9px] font-black text-[#0f4475] uppercase tracking-widest brightness-110">{item.category}</p>
@@ -368,7 +372,7 @@ const StoreDashboard = () => {
</div> </div>
</td> </td>
<td className="py-4 text-right"> <td className="py-4 text-right">
<span className="text-sm font-black text-slate-800">{item.qty}</span> <span className="text-sm font-black text-slate-800">{item.orderCount}</span>
</td> </td>
</tr> </tr>
))} ))}