Dashboard made robust
This commit is contained in:
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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'}`}>
|
||||||
|
|||||||
@@ -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'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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'}`}>
|
||||||
|
|||||||
@@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user