From d16c18cde4fc66a21aeda5d99bb19b5bfa6a9a6e Mon Sep 17 00:00:00 2001 From: Tharusha Bhashitha Date: Mon, 6 Apr 2026 00:17:10 +0530 Subject: [PATCH 1/3] feat: implement Customer CRM panel with intelligent recommendations and analytics endpoints --- .../src/components/pos/CustomerCRMPanel.tsx | 500 ++++++++++++++++++ .../client/src/pages/RetailPOS.tsx | 61 ++- .../controllers/registrationController.js | 72 +++ .../server/controllers/saleController.js | 19 +- ApexPOS Restaurent/server/models/AllModels.js | 1 + .../server/routes/registrationRoutes.js | 2 + 6 files changed, 651 insertions(+), 4 deletions(-) create mode 100644 ApexPOS Restaurent/client/src/components/pos/CustomerCRMPanel.tsx diff --git a/ApexPOS Restaurent/client/src/components/pos/CustomerCRMPanel.tsx b/ApexPOS Restaurent/client/src/components/pos/CustomerCRMPanel.tsx new file mode 100644 index 0000000..de15853 --- /dev/null +++ b/ApexPOS Restaurent/client/src/components/pos/CustomerCRMPanel.tsx @@ -0,0 +1,500 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Search, User, X, Star, TrendingUp, ShoppingBag, + Clock, Award, Zap, ChevronRight, UserPlus, Phone, + Crown, Sparkles, Heart +} from 'lucide-react'; +import api from '../../api/axios'; +import { useStore } from '../../store/useStore'; + +// ─── Interfaces ────────────────────────────────────────────────────────────── +interface Customer { + _id: string; + name: string; + phone: string; + email?: string; + address?: string; + totalPurchases: number; + loyaltyPoints: number; + status: string; +} + +interface Recommendation { + _id: string; + name: string; + price: number; + count: number; + totalQty: number; + lastOrdered: string; +} + +interface CustomerStats { + totalSpend: number; + visitCount: number; + avgOrderValue: number; + lastVisit: string | null; + firstVisit: string | null; + tier: string; + recentOrders: any[]; +} + +interface CustomerCRMPanelProps { + isOpen: boolean; + onClose: () => void; + onSelectCustomer: (customer: Customer | null) => void; + selectedCustomer: Customer | null; + onAddRecommendation: (productId: string, name: string, price: number) => void; +} + +// ─── Tier Config ───────────────────────────────────────────────────────────── +const TIER_CONFIG: Record = { + New: { color: 'text-gray-400', bg: 'bg-gray-500/10', border: 'border-gray-500/20', icon: , gradient: 'from-gray-600 to-gray-400' }, + Bronze: { color: 'text-amber-600', bg: 'bg-amber-600/10', border: 'border-amber-600/20', icon: , gradient: 'from-amber-700 to-amber-500' }, + Silver: { color: 'text-slate-300', bg: 'bg-slate-300/10', border: 'border-slate-300/20', icon: , gradient: 'from-slate-400 to-slate-200' }, + Gold: { color: 'text-yellow-400', bg: 'bg-yellow-400/10', border: 'border-yellow-400/20', icon: , gradient: 'from-yellow-500 to-amber-300' }, + Platinum: { color: 'text-violet-400', bg: 'bg-violet-400/10', border: 'border-violet-400/20', icon: , gradient: 'from-violet-500 to-fuchsia-400' }, +}; + +// ─── Main Component ────────────────────────────────────────────────────────── +const CustomerCRMPanel: React.FC = ({ + isOpen, onClose, onSelectCustomer, selectedCustomer, onAddRecommendation +}) => { + const theme = useStore(state => state.theme); + const [customers, setCustomers] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const [recommendations, setRecommendations] = useState([]); + const [stats, setStats] = useState(null); + const [loadingStats, setLoadingStats] = useState(false); + const [addedItems, setAddedItems] = useState>(new Set()); + + // Fetch all customers + useEffect(() => { + if (isOpen) { + api.get('/registration/customers') + .then(setCustomers) + .catch(err => console.error('Failed to fetch customers', err)); + } + }, [isOpen]); + + // Fetch recommendations + stats when customer is selected + const fetchCustomerData = useCallback(async (customerId: string) => { + setLoadingStats(true); + setAddedItems(new Set()); + try { + const [recs, customerStats] = await Promise.all([ + api.get(`/registration/customers/${customerId}/recommendations`), + api.get(`/registration/customers/${customerId}/stats`) + ]); + setRecommendations(recs); + setStats(customerStats); + } catch (err) { + console.error('Failed to fetch customer data', err); + setRecommendations([]); + setStats(null); + } finally { + setLoadingStats(false); + } + }, []); + + useEffect(() => { + if (selectedCustomer?._id) { + fetchCustomerData(selectedCustomer._id); + } else { + setRecommendations([]); + setStats(null); + } + }, [selectedCustomer, fetchCustomerData]); + + const handleSelectCustomer = (customer: Customer) => { + onSelectCustomer(customer); + }; + + const handleDeselectCustomer = () => { + onSelectCustomer(null); + setSearchTerm(''); + }; + + const handleAddItem = (rec: Recommendation) => { + onAddRecommendation(rec._id, rec.name, rec.price); + setAddedItems(prev => new Set(prev).add(rec._id)); + }; + + const filteredCustomers = customers.filter(c => + c.name.toLowerCase().includes(searchTerm.toLowerCase()) || + c.phone.includes(searchTerm) + ); + + const tierConfig = TIER_CONFIG[stats?.tier || 'New'] || TIER_CONFIG.New; + + const getTimeAgo = (dateStr: string | null) => { + if (!dateStr) return 'Never'; + const diff = Date.now() - new Date(dateStr).getTime(); + const days = Math.floor(diff / 86400000); + if (days === 0) return 'Today'; + if (days === 1) return 'Yesterday'; + if (days < 7) return `${days}d ago`; + if (days < 30) return `${Math.floor(days / 7)}w ago`; + return `${Math.floor(days / 30)}mo ago`; + }; + + return ( + + {isOpen && ( + <> + {/* Backdrop */} + + + {/* Panel */} + + {/* ─── Header ──────────────────────────────────────── */} +
+
+
+
+ +
+
+

