v >= 1000 ? `R${v/1000}k` : `R${v}`} />
@@ -245,9 +245,9 @@ const Dashboard = () => {
-
₹{stats.periodRevenue.toLocaleString()}
+
R{(stats.periodRevenue || 0).toLocaleString()}
{timeRange === 'Today' ? 'Today' : timeRange}
-
Total: ₹{stats.totalSales.toLocaleString()}
+
Total: R{(stats.totalSales || 0).toLocaleString()}
{pieData.map(item => (
@@ -412,7 +412,7 @@ const Dashboard = () => {
Gross Sale
-
₹{Number(store.sale).toLocaleString()}
+
R{Number(store.sale).toLocaleString()}
Volume
diff --git a/frontend/src/pages/NewArrivals.tsx b/frontend/src/pages/NewArrivals.tsx
index 848f2a28..d0b58a7f 100644
--- a/frontend/src/pages/NewArrivals.tsx
+++ b/frontend/src/pages/NewArrivals.tsx
@@ -277,7 +277,7 @@ const NewArrivals: React.FC = () => {
-
+
setFormData({ ...formData, price: parseFloat(e.target.value) || 0 })} className="w-full px-5 py-4 bg-[#231651]/5 border-2 border-transparent focus:border-[#231651]/20 rounded-2xl text-lg font-black text-[#231651] outline-none transition-all" />
diff --git a/frontend/src/pages/Orders.tsx b/frontend/src/pages/Orders.tsx
index ffcace45..4d4a7414 100644
--- a/frontend/src/pages/Orders.tsx
+++ b/frontend/src/pages/Orders.tsx
@@ -465,7 +465,7 @@ const Orders: React.FC = () => {
Grand Total
-
₹{order.totalAmount.toFixed(2)}
+
R{order.totalAmount.toFixed(2)}
@@ -628,9 +628,9 @@ const Orders: React.FC = () => {
-
₹{(item.price * item.quantity).toLocaleString()}
+
R{(item.price * item.quantity).toLocaleString()}
- {item.quantity} x ₹{item.price}
+ {item.quantity} x R{item.price}
@@ -700,7 +700,7 @@ const Orders: React.FC = () => {
Active Grand Total
-
₹{selectedOrder.totalAmount.toLocaleString()}
+
R{selectedOrder.totalAmount.toLocaleString()}
Transaction Pending Approval
@@ -784,7 +784,7 @@ const Orders: React.FC = () => {
{item.productName}
-
₹{item.price} each
+
R{item.price} each
@@ -805,7 +805,7 @@ const Orders: React.FC = () => {
-
₹{(item.price * item.quantity).toFixed(2)}
+
R{(item.price * item.quantity).toFixed(2)}
- ₹{product.price}
+ R{product.price}
))
) : editSearchQuery ? (
@@ -866,7 +866,7 @@ const Orders: React.FC = () => {
New Order Total
- ₹{editTotal.toFixed(2)}
+ R{editTotal.toFixed(2)}
- ₹{product.price} |
+ R{product.price} |
@@ -472,7 +472,7 @@ const Products = () => {
setFormData({ ...formData, discountPercent: parseFloat(e.target.value) || 0 })} className="w-full px-4 py-3 border border-[#e2e8f0] rounded-xl text-sm" />
diff --git a/frontend/src/pages/Ritz.tsx b/frontend/src/pages/Ritz.tsx
new file mode 100644
index 00000000..d55ebc39
--- /dev/null
+++ b/frontend/src/pages/Ritz.tsx
@@ -0,0 +1,235 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Building2,
+ CircleDollarSign,
+ TrendingUp,
+ Users,
+ ArrowUpRight,
+ ArrowDownLeft,
+ Clock,
+ Search,
+ Filter,
+ Download,
+ Fingerprint
+} from 'lucide-react';
+import { motion } from 'framer-motion';
+import colorVector from '../assets/color-vector.png';
+
+interface TokenTransaction {
+ id: number;
+ amount: number;
+ type: 'TOPUP' | 'SPEND' | 'REFUND';
+ description: string;
+ timestamp: string;
+ referenceId: string;
+ user: {
+ id: number;
+ name: string;
+ mobileNumber: string;
+ };
+}
+
+interface Stats {
+ totalCirculation: number;
+ activeWallets: number;
+ totalUsers: number;
+ serializedUnitsTotal: number;
+}
+
+const RitzPage: React.FC = () => {
+ const [transactions, setTransactions] = useState ([]);
+ const [stats, setStats] = useState({
+ totalCirculation: 0,
+ activeWallets: 0,
+ totalUsers: 0,
+ serializedUnitsTotal: 0
+ });
+ const [isLoading, setIsLoading] = useState(true);
+ const [searchTerm, setSearchTerm] = useState('');
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ const fetchData = async () => {
+ try {
+ setIsLoading(true);
+ const host = window.location.hostname;
+
+ const statsRes = await fetch(`http://${host}:8080/api/wallet/stats`);
+ const statsData = await statsRes.json();
+ setStats(statsData);
+
+ const transRes = await fetch(`http://${host}:8080/api/wallet/transactions/all`);
+ const transData = await transRes.json();
+ setTransactions(Array.isArray(transData) ? transData : []);
+ } catch (error) {
+ console.error('Error fetching Ritz data:', error);
+ setTransactions([]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const filteredTransactions = Array.isArray(transactions) ? transactions.filter(t =>
+ t.user?.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ t.user?.mobileNumber?.includes(searchTerm) ||
+ t.referenceId?.toLowerCase().includes(searchTerm.toLowerCase())
+ ) : [];
+
+ return (
+
+ {/* Premium Header Banner */}
+
+ 
+
+
+
+ Monitor the heartbeat of the Ritz digital economy. Track circulation,
+ active wallets, and system-wide transactions in real-time.
+
+
+
+
+ {/* Stats Grid */}
+
+ {[
+ { label: 'Tokens in Circulation', value: `R${(stats.totalCirculation || 0).toLocaleString()}`, icon: TrendingUp, color: 'text-emerald-600', bg: 'bg-emerald-50' },
+ { label: 'Active Token Wallets', value: (stats.activeWallets || 0).toLocaleString(), icon: Users, color: 'text-blue-600', bg: 'bg-blue-50' },
+ { label: 'Serialized Audit Units', value: (stats.serializedUnitsTotal || 0).toLocaleString(), icon: Fingerprint, color: 'text-amber-600', bg: 'bg-amber-50' },
+ { label: 'Total Accounts', value: (stats.totalUsers || 0).toLocaleString(), icon: Building2, color: 'text-indigo-600', bg: 'bg-indigo-50' }
+ ].map((item, i) => (
+
+
+ {item.label}
+ {item.value}
+
+
+
+
+
+ ))}
+
+
+ {/* Main Table Section */}
+
+
+
+ Master Transaction Log
+ Detailed history of all wallet activities
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+ {isLoading ? (
+
+
+ Syncing Transaction Feed...
+
+ ) : (
+
+
+
+ | Customer |
+ Type |
+ Amount |
+ Description |
+ Timestamp |
+
+
+
+ {filteredTransactions.map((t, i) => (
+
+
+
+
+ {t.user.name.split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase()}
+
+
+ {t.user.name}
+ {t.user.mobileNumber}
+
+
+ |
+
+
+ {t.type === 'TOPUP' ? : }
+ {t.type}
+
+ |
+
+
+ {t.type === 'TOPUP' ? '+' : '-'} {t.amount.toLocaleString()}
+
+ |
+
+ {t.description}
+ {t.referenceId}
+ |
+
+
+
+ {new Date(t.timestamp).toLocaleString('en-IN', {
+ day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit'
+ })}
+
+ |
+
+ ))}
+
+
+ )}
+
+
+
+ {/* Animation Styles */}
+
+
+ );
+};
+
+export default RitzPage;
diff --git a/frontend/src/pages/RitzCirculation.tsx b/frontend/src/pages/RitzCirculation.tsx
new file mode 100644
index 00000000..2afab956
--- /dev/null
+++ b/frontend/src/pages/RitzCirculation.tsx
@@ -0,0 +1,287 @@
+import React, { useState, useEffect } from 'react';
+import {
+ CircleDollarSign,
+ Search,
+ Filter,
+ Download,
+ Fingerprint,
+ ShieldCheck,
+ History,
+ Lock,
+ Unlock,
+ Users,
+ ChevronLeft,
+ ChevronRight
+} from 'lucide-react';
+import { motion } from 'framer-motion';
+import colorVector from '../assets/color-vector.png';
+
+interface TokenUnit {
+ id: number;
+ tokenHash: string;
+ ownerId: number;
+ status: 'ACTIVE' | 'SPENT' | 'REVOKED';
+ createdAt: string;
+ spentAt: string | null;
+}
+
+interface PageResponse {
+ content: TokenUnit[];
+ totalPages: number;
+ totalElements: number;
+ number: number;
+ size: number;
+}
+
+const RitzCirculation: React.FC = () => {
+ const [tokens, setTokens] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [searchTerm, setSearchTerm] = useState('');
+
+ // Pagination State
+ const [page, setPage] = useState(0);
+ const [size] = useState(20);
+ const [totalPages, setTotalPages] = useState(0);
+ const [totalElements, setTotalElements] = useState(0);
+
+ useEffect(() => {
+ fetchCirculation();
+ }, [page, size]);
+
+ const fetchCirculation = async () => {
+ try {
+ setIsLoading(true);
+ const host = window.location.hostname;
+ const res = await fetch(`http://${host}:8080/api/wallet/circulation?page=${page}&size=${size}`);
+ const data: PageResponse = await res.json();
+
+ setTokens(data.content || []);
+ setTotalPages(data.totalPages || 0);
+ setTotalElements(data.totalElements || 0);
+ } catch (error) {
+ console.error('Error fetching circulation:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const filteredTokens = tokens.filter(t =>
+ t.tokenHash.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ t.ownerId?.toString().includes(searchTerm)
+ );
+
+ return (
+
+ {/* Regulated Header */}
+
+ 
+
+
+
+
+
+
+ Ritz Forensic Ledger
+
+
+
+
+ A comprehensive, non-repudiable audit trail of every individual Ritz unit.
+ Each token is uniquely serialized and traceable to its point of issuance.
+
+
+
+
+ {/* Circulation Stats - Summary level only as we now have pagination */}
+
+
+
+
+
+
+ Total Audit Population
+
+ {totalElements.toLocaleString()} Units
+
+
+
+
+
+
+ Ledger Compliance
+
+ 100% Validated
+
+
+
+
+
+
+ Current View Range
+
+ Page {page + 1} / {totalPages || 1}
+
+
+
+ {/* Ledger Table */}
+
+
+
+ Circulation Audit
+ Page {page + 1} of {totalPages} (Showing {tokens.length} records)
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+ {isLoading ? (
+
+
+ Verifying Ledger Integrity...
+
+ ) : (
+
+
+
+ | Transaction Unit ID |
+ Cryptographic Serial |
+ Owner UID |
+ Status |
+ Issuance Date |
+
+
+
+ {filteredTokens.map((t, i) => (
+
+ |
+ #{t.id.toString().padStart(6, '0')}
+ |
+
+
+ |
+
+
+
+ {t.ownerId || 'SYSTEM'}
+
+ |
+
+
+ {t.status === 'ACTIVE' ? : }
+ {t.status}
+
+ |
+
+
+
+ {new Date(t.createdAt).toLocaleString('en-IN', {
+ day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'
+ })}
+
+ |
+
+ ))}
+
+
+ )}
+
+
+
+ {/* Pagination Footer - Sticky style */}
+
+
+
+
+
+
+ {isLoading ? '...' : `Page ${page + 1} of ${totalPages || 1}`}
+
+
+
+
+
+
+
+ {/* Animation Styles */}
+
+
+ );
+};
+
+export default RitzCirculation;
diff --git a/frontend/src/pages/Stalls.tsx b/frontend/src/pages/Stalls.tsx
index 9baf088e..fd8779a4 100644
--- a/frontend/src/pages/Stalls.tsx
+++ b/frontend/src/pages/Stalls.tsx
@@ -639,7 +639,7 @@ const Stalls: React.FC = () => {
{product.name}
- ₹{product.price} • {product.category}
+ R{product.price} • {product.category}
diff --git a/frontend/src/pages/StoreDashboard.tsx b/frontend/src/pages/StoreDashboard.tsx
index 65d39684..02a8efff 100644
--- a/frontend/src/pages/StoreDashboard.tsx
+++ b/frontend/src/pages/StoreDashboard.tsx
@@ -185,7 +185,7 @@ const StoreDashboard = () => {
- v >= 1000 ? `${v/1000}k` : v} />
+ v >= 1000 ? `R${v/1000}k` : `R${v}`} />
@@ -216,7 +216,7 @@ const StoreDashboard = () => {
- ₹{formatCurrency(stats.totalSales)}
+ R{formatCurrency(stats.totalSales)}
diff --git a/ordering_site/src/components/CartTab.tsx b/ordering_site/src/components/CartTab.tsx
index 14aec325..cc41c24f 100644
--- a/ordering_site/src/components/CartTab.tsx
+++ b/ordering_site/src/components/CartTab.tsx
@@ -23,7 +23,7 @@ const CartTab: React.FC = () => {
{totalItems} {totalItems === 1 ? 'item' : 'items'}
- ₹{totalPrice.toFixed(0)}
+ R{totalPrice.toFixed(0)}
{item.name}
- ₹{item.price.toFixed(2)}
+ R{item.price.toFixed(2)}
{item.description}
diff --git a/ordering_site/src/pages/CartScreen.tsx b/ordering_site/src/pages/CartScreen.tsx
index e90d702d..4d5721d9 100644
--- a/ordering_site/src/pages/CartScreen.tsx
+++ b/ordering_site/src/pages/CartScreen.tsx
@@ -46,7 +46,7 @@ const CartScreen: React.FC = () => {
- ₹{(item.price * item.quantity).toFixed(2)}
+ R{(item.price * item.quantity).toFixed(2)}
@@ -87,7 +87,7 @@ const CartScreen: React.FC = () => {
{totalItems} {totalItems === 1 ? 'Item' : 'Items'}
- ₹{(totalPrice + 2.5).toFixed(2)}
+ R{(totalPrice + 2.5).toFixed(2)}
{item.name}
- ₹{item.price.toFixed(2)}
+ R{item.price.toFixed(2)}
@@ -88,7 +88,7 @@ const ItemDetailScreen: React.FC = () => {
|