Smart CRM

+

Customer Intelligence

+
+
+ +
+ + {/* Search */} + {!selectedCustomer && ( +
+ + setSearchTerm(e.target.value)} + autoFocus + /> +
+ )} +
+ + {/* ─── Content ─────────────────────────────────────── */} +
+ {!selectedCustomer ? ( + /* ── Customer List ───────────────────────── */ +
+ {/* Walk-in option */} + + +
+

+ Registered Customers ({filteredCustomers.length}) +

+
+ + + {filteredCustomers.map((customer, i) => ( + handleSelectCustomer(customer)} + className={`w-full p-4 rounded-2xl border ${theme === 'light' ? 'border-slate-200 hover:border-violet-300 hover:bg-violet-50/50' : 'border-white/5 hover:border-violet-500/30 hover:bg-violet-500/5'} flex items-center gap-3 transition-all group text-left`} + > +
+ {customer.name.charAt(0).toUpperCase()} +
+
+

{customer.name}

+
+ + {customer.phone} +
+
+
+

+ {customer.loyaltyPoints > 0 ? `${customer.loyaltyPoints} pts` : ''} +

+
+ +
+ ))} +
+ + {filteredCustomers.length === 0 && searchTerm && ( +
+ +

No customers found

+
+ )} +
+ ) : ( + /* ── Customer Profile ────────────────────── */ +
+ {/* Profile Card */} + + {/* Decorative gradient orb */} +
+ +
+
+ {selectedCustomer.name.charAt(0).toUpperCase()} +
+
+

{selectedCustomer.name}

+

{selectedCustomer.phone}

+ {stats && ( +
+ + {tierConfig.icon} + {stats.tier} + + + {selectedCustomer.loyaltyPoints} pts + +
+ )} +
+ +
+ + + {/* Stats Grid */} + {loadingStats ? ( +
+ +
+ ) : stats ? ( + <> + + {[ + { label: 'Total Spend', value: `LKR ${Math.round(stats.totalSpend).toLocaleString()}`, icon: , color: 'text-emerald-500', bg: theme === 'light' ? 'bg-emerald-50' : 'bg-emerald-500/10' }, + { label: 'Avg Order', value: `LKR ${Math.round(stats.avgOrderValue).toLocaleString()}`, icon: , color: 'text-blue-400', bg: theme === 'light' ? 'bg-blue-50' : 'bg-blue-500/10' }, + { label: 'Total Visits', value: stats.visitCount.toString(), icon: , color: 'text-amber-400', bg: theme === 'light' ? 'bg-amber-50' : 'bg-amber-500/10' }, + { label: 'Last Visit', value: getTimeAgo(stats.lastVisit), icon: , color: 'text-violet-400', bg: theme === 'light' ? 'bg-violet-50' : 'bg-violet-500/10' }, + ].map((stat, i) => ( + +
{stat.icon}
+

{stat.label}

+

{stat.value}

+
+ ))} +
+ + {/* ── Usual Orders / Predictive Section ──── */} + {recommendations.length > 0 && ( + +
+
+ +
+
+

Usual Orders

+

One-tap to add their favorites

+
+
+ +
+ + {recommendations.map((rec, i) => { + const isAdded = addedItems.has(rec._id); + return ( + !isAdded && handleAddItem(rec)} + disabled={isAdded} + className={`w-full p-4 rounded-2xl flex items-center gap-3 transition-all text-left group ${isAdded + ? `${theme === 'light' ? 'bg-emerald-50 border border-emerald-200' : 'bg-emerald-500/10 border border-emerald-500/20'}` + : `${theme === 'light' ? 'bg-white border border-slate-200 hover:border-violet-300 hover:shadow-md hover:shadow-violet-100' : 'bg-white/[0.03] border border-white/5 hover:border-violet-500/30 hover:bg-violet-500/5'}` + }`} + > + {/* Rank */} +
+ #{i + 1} +
+ + {/* Details */} +
+

+ {rec.name} +

+
+ + Ordered {rec.count}× ({rec.totalQty} units) + +
+
+ + {/* Price + Action */} +
+

+ {isAdded ? '✓ Added' : `LKR ${rec.price.toLocaleString()}`} +

+
+ + {!isAdded && ( + + + + )} +
+ ); + })} +
+
+
+ )} + + {recommendations.length === 0 && stats.visitCount === 0 && ( + + +

First-time Customer

+

No purchase history yet. Complete this sale to start building their profile!

+
+ )} + + {/* ── Recent Orders ──────────────────────── */} + {stats.recentOrders && stats.recentOrders.length > 0 && ( + +

+ Recent Transactions +

+
+ {stats.recentOrders.map((order: any, i: number) => ( + +
+ +
+
+

+ {order.items?.map((it: any) => it.name).join(', ') || 'Order'} +

+

+ {order.date ? new Date(order.date).toLocaleDateString() : ''} +

+
+

+ LKR {Math.round(order.grandTotal || 0).toLocaleString()} +

+
+ ))} +
+
+ )} + + ) : null} +
+ )} +
+ + {/* ─── Footer ──────────────────────────────────────── */} + {selectedCustomer && ( + + + + )} + + + )} + + ); +}; + +export default CustomerCRMPanel; diff --git a/ApexPOS Restaurent/client/src/pages/RetailPOS.tsx b/ApexPOS Restaurent/client/src/pages/RetailPOS.tsx index 3524dca..8465e4d 100644 --- a/ApexPOS Restaurent/client/src/pages/RetailPOS.tsx +++ b/ApexPOS Restaurent/client/src/pages/RetailPOS.tsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; -import { Search, ShoppingCart, Trash2, Plus, Minus, Tag, Zap, AlertCircle } from 'lucide-react'; +import { Search, ShoppingCart, Trash2, Plus, Minus, Tag, Zap, AlertCircle, UserCircle, X } from 'lucide-react'; import { useStore, Product } from '../store/useStore'; import { motion, AnimatePresence } from 'framer-motion'; import CheckoutModal from '../components/pos/CheckoutModal'; +import CustomerCRMPanel from '../components/pos/CustomerCRMPanel'; import api from '../api/axios'; import { Link } from 'react-router-dom'; @@ -18,6 +19,8 @@ const RetailPOS = () => { const [categories, setCategories] = useState(['All']); const [discount, setDiscount] = useState(0); const [isCheckoutOpen, setIsCheckoutOpen] = useState(false); + const [isCRMOpen, setIsCRMOpen] = useState(false); + const [selectedCustomer, setSelectedCustomer] = useState(null); @@ -148,6 +151,7 @@ const RetailPOS = () => { payments: payments.map(p => ({ method: p.method, amount: Number(p.amount), reference: p.reference })), cashierName: user?.name || 'Cashier', branchId: user?.branch_id || 'HQ', + customerId: selectedCustomer?._id || undefined, date: new Date().toISOString(), }; @@ -290,6 +294,32 @@ const RetailPOS = () => {
+ {/* Customer CRM Badge */} +
+ {selectedCustomer ? ( +
+
+ {selectedCustomer.name.charAt(0).toUpperCase()} +
+
+

{selectedCustomer.name}

+

{selectedCustomer.loyaltyPoints || 0} loyalty pts

+
+ +
+ ) : ( + + )} +
+ {/* Cart Items */}
@@ -424,7 +454,34 @@ const RetailPOS = () => { cashierName={user?.name || 'Cashier'} settings={settings} onSaleComplete={handleCompleteSale} - onClear={() => { clearCart(); setDiscount(0); }} + onClear={() => { clearCart(); setDiscount(0); setSelectedCustomer(null); }} + /> + + {/* ── Customer CRM Panel ──────────────────────────────────────── */} + setIsCRMOpen(false)} + selectedCustomer={selectedCustomer} + onSelectCustomer={(customer) => { + setSelectedCustomer(customer); + if (customer) setIsCRMOpen(false); + }} + onAddRecommendation={(productId, name, price) => { + // Try to find the product in loaded products for full data + const product = products.find(p => p._id === productId); + if (product) { + addToCart(product); + } else { + // Fallback: add with minimal data + addToCart({ + _id: productId, + name, + price, + category: '', + stock: 999, + }); + } + }} /> diff --git a/ApexPOS Restaurent/server/controllers/registrationController.js b/ApexPOS Restaurent/server/controllers/registrationController.js index e46bbc2..8866704 100644 --- a/ApexPOS Restaurent/server/controllers/registrationController.js +++ b/ApexPOS Restaurent/server/controllers/registrationController.js @@ -82,6 +82,78 @@ exports.deleteCustomer = async (req, res) => { } }; +exports.getCustomerRecommendations = async (req, res) => { + try { + const { id } = req.params; + const mongoose = require('mongoose'); + const { Sale } = require('../models/AllModels'); + + const recommendations = await Sale.aggregate([ + { $match: { customerId: new mongoose.Types.ObjectId(id) } }, + { $unwind: "$items" }, + { $group: { + _id: "$items.productId", + name: { $first: "$items.name" }, + price: { $first: "$items.price" }, + count: { $sum: 1 }, + totalQty: { $sum: "$items.quantity" }, + lastOrdered: { $max: "$date" } + }}, + { $sort: { count: -1 } }, + { $limit: 5 } + ]); + + res.json(recommendations); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}; + +exports.getCustomerStats = async (req, res) => { + try { + const { id } = req.params; + const mongoose = require('mongoose'); + const { Sale } = require('../models/AllModels'); + + const stats = await Sale.aggregate([ + { $match: { customerId: new mongoose.Types.ObjectId(id) } }, + { $group: { + _id: null, + totalSpend: { $sum: "$grandTotal" }, + visitCount: { $sum: 1 }, + avgOrderValue: { $avg: "$grandTotal" }, + lastVisit: { $max: "$date" }, + firstVisit: { $min: "$date" } + }} + ]); + + const result = stats[0] || { + totalSpend: 0, + visitCount: 0, + avgOrderValue: 0, + lastVisit: null, + firstVisit: null + }; + + // Calculate loyalty tier + let tier = 'New'; + if (result.visitCount >= 50 || result.totalSpend >= 500000) tier = 'Platinum'; + else if (result.visitCount >= 20 || result.totalSpend >= 200000) tier = 'Gold'; + else if (result.visitCount >= 10 || result.totalSpend >= 100000) tier = 'Silver'; + else if (result.visitCount >= 3) tier = 'Bronze'; + + // Get recent orders (last 5) + const recentOrders = await Sale.find({ customerId: new mongoose.Types.ObjectId(id) }) + .sort({ date: -1 }) + .limit(5) + .select('date grandTotal items.name items.quantity'); + + res.json({ ...result, tier, recentOrders }); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}; + // ============ SUPPLIERS ============ exports.getSuppliers = async (req, res) => { try { diff --git a/ApexPOS Restaurent/server/controllers/saleController.js b/ApexPOS Restaurent/server/controllers/saleController.js index 9df439a..0a5f941 100644 --- a/ApexPOS Restaurent/server/controllers/saleController.js +++ b/ApexPOS Restaurent/server/controllers/saleController.js @@ -1,4 +1,4 @@ -const { Sale, Notification, StockMovement, Settings } = require('../models/AllModels'); +const { Sale, Notification, StockMovement, Settings, Customer } = require('../models/AllModels'); const ProductModel = require('../models/Product'); const { calculateSaleTax } = require('../utils/taxEngine'); @@ -7,7 +7,7 @@ const { calculateSaleTax } = require('../utils/taxEngine'); exports.createSale = async (req, res) => { try { console.log("Processing Sale:", req.body); - const { items, discount = 0, payments = [], cashierName = 'Unknown', branchId = 'HQ' } = req.body; + const { items, discount = 0, payments = [], cashierName = 'Unknown', branchId = 'HQ', customerId } = req.body; if (!items || items.length === 0) { return res.status(400).json({ message: "No items provided" }); @@ -50,6 +50,7 @@ exports.createSale = async (req, res) => { // 4. Create Sale Record const sale = new Sale({ + customerId: customerId || undefined, items: enrichedItems, totalAmount: taxes.subtotal, vatAmount: taxes.vatAmount, @@ -66,6 +67,20 @@ exports.createSale = async (req, res) => { await sale.save(); + // Update Customer stats if linked + if (customerId) { + try { + await Customer.findByIdAndUpdate(customerId, { + $inc: { + totalPurchases: taxes.grandTotal, + loyaltyPoints: Math.floor(taxes.grandTotal / 100) // 1 point per 100 LKR + } + }); + } catch (e) { + console.warn('Failed to update customer stats:', e.message); + } + } + // Notify Dashboard via Socket.io const io = req.app.get('io'); if (io) io.emit('dashboardUpdate'); diff --git a/ApexPOS Restaurent/server/models/AllModels.js b/ApexPOS Restaurent/server/models/AllModels.js index a69120f..27a81d2 100644 --- a/ApexPOS Restaurent/server/models/AllModels.js +++ b/ApexPOS Restaurent/server/models/AllModels.js @@ -37,6 +37,7 @@ const hpSchema = new mongoose.Schema({ }, { timestamps: true }); const saleSchema = new mongoose.Schema({ + customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer' }, items: [{ productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' }, name: String, diff --git a/ApexPOS Restaurent/server/routes/registrationRoutes.js b/ApexPOS Restaurent/server/routes/registrationRoutes.js index d782d8c..b711bc6 100644 --- a/ApexPOS Restaurent/server/routes/registrationRoutes.js +++ b/ApexPOS Restaurent/server/routes/registrationRoutes.js @@ -11,6 +11,8 @@ router.delete('/staff/:id', registrationController.deleteStaff); // Customer routes router.get('/customers', registrationController.getCustomers); router.post('/customers', registrationController.createCustomer); +router.get('/customers/:id/recommendations', registrationController.getCustomerRecommendations); +router.get('/customers/:id/stats', registrationController.getCustomerStats); router.patch('/customers/:id', registrationController.updateCustomer); router.delete('/customers/:id', registrationController.deleteCustomer); From f3312363cb067a8b3e8e180eb2f7873d539ecb5b Mon Sep 17 00:00:00 2001 From: Tharusha Bhashitha Date: Mon, 6 Apr 2026 00:25:02 +0530 Subject: [PATCH 2/3] feat: implement Customer CRM panel with intelligent recommendations and analytics endpoints --- ApexPOS Restaurent/server/package-lock.json | 16 +++++++++++++++- ApexPOS Restaurent/server/package.json | 5 +++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ApexPOS Restaurent/server/package-lock.json b/ApexPOS Restaurent/server/package-lock.json index 2ea2873..6764a9d 100644 --- a/ApexPOS Restaurent/server/package-lock.json +++ b/ApexPOS Restaurent/server/package-lock.json @@ -15,7 +15,8 @@ "express": "^5.2.1", "jsonwebtoken": "^9.0.2", "mongoose": "^9.1.5", - "socket.io": "^4.8.3" + "socket.io": "^4.8.3", + "uuid": "^13.0.0" }, "devDependencies": { "nodemon": "^3.1.11" @@ -1705,6 +1706,19 @@ "node": ">= 0.8" } }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/ApexPOS Restaurent/server/package.json b/ApexPOS Restaurent/server/package.json index fc76de5..b0b906c 100644 --- a/ApexPOS Restaurent/server/package.json +++ b/ApexPOS Restaurent/server/package.json @@ -18,9 +18,10 @@ "express": "^5.2.1", "jsonwebtoken": "^9.0.2", "mongoose": "^9.1.5", - "socket.io": "^4.8.3" + "socket.io": "^4.8.3", + "uuid": "^13.0.0" }, "devDependencies": { "nodemon": "^3.1.11" } -} \ No newline at end of file +} From c78e22e057178e4e9b2d120ec60ed9f72dde13d3 Mon Sep 17 00:00:00 2001 From: Tharusha Bhashitha Date: Mon, 6 Apr 2026 00:28:35 +0530 Subject: [PATCH 3/3] add some features --- ApexPOS Restaurent/client/src/App.tsx | 49 +++++++------------ .../client/src/components/layout/Sidebar.tsx | 41 ++++++++-------- 2 files changed, 40 insertions(+), 50 deletions(-) diff --git a/ApexPOS Restaurent/client/src/App.tsx b/ApexPOS Restaurent/client/src/App.tsx index eff37fe..601bd3e 100644 --- a/ApexPOS Restaurent/client/src/App.tsx +++ b/ApexPOS Restaurent/client/src/App.tsx @@ -4,7 +4,6 @@ import Layout from './components/layout/Layout'; import Dashboard from './pages/Dashboard'; import Login from './pages/Login'; import { useStore } from './store/useStore'; -import Inventory from './pages/Inventory'; // Protected Route Component const ProtectedRoute = () => { @@ -12,16 +11,13 @@ const ProtectedRoute = () => { return isAuthenticated ? : ; }; +// ─── Restaurant POS Pages ──────────────────────────────────────────────────── import RetailPOS from './pages/RetailPOS'; +import Inventory from './pages/Inventory'; import CategoryManagement from './pages/CategoryManagement'; import SalesHistory from './pages/SalesHistory'; -import Delivery from './pages/Delivery'; -import RepairManagement from './pages/RepairManagement'; -import AddJob from './pages/AddJob'; -import Reload from './pages/Reload'; import Registration from './pages/Registration'; import Reports from './pages/Reports'; -import HirePurchase from './pages/HirePurchase'; import Expenses from './pages/Expenses'; import Notifications from './pages/Notifications'; import StaffManagement from './pages/StaffManagement'; @@ -30,47 +26,40 @@ import TableManagement from './pages/TableManagement'; import QROrder from './pages/QROrder'; import KDS from './pages/KDS'; - - -// Placeholder components for other routes -const Placeholder = ({ title }: { title: string }) => ( -
-

{title}

-

Component under construction

-
-); - function App() { return ( + {/* Public Routes */} } /> } /> - } /> + {/* Protected Restaurant POS Routes */} }> }> } /> - } /> + + {/* 🍽️ Operations */} } /> } /> - } /> - } /> + } /> + } /> + + {/* 📋 Management */} + } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + + {/* 💰 Finance */} } /> + } /> + + {/* ⚙️ Admin */} + } /> } /> + } /> } /> - } /> - - diff --git a/ApexPOS Restaurent/client/src/components/layout/Sidebar.tsx b/ApexPOS Restaurent/client/src/components/layout/Sidebar.tsx index b402f4c..4b1b339 100644 --- a/ApexPOS Restaurent/client/src/components/layout/Sidebar.tsx +++ b/ApexPOS Restaurent/client/src/components/layout/Sidebar.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { NavLink, useNavigate } from 'react-router-dom'; import { - LayoutDashboard, ShoppingCart, Package, Truck, History, - Tags, Wrench, Smartphone, Users, PlusCircle, FileText, - Bell, CreditCard, DollarSign, LogOut, ChevronLeft, ChevronRight, - ShieldCheck, Settings, UtensilsCrossed + LayoutDashboard, ShoppingCart, Package, History, + Tags, Users, FileText, + Bell, DollarSign, LogOut, ChevronLeft, ChevronRight, + ShieldCheck, Settings, UtensilsCrossed, MonitorPlay, + ChefHat, Heart } from 'lucide-react'; @@ -13,21 +14,20 @@ import { useStore } from '../../store/useStore'; import { motion } from 'framer-motion'; const menuItems = [ - { path: '/', icon: LayoutDashboard, label: 'Dashboard', animation: 'hover-rotate' as const, group: 'Core' }, - { path: '/retail-pos', icon: ShoppingCart, label: 'Retail POS', animation: 'hover-scale' as const, group: 'Core' }, - { path: '/hospitality', icon: UtensilsCrossed, label: 'Hospitality', animation: 'hover-rotate' as const, group: 'Core' }, - { path: '/inventory', icon: Package, label: 'Inventory', animation: 'hover-scale' as const, group: 'Core' }, - - { path: '/categories', icon: Tags, label: 'Categories', animation: 'hover-scale' as const, group: 'Core' }, - { path: '/sales', icon: History, label: 'Sales History', animation: 'hover-rotate' as const, group: 'Core' }, - { path: '/delivery', icon: Truck, label: 'Delivery', animation: 'hover-scale' as const, group: 'Service' }, - { path: '/repairs', icon: Wrench, label: 'Repairs', animation: 'hover-rotate' as const, group: 'Service' }, - { path: '/add-job', icon: PlusCircle, label: 'Add Job', animation: 'hover-rotate' as const, group: 'Service' }, - { path: '/reload', icon: Smartphone, label: 'Reload', animation: 'pulse' as const, group: 'Service' }, - { path: '/hp', icon: CreditCard, label: 'Hire Purchase', animation: 'hover-scale' as const, group: 'Finance' }, + { path: '/', icon: LayoutDashboard, label: 'Dashboard', animation: 'hover-rotate' as const, group: 'Overview' }, + + { path: '/retail-pos', icon: ShoppingCart, label: 'Order POS', animation: 'hover-scale' as const, group: 'Operations' }, + { path: '/hospitality', icon: UtensilsCrossed, label: 'Tables', animation: 'hover-rotate' as const, group: 'Operations' }, + { path: '/kds', icon: MonitorPlay, label: 'Kitchen Display', animation: 'hover-scale' as const, group: 'Operations' }, + + { path: '/inventory', icon: ChefHat, label: 'Menu Items', animation: 'hover-scale' as const, group: 'Management' }, + { path: '/categories', icon: Tags, label: 'Categories', animation: 'hover-scale' as const, group: 'Management' }, + { path: '/sales', icon: History, label: 'Sales History', animation: 'hover-rotate' as const, group: 'Management' }, + { path: '/expenses', icon: DollarSign, label: 'Expenses', animation: 'bounce' as const, group: 'Finance' }, { path: '/reports', icon: FileText, label: 'Reports', animation: 'hover-scale' as const, group: 'Finance' }, - { path: '/registration', icon: Users, label: 'Registration', animation: 'hover-scale' as const, group: 'Admin' }, + + { path: '/registration', icon: Heart, label: 'Customers', animation: 'hover-scale' as const, group: 'Admin' }, { path: '/staff', icon: ShieldCheck, label: 'Staff & Auth', animation: 'hover-scale' as const, group: 'Admin' }, { path: '/notifications', icon: Bell, label: 'Notifications', animation: 'pulse' as const, group: 'Admin' }, { path: '/settings', icon: Settings, label: 'Settings', animation: 'hover-rotate' as const, group: 'Admin' }, @@ -35,8 +35,9 @@ const menuItems = [ const groupLabels: Record = { - Core: '🏪 Core', - Service: '🔧 Service', + Overview: '📊 Overview', + Operations: '🍽️ Operations', + Management: '📋 Management', Finance: '💰 Finance', Admin: '⚙️ Admin', }; @@ -49,7 +50,7 @@ const Sidebar = () => { const initials = user?.name?.split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase() || 'A'; // Group menu items - const groups = ['Core', 'Service', 'Finance', 'Admin']; + const groups = ['Overview', 'Operations', 'Management', 'Finance', 'Admin']; return (