import React, { useState, useEffect, useRef, createContext, useContext } from 'react';
import {
Menu, X, ShoppingCart, Package, BarChart2, User, Users, Settings, LogOut,
Plus, Minus, Trash2, Edit, Eye, EyeOff, Search, Filter, Download, Printer,
Save, AlertTriangle, Clock, DollarSign, CreditCard, FileText, Calendar,
ChevronRight, ChevronDown, ChevronLeft, Check, RefreshCw, Lock, Unlock,
ArrowRight, ArrowLeft, Home, Bell, Moon, Sun, HelpCircle
} from 'lucide-react';
// Firebase imports would go here in a real implementation
// import { initializeApp } from 'firebase/app';
// import { getAuth, signInWithEmailAndPassword, signOut, onAuthStateChanged } from 'firebase/auth';
// import { getDatabase, ref, set, get, update, remove, onValue, push } from 'firebase/database';
// import { getStorage, ref as storageRef, uploadBytes, getDownloadURL } from 'firebase/storage';
// Mock Firebase for demo purposes
const mockFirebase = {
auth: {
currentUser: null,
signInWithEmailAndPassword: (email, password) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (email === 'admin@example.com' && password === 'admin123') {
mockFirebase.auth.currentUser = { uid: '1', email, displayName: 'Admin User', role: 'admin' };
resolve({ user: mockFirebase.auth.currentUser });
} else if (email === 'cashier@example.com' && password === 'cashier123') {
mockFirebase.auth.currentUser = { uid: '2', email, displayName: 'Cashier User', role: 'cashier' };
resolve({ user: mockFirebase.auth.currentUser });
} else if (email === 'manager@example.com' && password === 'manager123') {
mockFirebase.auth.currentUser = { uid: '3', email, displayName: 'Manager User', role: 'manager' };
resolve({ user: mockFirebase.auth.currentUser });
} else {
reject(new Error('Invalid email or password'));
}
}, 1000);
});
},
signOut: () => {
return new Promise((resolve) => {
setTimeout(() => {
mockFirebase.auth.currentUser = null;
resolve();
}, 500);
});
},
onAuthStateChanged: (callback) => {
callback(mockFirebase.auth.currentUser);
return () => {};
}
},
database: {
data: {
products: [
{ id: '1', name: 'T-Shirt', price: 19.99, category: 'Clothing', stock: 45, barcode: '123456789', description: 'Cotton t-shirt', expires: false },
{ id: '2', name: 'Jeans', price: 49.99, category: 'Clothing', stock: 30, barcode: '223456789', description: 'Blue denim jeans', expires: false },
{ id: '3', name: 'Milk', price: 3.99, category: 'Dairy', stock: 25, barcode: '323456789', description: 'Fresh milk', expires: true, expiryDate: '2023-12-30' },
{ id: '4', name: 'Bread', price: 2.99, category: 'Bakery', stock: 15, barcode: '423456789', description: 'Whole wheat bread', expires: true, expiryDate: '2023-12-25' },
{ id: '5', name: 'Notebook', price: 4.99, category: 'Stationery', stock: 100, barcode: '523456789', description: 'Lined notebook', expires: false },
{ id: '6', name: 'Yogurt', price: 1.99, category: 'Dairy', stock: 20, barcode: '623456789', description: 'Greek yogurt', expires: true, expiryDate: '2023-12-28' },
{ id: '7', name: 'Water Bottle', price: 14.99, category: 'Accessories', stock: 40, barcode: '723456789', description: 'Stainless steel water bottle', expires: false },
{ id: '8', name: 'Headphones', price: 89.99, category: 'Electronics', stock: 15, barcode: '823456789', description: 'Wireless headphones', expires: false },
],
categories: ['Clothing', 'Dairy', 'Bakery', 'Stationery', 'Accessories', 'Electronics'],
customers: [
{ id: '1', name: 'John Doe', email: 'john@example.com', phone: '123-456-7890', address: '123 Main St', creditLimit: 500, creditUsed: 200 },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', phone: '234-567-8901', address: '456 Oak Ave', creditLimit: 1000, creditUsed: 750 },
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com', phone: '345-678-9012', address: '789 Pine Rd', creditLimit: 300, creditUsed: 0 },
{ id: '4', name: 'Alice Brown', email: 'alice@example.com', phone: '456-789-0123', address: '321 Cedar Ln', creditLimit: 800, creditUsed: 400 },
],
staff: [
{ id: '1', name: 'Admin User', email: 'admin@example.com', role: 'admin', phone: '123-456-7890' },
{ id: '2', name: 'Cashier User', email: 'cashier@example.com', role: 'cashier', phone: '234-567-8901' },
{ id: '3', name: 'Manager User', email: 'manager@example.com', role: 'manager', phone: '345-678-9012' },
],
sales: [
{
id: '1',
date: '2023-12-15T10:30:00',
items: [
{ productId: '1', name: 'T-Shirt', price: 19.99, quantity: 2 },
{ productId: '5', name: 'Notebook', price: 4.99, quantity: 1 }
],
subtotal: 44.97,
tax: 3.15,
total: 48.12,
paymentMethod: 'cash',
customerId: '1',
customerName: 'John Doe',
staffId: '2',
staffName: 'Cashier User',
status: 'completed'
},
{
id: '2',
date: '2023-12-16T14:45:00',
items: [
{ productId: '3', name: 'Milk', price: 3.99, quantity: 2 },
{ productId: '4', name: 'Bread', price: 2.99, quantity: 1 },
{ productId: '6', name: 'Yogurt', price: 1.99, quantity: 3 }
],
subtotal: 18.94,
tax: 1.33,
total: 20.27,
paymentMethod: 'credit',
customerId: '2',
customerName: 'Jane Smith',
staffId: '2',
staffName: 'Cashier User',
status: 'completed'
},
{
id: '3',
date: '2023-12-17T16:20:00',
items: [
{ productId: '8', name: 'Headphones', price: 89.99, quantity: 1 }
],
subtotal: 89.99,
tax: 6.30,
total: 96.29,
paymentMethod: 'credit_account',
customerId: '2',
customerName: 'Jane Smith',
staffId: '3',
staffName: 'Manager User',
status: 'completed'
},
],
settings: {
businessName: 'My POS Store',
address: '123 Business St, City, State, ZIP',
phone: '(123) 456-7890',
email: 'contact@myposstore.com',
taxRate: 7,
currency: 'USD',
receiptFooter: 'Thank you for your business!',
lowStockThreshold: 10,
theme: 'light'
}
},
ref: (path) => ({
path,
set: (data) => {
return new Promise((resolve) => {
setTimeout(() => {
const pathParts = path.split('/');
let current = mockFirebase.database.data;
for (let i = 0; i < pathParts.length - 1; i++) {
if (!current[pathParts[i]]) {
current[pathParts[i]] = {};
}
current = current[pathParts[i]];
}
current[pathParts[pathParts.length - 1]] = data;
resolve();
}, 500);
});
},
update: (data) => {
return new Promise((resolve) => {
setTimeout(() => {
const pathParts = path.split('/');
let current = mockFirebase.database.data;
for (let i = 0; i < pathParts.length - 1; i++) {
if (!current[pathParts[i]]) {
current[pathParts[i]] = {};
}
current = current[pathParts[i]];
}
if (!current[pathParts[pathParts.length - 1]]) {
current[pathParts[pathParts.length - 1]] = {};
}
Object.assign(current[pathParts[pathParts.length - 1]], data);
resolve();
}, 500);
});
},
push: () => {
const newId = Math.random().toString(36).substring(2, 15);
return {
key: newId,
set: (data) => {
return new Promise((resolve) => {
setTimeout(() => {
const pathParts = path.split('/');
let current = mockFirebase.database.data;
for (let i = 0; i < pathParts.length; i++) {
if (!current[pathParts[i]]) {
current[pathParts[i]] = {};
}
current = current[pathParts[i]];
}
if (!Array.isArray(current)) {
current = mockFirebase.database.data[pathParts[0]] = [];
}
current.push({ ...data, id: newId });
resolve();
}, 500);
});
}
};
},
remove: () => {
return new Promise((resolve) => {
setTimeout(() => {
const pathParts = path.split('/');
let current = mockFirebase.database.data;
for (let i = 0; i < pathParts.length - 1; i++) {
if (!current[pathParts[i]]) {
return resolve();
}
current = current[pathParts[i]];
}
if (pathParts.length === 1) {
delete mockFirebase.database.data[pathParts[0]];
} else {
delete current[pathParts[pathParts.length - 1]];
}
resolve();
}, 500);
});
},
onValue: (callback) => {
setTimeout(() => {
const pathParts = path.split('/');
let data = mockFirebase.database.data;
for (let i = 0; i < pathParts.length; i++) {
if (!data[pathParts[i]]) {
data = null;
break;
}
data = data[pathParts[i]];
}
callback({ val: () => data });
}, 500);
return () => {};
},
get: () => {
return new Promise((resolve) => {
setTimeout(() => {
const pathParts = path.split('/');
let data = mockFirebase.database.data;
for (let i = 0; i < pathParts.length; i++) {
if (!data[pathParts[i]]) {
data = null;
break;
}
data = data[pathParts[i]];
}
resolve({ val: () => data });
}, 500);
});
}
})
}
};
// Create context for global state
const AppContext = createContext();
// Main POS Application
export default function POSSystem() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isDemo, setIsDemo] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const [activeView, setActiveView] = useState('dashboard');
const [darkMode, setDarkMode] = useState(false);
const [settings, setSettings] = useState(null);
const [notification, setNotification] = useState(null);
// Initialize app and check authentication
useEffect(() => {
// Check for existing session
const savedUser = localStorage.getItem('posUser');
const savedIsDemo = localStorage.getItem('posIsDemo');
if (savedUser) {
setUser(JSON.parse(savedUser));
setIsAuthenticated(true);
setIsDemo(savedIsDemo === 'true');
}
// Load settings
loadSettings();
setIsLoading(false);
}, []);
// Load settings from Firebase
const loadSettings = async () => {
try {
const settingsRef = mockFirebase.database.ref('settings');
const snapshot = await settingsRef.get();
const settingsData = snapshot.val();
if (settingsData) {
setSettings(settingsData);
setDarkMode(settingsData.theme === 'dark');
}
} catch (error) {
console.error('Error loading settings:', error);
}
};
// Handle login
const handleLogin = (userData, demo = false) => {
setUser(userData);
setIsAuthenticated(true);
setIsDemo(demo);
// Save to localStorage
localStorage.setItem('posUser', JSON.stringify(userData));
localStorage.setItem('posIsDemo', demo.toString());
// Show welcome notification
showNotification(`Welcome, ${userData.displayName || userData.email}!`, 'success');
};
// Handle logout
const handleLogout = async () => {
try {
await mockFirebase.auth.signOut();
setUser(null);
setIsAuthenticated(false);
// Clear from localStorage
localStorage.removeItem('posUser');
localStorage.removeItem('posIsDemo');
showNotification('You have been logged out successfully', 'info');
} catch (error) {
console.error('Error signing out:', error);
showNotification('Error signing out', 'error');
}
};
// Show notification
const showNotification = (message, type = 'info', duration = 3000) => {
setNotification({ message, type });
if (duration) {
setTimeout(() => {
setNotification(null);
}, duration);
}
};
// Toggle dark mode
const toggleDarkMode = async () => {
const newMode = !darkMode;
setDarkMode(newMode);
// Update settings in Firebase
if (settings) {
const updatedSettings = { ...settings, theme: newMode ? 'dark' : 'light' };
setSettings(updatedSettings);
try {
await mockFirebase.database.ref('settings').update(updatedSettings);
} catch (error) {
console.error('Error updating theme setting:', error);
}
}
};
// Context value
const contextValue = {
user,
isDemo,
settings,
darkMode,
showNotification,
toggleDarkMode
};
if (isLoading) {
return (
);
}
if (!isAuthenticated) {
return ;
}
return (
{/* Header */}
{/* Main Content with Slide Menu */}
{/* Slide Menu */}
}
label="Dashboard"
onClick={() => { setActiveView('dashboard'); setMenuOpen(false); }}
active={activeView === 'dashboard'}
collapsed={!menuOpen}
/>
}
label="Point of Sale"
onClick={() => { setActiveView('pos'); setMenuOpen(false); }}
active={activeView === 'pos'}
collapsed={!menuOpen}
/>
}
label="Products"
onClick={() => { setActiveView('products'); setMenuOpen(false); }}
active={activeView === 'products'}
collapsed={!menuOpen}
/>
}
label="Customers"
onClick={() => { setActiveView('customers'); setMenuOpen(false); }}
active={activeView === 'customers'}
collapsed={!menuOpen}
/>
}
label="Invoices"
onClick={() => { setActiveView('invoices'); setMenuOpen(false); }}
active={activeView === 'invoices'}
collapsed={!menuOpen}
/>
}
label="Reports"
onClick={() => { setActiveView('reports'); setMenuOpen(false); }}
active={activeView === 'reports'}
collapsed={!menuOpen}
/>
}
label="Staff"
onClick={() => { setActiveView('staff'); setMenuOpen(false); }}
active={activeView === 'staff'}
collapsed={!menuOpen}
disabled={user.role !== 'admin'}
/>
}
label="Settings"
onClick={() => { setActiveView('settings'); setMenuOpen(false); }}
active={activeView === 'settings'}
collapsed={!menuOpen}
disabled={user.role !== 'admin' && user.role !== 'manager'}
/>
}
label="Help"
onClick={() => { setActiveView('help'); setMenuOpen(false); }}
active={activeView === 'help'}
collapsed={!menuOpen}
/>
{menuOpen && (
Version 1.0.0
© 2023 POS System
)}
{/* Overlay for mobile menu */}
{menuOpen && (
setMenuOpen(false)}
>
)}
{/* Main Content Area */}
{activeView === 'dashboard' &&
}
{activeView === 'pos' &&
}
{activeView === 'products' &&
}
{activeView === 'customers' &&
}
{activeView === 'invoices' &&
}
{activeView === 'reports' &&
}
{activeView === 'staff' &&
}
{activeView === 'settings' &&
}
{activeView === 'help' &&
}
{activeView === 'notifications' &&
}
{/* Notification Toast */}
{notification && (
{notification.type === 'success' &&
}
{notification.type === 'error' &&
}
{notification.type === 'warning' &&
}
{notification.type === 'info' &&
}
{notification.message}
setNotification(null)}
className="ml-auto p-1 hover:bg-white hover:bg-opacity-20 rounded-full"
>
)}
);
}
// Menu Item Component
function MenuItem({ icon, label, onClick, active, collapsed, disabled = false }) {
const { darkMode } = useContext(AppContext);
return (
{icon}
{!collapsed && {label} }
);
}
// Login Screen Component
function LoginScreen({ onLogin }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
// Simple validation
if (!email || !password) {
setError('Please enter both email and password');
return;
}
setIsLoading(true);
setError('');
try {
// Sign in with Firebase Auth
const result = await mockFirebase.auth.signInWithEmailAndPassword(email, password);
onLogin(result.user, false);
} catch (error) {
console.error('Error signing in:', error);
setError(error.message);
} finally {
setIsLoading(false);
}
};
const handleDemoLogin = async () => {
setIsLoading(true);
try {
// Use admin credentials for demo
const result = await mockFirebase.auth.signInWithEmailAndPassword('admin@example.com', 'admin123');
onLogin(result.user, true);
} catch (error) {
console.error('Error signing in to demo:', error);
setError(error.message);
} finally {
setIsLoading(false);
}
};
return (
POS System Login
Enter your credentials to access the system
);
}
// Dashboard View
function DashboardView() {
const { darkMode, isDemo, settings } = useContext(AppContext);
const [salesData, setSalesData] = useState([]);
const [products, setProducts] = useState([]);
const [customers, setCustomers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const loadDashboardData = async () => {
try {
// Fetch sales data
const salesRef = mockFirebase.database.ref('sales');
const salesSnapshot = await salesRef.get();
const salesData = salesSnapshot.val() || [];
setSalesData(salesData);
// Fetch products
const productsRef = mockFirebase.database.ref('products');
const productsSnapshot = await productsRef.get();
const productsData = productsSnapshot.val() || [];
setProducts(productsData);
// Fetch customers
const customersRef = mockFirebase.database.ref('customers');
const customersSnapshot = await customersRef.get();
const customersData = customersSnapshot.val() || [];
setCustomers(customersData);
setIsLoading(false);
} catch (error) {
console.error('Error loading dashboard data:', error);
setIsLoading(false);
}
};
loadDashboardData();
}, []);
// Calculate dashboard metrics
const calculateDailyTotal = () => {
const today = new Date().toISOString().split('T')[0];
return salesData
.filter(sale => sale.date.startsWith(today))
.reduce((sum, sale) => sum + sale.total, 0);
};
const calculateTotalSales = () => {
return salesData.reduce((sum, sale) => sum + sale.total, 0);
};
const calculateAverageOrderValue = () => {
if (salesData.length === 0) return 0;
return calculateTotalSales() / salesData.length;
};
const calculateLowStockItems = () => {
const threshold = settings?.lowStockThreshold || 10;
return products.filter(product => product.stock < threshold).length;
};
const calculateTotalCustomers = () => {
return customers.length;
};
const calculateTotalCredit = () => {
return customers.reduce((sum, customer) => sum + (customer.creditUsed || 0), 0);
};
const calculateExpiringItems = () => {
const today = new Date();
const nextWeek = new Date();
nextWeek.setDate(today.getDate() + 7);
return products.filter(product => {
if (!product.expires || !product.expiryDate) return false;
const expiryDate = new Date(product.expiryDate);
return expiryDate > today && expiryDate <= nextWeek;
}).length;
};
if (isLoading) {
return (
Loading dashboard data...
);
}
return (
Dashboard
Welcome back! Here's an overview of your business.
{/* Dashboard Cards */}
}
color="indigo"
/>
}
color="green"
/>
}
color={calculateLowStockItems() > 0 ? "amber" : "green"}
/>
}
color="blue"
/>
{/* Recent Sales */}
Recent Sales
{salesData.length === 0 ? (
No sales data available yet
) : (
Date
Customer
Items
Total
{salesData.slice(0, 5).map(sale => (
{new Date(sale.date).toLocaleDateString()}
{sale.customerName || 'Walk-in'}
{sale.items.length}
${sale.total.toFixed(2)}
))}
)}
View All Sales
{/* Low Stock Items */}
Low Stock Items
{products.filter(product => product.stock < (settings?.lowStockThreshold || 10)).length === 0 ? (
) : (
Name
Category
Current Stock
Status
{products
.filter(product => product.stock < (settings?.lowStockThreshold || 10))
.slice(0, 5)
.map(product => (
{product.name}
{product.category}
{product.stock}
5
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'
}`}>
{product.stock > 5 ? 'Low Stock' : 'Critical'}
))}
)}
View All Low Stock
{/* Credit Alerts */}
Credit Alerts
{customers.filter(customer => (customer.creditUsed || 0) > (customer.creditLimit * 0.8)).length === 0 ? (
) : (
Customer
Credit Used
Credit Limit
Status
{customers
.filter(customer => (customer.creditUsed || 0) > (customer.creditLimit * 0.8))
.slice(0, 5)
.map(customer => (
{customer.name}
${customer.creditUsed.toFixed(2)}
${customer.creditLimit.toFixed(2)}
= customer.creditLimit
? 'bg-red-100 text-red-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
{customer.creditUsed >= customer.creditLimit ? 'Limit Reached' : 'Near Limit'}
))}
)}
View All Credit Alerts
{/* Expiring Products */}
Expiring Products
{products.filter(product => {
if (!product.expires || !product.expiryDate) return false;
const expiryDate = new Date(product.expiryDate);
const today = new Date();
const nextWeek = new Date();
nextWeek.setDate(today.getDate() + 7);
return expiryDate > today && expiryDate <= nextWeek;
}).length === 0 ? (
No products expiring soon
) : (
Name
Category
Expiry Date
Status
{products
.filter(product => {
if (!product.expires || !product.expiryDate) return false;
const expiryDate = new Date(product.expiryDate);
const today = new Date();
const nextWeek = new Date();
nextWeek.setDate(today.getDate() + 7);
return expiryDate > today && expiryDate <= nextWeek;
})
.slice(0, 5)
.map(product => {
const expiryDate = new Date(product.expiryDate);
const today = new Date();
const daysUntilExpiry = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));
return (
{product.name}
{product.category}
{new Date(product.expiryDate).toLocaleDateString()}
{daysUntilExpiry <= 3 ? 'Critical' : `${daysUntilExpiry} days left`}
);
})}
)}
View All Expiring Products
);
}
// Dashboard Card Component
function DashboardCard({ title, value, icon, color }) {
const { darkMode } = useContext(AppContext);
const getColorClasses = () => {
if (darkMode) {
switch (color) {
case 'indigo': return 'bg-indigo-900 text-indigo-200';
case 'green': return 'bg-green-900 text-green-200';
case 'amber': return 'bg-amber-900 text-amber-200';
case 'blue': return 'bg-blue-900 text-blue-200';
case 'red': return 'bg-red-900 text-red-200';
default: return 'bg-gray-800 text-gray-200';
}
} else {
switch (color) {
case 'indigo': return 'bg-indigo-100 text-indigo-600';
case 'green': return 'bg-green-100 text-green-600';
case 'amber': return 'bg-amber-100 text-amber-600';
case 'blue': return 'bg-blue-100 text-blue-600';
case 'red': return 'bg-red-100 text-red-600';
default: return 'bg-gray-100 text-gray-600';
}
}
};
return (
);
}
// POS View
function POSView() {
const { darkMode, isDemo, settings, showNotification } = useContext(AppContext);
const [products, setProducts] = useState([]);
const [categories, setCategories] = useState([]);
const [customers, setCustomers] = useState([]);
const [cart, setCart] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [showCheckoutModal, setShowCheckoutModal] = useState(false);
const [paymentMethod, setPaymentMethod] = useState('cash');
const [amountReceived, setAmountReceived] = useState('');
const [selectedCustomer, setSelectedCustomer] = useState(null);
const [showCustomerModal, setShowCustomerModal] = useState(false);
const [showReceiptModal, setShowReceiptModal] = useState(false);
const [currentReceipt, setCurrentReceipt] = useState(null);
const [barcodeInput, setBarcodeInput] = useState('');
const barcodeInputRef = useRef(null);
useEffect(() => {
const loadPOSData = async () => {
try {
// Fetch products
const productsRef = mockFirebase.database.ref('products');
const productsSnapshot = await productsRef.get();
const productsData = productsSnapshot.val() || [];
setProducts(productsData);
// Fetch categories
const categoriesRef = mockFirebase.database.ref('categories');
const categoriesSnapshot = await categoriesRef.get();
const categoriesData = categoriesSnapshot.val() || [];
setCategories(categoriesData);
// Fetch customers
const customersRef = mockFirebase.database.ref('customers');
const customersSnapshot = await customersRef.get();
const customersData = customersSnapshot.val() || [];
setCustomers(customersData);
setIsLoading(false);
} catch (error) {
console.error('Error loading POS data:', error);
setIsLoading(false);
}
};
loadPOSData();
}, []);
// Focus on barcode input when component mounts
useEffect(() => {
if (barcodeInputRef.current) {
barcodeInputRef.current.focus();
}
}, []);
// Handle barcode input
const handleBarcodeSubmit = (e) => {
e.preventDefault();
if (!barcodeInput) return;
const product = products.find(p => p.barcode === barcodeInput);
if (product) {
addToCart(product);
setBarcodeInput('');
} else {
showNotification(`Product with barcode ${barcodeInput} not found`, 'error');
setBarcodeInput('');
}
};
// Add product to cart
const addToCart = (product) => {
if (product.stock <= 0) {
showNotification(`${product.name} is out of stock`, 'error');
return;
}
const existingItem = cart.find(item => item.id === product.id);
if (existingItem) {
if (existingItem.quantity >= product.stock) {
showNotification(`Cannot add more ${product.name}. Only ${product.stock} in stock.`, 'warning');
return;
}
setCart(cart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
));
} else {
setCart([...cart, {
id: product.id,
name: product.name,
price: product.price,
quantity: 1,
stock: product.stock
}]);
}
showNotification(`${product.name} added to cart`, 'success');
};
// Update cart item quantity
const updateCartItemQuantity = (productId, newQuantity) => {
const product = products.find(p => p.id === productId);
if (newQuantity <= 0) {
removeFromCart(productId);
return;
}
if (newQuantity > product.stock) {
showNotification(`Cannot add more ${product.name}. Only ${product.stock} in stock.`, 'warning');
return;
}
setCart(cart.map(item =>
item.id === productId
? { ...item, quantity: newQuantity }
: item
));
};
// Remove item from cart
const removeFromCart = (productId) => {
setCart(cart.filter(item => item.id !== productId));
};
// Clear cart
const clearCart = () => {
if (cart.length === 0) return;
if (confirm('Are you sure you want to clear the cart?')) {
setCart([]);
setSelectedCustomer(null);
showNotification('Cart cleared', 'info');
}
};
// Calculate cart subtotal
const calculateSubtotal = () => {
return cart.reduce((total, item) => total + (item.price * item.quantity), 0);
};
// Calculate tax
const calculateTax = () => {
const taxRate = settings?.taxRate || 7;
return calculateSubtotal() * (taxRate / 100);
};
// Calculate total
const calculateTotal = () => {
return calculateSubtotal() + calculateTax();
};
// Process checkout
const processCheckout = async () => {
if (cart.length === 0) return;
try {
const subtotal = calculateSubtotal();
const tax = calculateTax();
const total = calculateTotal();
const change = paymentMethod === 'cash' ? parseFloat(amountReceived) - total : 0;
// Check if customer has enough credit limit for credit_account payment
if (paymentMethod === 'credit_account') {
if (!selectedCustomer) {
showNotification('Please select a customer for credit account payment', 'error');
return;
}
const customer = customers.find(c => c.id === selectedCustomer.id);
const newCreditUsed = (customer.creditUsed || 0) + total;
if (newCreditUsed > customer.creditLimit) {
showNotification(`This purchase exceeds ${customer.name}'s credit limit`, 'error');
return;
}
}
// Create receipt
const receipt = {
id: Date.now().toString(),
date: new Date().toISOString(),
items: cart.map(item => ({
productId: item.id,
name: item.name,
price: item.price,
quantity: item.quantity
})),
subtotal,
tax,
total,
paymentMethod,
amountReceived: paymentMethod === 'cash' ? parseFloat(amountReceived) : total,
change,
customerId: selectedCustomer?.id || null,
customerName: selectedCustomer?.name || 'Walk-in Customer',
staffId: mockFirebase.auth.currentUser.uid,
staffName: mockFirebase.auth.currentUser.displayName || mockFirebase.auth.currentUser.email,
status: 'completed'
};
// Update inventory and sales
if (isDemo) {
// Update demo data in memory only
updateDemoData(receipt);
} else {
// Update real data in Firebase
await updateRealData(receipt);
}
// Show receipt
setCurrentReceipt(receipt);
setShowReceiptModal(true);
setShowCheckoutModal(false);
// Clear cart and reset
setCart([]);
setAmountReceived('');
setSelectedCustomer(null);
showNotification('Sale completed successfully', 'success');
} catch (error) {
console.error('Error processing checkout:', error);
showNotification('Error processing checkout', 'error');
}
};
// Update demo data
const updateDemoData = (receipt) => {
// Update products stock
const updatedProducts = products.map(product => {
const cartItem = cart.find(item => item.id === product.id);
if (cartItem) {
return { ...product, stock: product.stock - cartItem.quantity };
}
return product;
});
setProducts(updatedProducts);
// Update customer credit if payment method is credit_account
if (receipt.paymentMethod === 'credit_account' && receipt.customerId) {
const updatedCustomers = customers.map(customer => {
if (customer.id === receipt.customerId) {
return {
...customer,
creditUsed: (customer.creditUsed || 0) + receipt.total
};
}
return customer;
});
setCustomers(updatedCustomers);
}
};
// Update real data in Firebase
const updateRealData = async (receipt) => {
try {
// Add sale to Firebase
const salesRef = mockFirebase.database.ref('sales');
await salesRef.push().set(receipt);
// Update product stock
for (const item of cart) {
const productRef = mockFirebase.database.ref(`products/${item.id}`);
const productSnapshot = await productRef.get();
const product = productSnapshot.val();
if (product) {
await productRef.update({
stock: product.stock - item.quantity
});
}
}
// Update customer credit if payment method is credit_account
if (receipt.paymentMethod === 'credit_account' && receipt.customerId) {
const customerRef = mockFirebase.database.ref(`customers/${receipt.customerId}`);
const customerSnapshot = await customerRef.get();
const customer = customerSnapshot.val();
if (customer) {
await customerRef.update({
creditUsed: (customer.creditUsed || 0) + receipt.total
});
}
}
} catch (error) {
console.error('Error updating data in Firebase:', error);
throw error;
}
};
// Filter products based on search and category
const filteredProducts = products.filter(product => {
const matchesSearch =
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(product.barcode && product.barcode.includes(searchQuery));
const matchesCategory = selectedCategory === 'all' || product.category === selectedCategory;
return matchesSearch && matchesCategory;
});
if (isLoading) {
return (
);
}
return (
Point of Sale
Process sales and manage transactions.
{/* Product Catalog */}
{/* Search and Filter */}
{/* Products Grid */}
{filteredProducts.length === 0 ? (
) : (
{filteredProducts.map(product => (
addToCart(product)}
>
{product.name}
${product.price.toFixed(2)}
{product.category}
10
? darkMode ? 'text-green-400' : 'text-green-600'
: darkMode ? 'text-red-400' : 'text-red-600'
}`}>
Stock: {product.stock}
{
e.stopPropagation();
addToCart(product);
}}
disabled={product.stock <= 0}
className={`px-3 py-1 rounded-lg ${
product.stock > 0
? 'bg-indigo-600 hover:bg-indigo-700 text-white'
: `${darkMode ? 'bg-gray-700 text-gray-500' : 'bg-gray-300 text-gray-500'} cursor-not-allowed`
}`}
>
Add
))}
)}
{/* Shopping Cart */}
Shopping Cart
setShowCustomerModal(true)}
className={`px-3 py-1 rounded-lg ${darkMode ? 'bg-gray-700 hover:bg-gray-600' : 'bg-gray-100 hover:bg-gray-200'} flex items-center`}
>
{selectedCustomer ? selectedCustomer.name : 'Customer'}
0
? 'bg-red-600 hover:bg-red-700 text-white'
: `${darkMode ? 'bg-gray-700 text-gray-500' : 'bg-gray-300 text-gray-500'} cursor-not-allowed`
}`}
>
{cart.length === 0 ? (
) : (
{cart.map(item => (
{item.name}
${item.price.toFixed(2)} each
updateCartItemQuantity(item.id, item.quantity - 1)}
className={`p-1 rounded-full ${darkMode ? 'bg-gray-600 hover:bg-gray-500' : 'bg-gray-200 hover:bg-gray-300'}`}
>
{item.quantity}
updateCartItemQuantity(item.id, item.quantity + 1)}
className={`p-1 rounded-full ${darkMode ? 'bg-gray-600 hover:bg-gray-500' : 'bg-gray-200 hover:bg-gray-300'}`}
disabled={item.quantity >= item.stock}
>
removeFromCart(item.id)}
className={`p-1 rounded-full ${darkMode ? 'bg-red-900 text-red-300 hover:bg-red-800' : 'bg-red-100 text-red-600 hover:bg-red-200'} ml-2`}
>
))}
)}
Subtotal:
${calculateSubtotal().toFixed(2)}
Tax ({settings?.taxRate || 7}%):
${calculateTax().toFixed(2)}
Total:
${calculateTotal().toFixed(2)}
setShowCheckoutModal(true)}
disabled={cart.length === 0}
className={`w-full py-3 rounded-lg ${
cart.length > 0
? 'bg-indigo-600 hover:bg-indigo-700 text-white'
: `${darkMode ? 'bg-gray-700 text-gray-500' : 'bg-gray-300 text-gray-500'} cursor-not-allowed`
}`}
>
Checkout
{/* Customer Selection Modal */}
{showCustomerModal && (
{
setSelectedCustomer(customer);
setShowCustomerModal(false);
}}
onClose={() => setShowCustomerModal(false)}
/>
)}
{/* Checkout Modal */}
{showCheckoutModal && (
setShowCheckoutModal(false)}
/>
)}
{/* Receipt Modal */}
{showReceiptModal && currentReceipt && (
setShowReceiptModal(false)}
/>
)}
);
}
// Customer Selection Modal
function CustomerSelectionModal({ customers, selectedCustomer, onSelectCustomer, onClose }) {
const { darkMode } = useContext(AppContext);
const [searchQuery, setSearchQuery] = useState('');
const filteredCustomers = customers.filter(customer =>
customer.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
customer.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
customer.phone.includes(searchQuery)
);
return (
Select Customer
Name
Contact
Credit
Action
Walk-in Customer
N/A
N/A
onSelectCustomer(null)}
className={`px-3 py-1 rounded-lg ${
selectedCustomer === null
? 'bg-indigo-600 text-white'
: darkMode ? 'bg-gray-700 hover:bg-gray-600 text-white' : 'bg-gray-100 hover:bg-gray-200 text-gray-800'
}`}
>
Select
{filteredCustomers.map(customer => (
{customer.name}
{customer.phone}
{customer.email}
customer.creditLimit * 0.8
? darkMode ? 'text-red-400' : 'text-red-600'
: ''
}`}>
${(customer.creditUsed || 0).toFixed(2)}
{' / '}
${customer.creditLimit.toFixed(2)}
onSelectCustomer(customer)}
className={`px-3 py-1 rounded-lg ${
selectedCustomer?.id === customer.id
? 'bg-indigo-600 text-white'
: darkMode ? 'bg-gray-700 hover:bg-gray-600 text-white' : 'bg-gray-100 hover:bg-gray-200 text-gray-800'
}`}
>
Select
))}
Cancel
);
}
// Checkout Modal
function CheckoutModal({
cart,
subtotal,
tax,
total,
paymentMethod,
setPaymentMethod,
amountReceived,
setAmountReceived,
selectedCustomer,
onCheckout,
onClose
}) {
const { darkMode } = useContext(AppContext);
const handleSubmit = (e) => {
e.preventDefault();
onCheckout();
};
return (
);
}
// Receipt Modal
function ReceiptModal({ receipt, settings, onClose }) {
const { darkMode } = useContext(AppContext);
const receiptRef = useRef(null);
const handlePrint = () => {
const printContent = receiptRef.current.innerHTML;
const originalContent = document.body.innerHTML;
document.body.innerHTML = `
${printContent}
`;
window.print();
document.body.innerHTML = originalContent;
};
const handleDownload = () => {
// In a real implementation, this would generate a PDF
alert('PDF download functionality would be implemented here.');
};
return (
{settings?.businessName || 'My POS Store'}
{settings?.address || '123 Business St, City, State, ZIP'}
Tel: {settings?.phone || '(123) 456-7890'}
Receipt #:
{receipt.id}
Date:
{new Date(receipt.date).toLocaleString()}
Cashier:
{receipt.staffName}
Customer:
{receipt.customerName}
{receipt.items.map(item => (
{item.name}
{item.quantity}
${item.price.toFixed(2)}
${(item.price * item.quantity).toFixed(2)}
))}
Subtotal
${receipt.subtotal.toFixed(2)}
Tax
${receipt.tax.toFixed(2)}
Total
${receipt.total.toFixed(2)}
{receipt.paymentMethod === 'cash' && (
<>
Amount Received
${parseFloat(receipt.amountReceived).toFixed(2)}
Change
${receipt.change.toFixed(2)}
>
)}
Payment Method
{receipt.paymentMethod === 'cash' ? 'Cash' :
receipt.paymentMethod === 'credit' ? 'Credit Card' :
'Credit Account'}
{settings?.receiptFooter || 'Thank you for your business!'}
Please come again
Close
);
}
// Products View
function ProductsView() {
const { darkMode, showNotification } = useContext(AppContext);
const [products, setProducts] = useState([]);
const [categories, setCategories] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [showAddProductModal, setShowAddProductModal] = useState(false);
const [showEditProductModal, setShowEditProductModal] = useState(false);
const [currentProduct, setCurrentProduct] = useState(null);
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const [productToDelete, setProductToDelete] = useState(null);
const [showExpiringOnly, setShowExpiringOnly] = useState(false);
const [showLowStockOnly, setShowLowStockOnly] = useState(false);
useEffect(() => {
const loadProductsData = async () => {
try {
// Fetch products
const productsRef = mockFirebase.database.ref('products');
const productsSnapshot = await productsRef.get();
const productsData = productsSnapshot.val() || [];
setProducts(productsData);
// Fetch categories
const categoriesRef = mockFirebase.database.ref('categories');
const categoriesSnapshot = await categoriesRef.get();
const categoriesData = categoriesSnapshot.val() || [];
setCategories(categoriesData);
setIsLoading(false);
} catch (error) {
console.error('Error loading products data:', error);
setIsLoading(false);
}
};
loadProductsData();
}, []);
// Handle adding a new product
const handleAddProduct = async (productData) => {
try {
// Generate a new ID
const newId = Date.now().toString();
const newProduct = { ...productData, id: newId };
// Add to products array
const updatedProducts = [...products, newProduct];
setProducts(updatedProducts);
// Save to Firebase
const productRef = mockFirebase.database.ref(`products/${newId}`);
await productRef.set(newProduct);
showNotification('Product added successfully', 'success');
} catch (error) {
console.error('Error adding product:', error);
showNotification('Error adding product', 'error');
}
};
// Handle editing a product
const handleEditProduct = async (productData) => {
try {
// Update in products array
const updatedProducts = products.map(product =>
product.id === productData.id ? productData : product
);
setProducts(updatedProducts);
// Save to Firebase
const productRef = mockFirebase.database.ref(`products/${productData.id}`);
await productRef.update(productData);
showNotification('Product updated successfully', 'success');
} catch (error) {
console.error('Error updating product:', error);
showNotification('Error updating product', 'error');
}
};
// Handle deleting a product
const handleDeleteProduct = async (productId) => {
try {
// Remove from products array
const updatedProducts = products.filter(product => product.id !== productId);
setProducts(updatedProducts);
// Remove from Firebase
const productRef = mockFirebase.database.ref(`products/${productId}`);
await productRef.remove();
showNotification('Product deleted successfully', 'success');
} catch (error) {
console.error('Error deleting product:', error);
showNotification('Error deleting product', 'error');
}
};
// Filter products based on search, category, and other filters
const filteredProducts = products.filter(product => {
const matchesSearch =
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(product.barcode && product.barcode.includes(searchQuery)) ||
(product.description && product.description.toLowerCase().includes(searchQuery.toLowerCase()));
const matchesCategory = selectedCategory === 'all' || product.category === selectedCategory;
const matchesExpiring = !showExpiringOnly || (product.expires && product.expiryDate);
const matchesLowStock = !showLowStockOnly || product.stock < 10;
return matchesSearch && matchesCategory && matchesExpiring && matchesLowStock;
});
if (isLoading) {
return (
);
}
return (
Products
Manage your product inventory.
setShowExpiringOnly(!showExpiringOnly)}
className={`px-3 py-2 rounded-lg flex items-center ${
showExpiringOnly
? 'bg-amber-600 text-white'
: darkMode
? 'bg-gray-700 text-white hover:bg-gray-600'
: 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
}`}
>
Expiring
setShowLowStockOnly(!showLowStockOnly)}
className={`px-3 py-2 rounded-lg flex items-center ${
showLowStockOnly
? 'bg-red-600 text-white'
: darkMode
? 'bg-gray-700 text-white hover:bg-gray-600'
: 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
}`}
>
Low Stock
{
setCurrentProduct(null);
setShowAddProductModal(true);
}}
className="px-3 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white flex items-center"
>
Add Product
{filteredProducts.length === 0 ? (
No products found
{
setCurrentProduct(null);
setShowAddProductModal(true);
}}
className="mt-4 px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg"
>
Add Your First Product
) : (
Name
Category
Price
Stock
Expiry
Actions
{filteredProducts.map(product => (
{product.name}
{product.barcode}
{product.category}
${product.price.toFixed(2)}
10
? darkMode ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'
: product.stock > 0
? darkMode ? 'bg-yellow-900 text-yellow-200' : 'bg-yellow-100 text-yellow-800'
: darkMode ? 'bg-red-900 text-red-200' : 'bg-red-100 text-red-800'
}`}>
{product.stock}
{product.expires ? (
{new Date(product.expiryDate).toLocaleDateString()}
) : (
N/A
)}
{
setCurrentProduct(product);
setShowEditProductModal(true);
}}
className={`text-indigo-600 hover:text-indigo-900 mr-3 ${darkMode ? 'hover:text-indigo-400' : 'hover:text-indigo-900'}`}
>
Edit
{
setProductToDelete(product);
setShowDeleteConfirmation(true);
}}
className={`text-red-600 ${darkMode ? 'hover:text-red-400' : 'hover:text-red-900'}`}
>
Delete
))}
)}
{/* Add/Edit Product Modal */}
{(showAddProductModal || showEditProductModal) && (
{
setShowAddProductModal(false);
setShowEditProductModal(false);
setCurrentProduct(null);
}}
isEdit={showEditProductModal}
/>
)}
{/* Delete Confirmation Modal */}
{showDeleteConfirmation && (
{
handleDeleteProduct(productToDelete.id);
setShowDeleteConfirmation(false);
setProductToDelete(null);
}}
onCancel={() => {
setShowDeleteConfirmation(false);
setProductToDelete(null);
}}
confirmButtonClass="bg-red-600 hover:bg-red-700"
/>
)}
);
}
// Product Form Modal
function ProductFormModal({ product, categories, onSave, onClose, isEdit }) {
const { darkMode } = useContext(AppContext);
const [formData, setFormData] = useState(
product ? { ...product } : {
name: '',
price: '',
category: '',
stock: '',
barcode: '',
description: '',
expires: false,
expiryDate: ''
}
);
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : value
});
};
const handleSubmit = (e) => {
e.preventDefault();
// Validate form
if (!formData.name || !formData.price || !formData.category || !formData.stock) {
alert('Please fill in all required fields');
return;
}
// Convert price and stock to numbers
const processedData = {
...formData,
price: parseFloat(formData.price),
stock: parseInt(formData.stock)
};
onSave(processedData);
onClose();
};
return (
import React, { useState, useEffect } from 'react';
import { Search, ShoppingCart, Package, BarChart2, User, LogOut, Plus, Minus, Trash2, X, Check, DollarSign, Eye, EyeOff } from 'lucide-react';
// Main POS Application
export default function POSSystem() {
// State management
const [activeTab, setActiveTab] = useState('pos');
const [products, setProducts] = useState([]);
const [cart, setCart] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState(null);
const [sales, setSales] = useState([]);
const [showCheckoutModal, setShowCheckoutModal] = useState(false);
const [paymentMethod, setPaymentMethod] = useState('cash');
const [amountReceived, setAmountReceived] = useState('');
const [showReceiptModal, setShowReceiptModal] = useState(false);
const [currentReceipt, setCurrentReceipt] = useState(null);
const [categories, setCategories] = useState([]);
const [selectedCategory, setSelectedCategory] = useState('all');
const [showAddProductModal, setShowAddProductModal] = useState(false);
const [newProduct, setNewProduct] = useState({
name: '',
price: '',
category: '',
stock: '',
barcode: '',
description: ''
});
const [isDemo, setIsDemo] = useState(false);
const [registeredUsers, setRegisteredUsers] = useState([
{ username: 'admin', password: 'admin123', role: 'admin' },
{ username: 'cashier', password: 'cashier123', role: 'cashier' },
{ username: 'manager', password: 'manager123', role: 'manager' }
]);
const [showRegisterModal, setShowRegisterModal] = useState(false);
const [newUser, setNewUser] = useState({
username: '',
password: '',
confirmPassword: '',
role: 'cashier'
});
// Load data on component mount
useEffect(() => {
// Check for existing session
const savedUser = localStorage.getItem('posUser');
const savedIsDemo = localStorage.getItem('posIsDemo');
if (savedUser) {
setUser(JSON.parse(savedUser));
setIsAuthenticated(true);
setIsDemo(savedIsDemo === 'true');
}
// Load registered users from localStorage if available
const savedUsers = localStorage.getItem('posRegisteredUsers');
if (savedUsers) {
setRegisteredUsers(JSON.parse(savedUsers));
} else {
// Save default users to localStorage
localStorage.setItem('posRegisteredUsers', JSON.stringify(registeredUsers));
}
// Load data based on authentication status
loadData();
}, [isAuthenticated, isDemo]);
// Load data function
const loadData = async () => {
try {
if (isAuthenticated) {
if (isDemo) {
// Load demo data
loadDemoData();
} else {
// Load real data from API or localStorage
await fetchProducts();
await fetchSales();
await fetchCategories();
}
}
setIsLoading(false);
} catch (error) {
console.error('Error loading data:', error);
setIsLoading(false);
}
};
// Load demo data
const loadDemoData = () => {
// Load demo products
const demoProducts = [
{ _id: '1', name: 'T-Shirt', price: 19.99, category: 'Clothing', stock: 45, barcode: '123456789', description: 'Cotton t-shirt' },
{ _id: '2', name: 'Jeans', price: 49.99, category: 'Clothing', stock: 30, barcode: '223456789', description: 'Blue denim jeans' },
{ _id: '3', name: 'Sneakers', price: 79.99, category: 'Footwear', stock: 25, barcode: '323456789', description: 'Running shoes' },
{ _id: '4', name: 'Coffee Mug', price: 12.99, category: 'Accessories', stock: 50, barcode: '423456789', description: 'Ceramic coffee mug' },
{ _id: '5', name: 'Notebook', price: 4.99, category: 'Stationery', stock: 100, barcode: '523456789', description: 'Lined notebook' },
{ _id: '6', name: 'Backpack', price: 39.99, category: 'Accessories', stock: 20, barcode: '623456789', description: 'School backpack' },
{ _id: '7', name: 'Water Bottle', price: 14.99, category: 'Accessories', stock: 40, barcode: '723456789', description: 'Stainless steel water bottle' },
{ _id: '8', name: 'Headphones', price: 89.99, category: 'Electronics', stock: 15, barcode: '823456789', description: 'Wireless headphones' },
];
setProducts(demoProducts);
// Load demo sales
const demoSales = [
{ _id: '1', date: '2023-06-01', total: 149.95, items: 3, paymentMethod: 'credit' },
{ _id: '2', date: '2023-06-02', total: 89.97, items: 2, paymentMethod: 'cash' },
{ _id: '3', date: '2023-06-03', total: 199.96, items: 4, paymentMethod: 'credit' },
{ _id: '4', date: '2023-06-04', total: 59.98, items: 2, paymentMethod: 'cash' },
{ _id: '5', date: '2023-06-05', total: 129.95, items: 3, paymentMethod: 'credit' },
];
setSales(demoSales);
// Load demo categories
const demoCategories = ['Clothing', 'Footwear', 'Accessories', 'Electronics', 'Stationery'];
setCategories(demoCategories);
};
// Fetch products from API or localStorage
const fetchProducts = async () => {
try {
// Check if we have products in localStorage
const savedProducts = localStorage.getItem(`posProducts_${user.username}`);
if (savedProducts) {
setProducts(JSON.parse(savedProducts));
} else {
// If no saved products, we could fetch from API
// For now, initialize with empty array
setProducts([]);
localStorage.setItem(`posProducts_${user.username}`, JSON.stringify([]));
}
} catch (error) {
console.error('Error fetching products:', error);
}
};
// Fetch sales from API or localStorage
const fetchSales = async () => {
try {
// Check if we have sales in localStorage
const savedSales = localStorage.getItem(`posSales_${user.username}`);
if (savedSales) {
setSales(JSON.parse(savedSales));
} else {
// If no saved sales, we could fetch from API
// For now, initialize with empty array
setSales([]);
localStorage.setItem(`posSales_${user.username}`, JSON.stringify([]));
}
} catch (error) {
console.error('Error fetching sales:', error);
}
};
// Fetch categories from API or localStorage
const fetchCategories = async () => {
try {
// Check if we have categories in localStorage
const savedCategories = localStorage.getItem(`posCategories_${user.username}`);
if (savedCategories) {
setCategories(JSON.parse(savedCategories));
} else {
// If no saved categories, initialize with default categories
const defaultCategories = ['Clothing', 'Footwear', 'Accessories', 'Electronics', 'Stationery'];
setCategories(defaultCategories);
localStorage.setItem(`posCategories_${user.username}`, JSON.stringify(defaultCategories));
}
} catch (error) {
console.error('Error fetching categories:', error);
}
};
// Handle login
const handleLogin = (userData, demo = false) => {
setUser(userData);
setIsAuthenticated(true);
setIsDemo(demo);
// Save to localStorage
localStorage.setItem('posUser', JSON.stringify(userData));
localStorage.setItem('posIsDemo', demo.toString());
};
// Handle logout
const handleLogout = () => {
setUser(null);
setIsAuthenticated(false);
setCart([]);
// Clear from localStorage
localStorage.removeItem('posUser');
localStorage.removeItem('posIsDemo');
};
// Add product to cart
const addToCart = (product) => {
const existingItem = cart.find(item => item._id === product._id);
if (existingItem) {
setCart(cart.map(item =>
item._id === product._id
? { ...item, quantity: item.quantity + 1 }
: item
));
} else {
setCart([...cart, { ...product, quantity: 1 }]);
}
};
// Update cart item quantity
const updateCartItemQuantity = (productId, newQuantity) => {
if (newQuantity <= 0) {
removeFromCart(productId);
return;
}
setCart(cart.map(item =>
item._id === productId
? { ...item, quantity: newQuantity }
: item
));
};
// Remove item from cart
const removeFromCart = (productId) => {
setCart(cart.filter(item => item._id !== productId));
};
// Calculate cart total
const calculateTotal = () => {
return cart.reduce((total, item) => total + (item.price * item.quantity), 0);
};
// Process checkout
const processCheckout = async () => {
if (cart.length === 0) return;
try {
const total = calculateTotal();
const change = paymentMethod === 'cash' ? parseFloat(amountReceived) - total : 0;
// Create receipt
const receipt = {
_id: Date.now().toString(),
date: new Date().toISOString(),
items: cart,
total: total,
paymentMethod: paymentMethod,
amountReceived: paymentMethod === 'cash' ? parseFloat(amountReceived) : total,
change: change
};
// Update inventory and sales
if (isDemo) {
// Update demo data in memory only
updateDemoData(receipt);
} else {
// Update real data in localStorage or API
await updateRealData(receipt);
}
// Show receipt
setCurrentReceipt(receipt);
setShowReceiptModal(true);
setShowCheckoutModal(false);
// Clear cart
setCart([]);
setAmountReceived('');
} catch (error) {
console.error('Error processing checkout:', error);
}
};
// Update demo data
const updateDemoData = (receipt) => {
// Update products stock
const updatedProducts = products.map(product => {
const cartItem = cart.find(item => item._id === product._id);
if (cartItem) {
return { ...product, stock: product.stock - cartItem.quantity };
}
return product;
});
setProducts(updatedProducts);
// Update sales
const newSale = {
_id: receipt._id,
date: receipt.date,
total: receipt.total,
items: cart.reduce((sum, item) => sum + item.quantity, 0),
paymentMethod: receipt.paymentMethod
};
setSales([...sales, newSale]);
};
// Update real data
const updateRealData = async (receipt) => {
// Update products stock
const updatedProducts = products.map(product => {
const cartItem = cart.find(item => item._id === product._id);
if (cartItem) {
return { ...product, stock: product.stock - cartItem.quantity };
}
return product;
});
setProducts(updatedProducts);
// Save updated products to localStorage
localStorage.setItem(`posProducts_${user.username}`, JSON.stringify(updatedProducts));
// Update sales
const newSale = {
_id: receipt._id,
date: receipt.date,
total: receipt.total,
items: cart.reduce((sum, item) => sum + item.quantity, 0),
paymentMethod: receipt.paymentMethod
};
const updatedSales = [...sales, newSale];
setSales(updatedSales);
// Save updated sales to localStorage
localStorage.setItem(`posSales_${user.username}`, JSON.stringify(updatedSales));
// In a real app, you would also make API calls here
// await fetch('https://www.sitebrew.ai/api/eNqleS/sales', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ document: receipt })
// });
// For each product in cart, update inventory
// cart.forEach(async (item) => {
// const updatedProduct = products.find(p => p._id === item._id);
// await fetch(`https://www.sitebrew.ai/api/eNqleS/products`, {
// method: 'PUT',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ document: updatedProduct })
// });
// });
};
// Add new product
const handleAddProduct = async () => {
try {
const productToAdd = {
...newProduct,
_id: Date.now().toString(),
price: parseFloat(newProduct.price),
stock: parseInt(newProduct.stock)
};
if (isDemo) {
// Add to demo products
setProducts([...products, productToAdd]);
} else {
// Add to real products
const updatedProducts = [...products, productToAdd];
setProducts(updatedProducts);
// Save to localStorage
localStorage.setItem(`posProducts_${user.username}`, JSON.stringify(updatedProducts));
// In a real app, you would also make API call here
// await fetch('https://www.sitebrew.ai/api/eNqleS/products', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ document: productToAdd })
// });
}
// Reset form
setNewProduct({
name: '',
price: '',
category: '',
stock: '',
barcode: '',
description: ''
});
setShowAddProductModal(false);
} catch (error) {
console.error('Error adding product:', error);
}
};
// Register new user
const handleRegisterUser = () => {
// Validate form
if (newUser.password !== newUser.confirmPassword) {
alert("Passwords don't match");
return;
}
if (registeredUsers.some(user => user.username === newUser.username)) {
alert("Username already exists");
return;
}
// Create new user object
const userToAdd = {
username: newUser.username,
password: newUser.password,
role: newUser.role
};
// Add to registered users
const updatedUsers = [...registeredUsers, userToAdd];
setRegisteredUsers(updatedUsers);
// Save to localStorage
localStorage.setItem('posRegisteredUsers', JSON.stringify(updatedUsers));
// Reset form and close modal
setNewUser({
username: '',
password: '',
confirmPassword: '',
role: 'cashier'
});
setShowRegisterModal(false);
};
// Filter products based on search and category
const filteredProducts = products.filter(product => {
const matchesSearch = product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(product.barcode && product.barcode.includes(searchQuery));
const matchesCategory = selectedCategory === 'all' || product.category === selectedCategory;
return matchesSearch && matchesCategory;
});
// Calculate dashboard metrics
const calculateDailyTotal = () => {
const today = new Date().toISOString().split('T')[0];
return sales
.filter(sale => sale.date.startsWith(today))
.reduce((sum, sale) => sum + sale.total, 0);
};
const calculateTotalSales = () => {
return sales.reduce((sum, sale) => sum + sale.total, 0);
};
const calculateAverageOrderValue = () => {
if (sales.length === 0) return 0;
return calculateTotalSales() / sales.length;
};
const calculateLowStockItems = () => {
return products.filter(product => product.stock < 10).length;
};
// Check if user has admin privileges
const isAdmin = user && (user.role === 'admin' || user.role === 'manager');
if (isLoading) {
return (
);
}
if (!isAuthenticated) {
return
;
}
return (
{/* Header */}
POS System {isDemo && DEMO MODE }
{user.username} ({user.role})
{isAdmin && (
setShowRegisterModal(true)}
className="flex items-center space-x-1 bg-[#4fd1c5] hover:bg-[#38b2ac] px-3 py-1 rounded"
>
Add User
)}
Logout
{/* Navigation */}
setActiveTab('pos')}
className={`flex items-center space-x-2 px-6 py-4 ${activeTab === 'pos' ? 'border-b-2 border-[#ff6b6b] text-[#1a365d] font-medium' : 'text-gray-600'}`}
>
Point of Sale
setActiveTab('inventory')}
className={`flex items-center space-x-2 px-6 py-4 ${activeTab === 'inventory' ? 'border-b-2 border-[#ff6b6b] text-[#1a365d] font-medium' : 'text-gray-600'}`}
>
Inventory
setActiveTab('reports')}
className={`flex items-center space-x-2 px-6 py-4 ${activeTab === 'reports' ? 'border-b-2 border-[#ff6b6b] text-[#1a365d] font-medium' : 'text-gray-600'}`}
>
Reports
{/* Main Content */}
{activeTab === 'pos' && (
{/* Product Catalog */}
{filteredProducts.length === 0 ? (
No products found
{products.length === 0 && (
setShowAddProductModal(true)}
className="mt-4 px-4 py-2 bg-[#4fd1c5] hover:bg-[#38b2ac] text-white rounded-lg"
>
Add Your First Product
)}
) : (
{filteredProducts.map(product => (
{product.name}
${product.price.toFixed(2)}
{product.category}
{product.description}
10 ? 'text-green-600' : 'text-red-600'}`}>
Stock: {product.stock}
addToCart(product)}
disabled={product.stock <= 0}
className={`px-4 py-2 rounded-lg ${
product.stock > 0
? 'bg-[#ff6b6b] hover:bg-[#ff5252] text-white'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
>
Add to Cart
))}
)}
{/* Shopping Cart */}
Shopping Cart
{cart.length === 0 ? (
) : (
{cart.map(item => (
{item.name}
${item.price.toFixed(2)} each
updateCartItemQuantity(item._id, item.quantity - 1)}
className="p-1 rounded-full bg-gray-200 hover:bg-gray-300"
>
{item.quantity}
updateCartItemQuantity(item._id, item.quantity + 1)}
className="p-1 rounded-full bg-gray-200 hover:bg-gray-300"
disabled={item.quantity >= products.find(p => p._id === item._id).stock}
>
removeFromCart(item._id)}
className="p-1 rounded-full bg-red-100 text-red-600 hover:bg-red-200 ml-2"
>
))}
)}
Subtotal:
${calculateTotal().toFixed(2)}
Tax (7%):
${(calculateTotal() * 0.07).toFixed(2)}
Total:
${(calculateTotal() * 1.07).toFixed(2)}
setShowCheckoutModal(true)}
disabled={cart.length === 0}
className={`w-full py-3 rounded-lg ${
cart.length > 0
? 'bg-[#4fd1c5] hover:bg-[#38b2ac] text-white'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
>
Checkout
)}
{activeTab === 'inventory' && (
Inventory Management
setShowAddProductModal(true)}
className="bg-[#4fd1c5] hover:bg-[#38b2ac] text-white px-4 py-2 rounded-lg flex items-center"
>
Add Product
{products.length === 0 ? (
No products found
setShowAddProductModal(true)}
className="mt-4 px-4 py-2 bg-[#4fd1c5] hover:bg-[#38b2ac] text-white rounded-lg"
>
Add Your First Product
) : (
Name
Category
Price
Stock
Barcode
Actions
{filteredProducts.map(product => (
{product.name}
{product.description}
{product.category}
${product.price.toFixed(2)}
10
? 'bg-green-100 text-green-800'
: product.stock > 0
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'
}`}>
{product.stock}
{product.barcode}
Edit
Delete
))}
)}
)}
{activeTab === 'reports' && (
Reports & Analytics
{/* Dashboard Cards */}
Today's Sales
${calculateDailyTotal().toFixed(2)}
Total Sales
${calculateTotalSales().toFixed(2)}
Average Order Value
${calculateAverageOrderValue().toFixed(2)}
Low Stock Items
{calculateLowStockItems()}
{/* Sales History */}
Recent Sales
{sales.length === 0 ? (
No sales data available yet
) : (
Date
Transaction ID
Items
Payment Method
Total
{sales.map(sale => (
{new Date(sale.date).toLocaleDateString()}
{sale._id}
{sale.items}
{sale.paymentMethod === 'cash' ? 'Cash' : 'Credit Card'}
${sale.total.toFixed(2)}
))}
)}
{/* Low Stock Items */}
Low Stock Items
{products.filter(product => product.stock < 10).length === 0 ? (
) : (
Name
Category
Current Stock
Status
{products.filter(product => product.stock < 10).map(product => (
{product.name}
{product.category}
{product.stock}
5
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'
}`}>
{product.stock > 5 ? 'Low Stock' : 'Critical'}
))}
)}
)}
{/* Checkout Modal */}
{showCheckoutModal && (
Checkout
setShowCheckoutModal(false)} className="text-gray-500 hover:text-gray-700">
Order Summary
{cart.map(item => (
{item.name} x {item.quantity}
${(item.price * item.quantity).toFixed(2)}
))}
Subtotal
${calculateTotal().toFixed(2)}
Tax (7%)
${(calculateTotal() * 0.07).toFixed(2)}
Total
${(calculateTotal() * 1.07).toFixed(2)}
Payment Method
setPaymentMethod('cash')}
className={`flex-1 py-3 rounded-lg border ${
paymentMethod === 'cash'
? 'bg-[#4fd1c5] text-white border-[#4fd1c5]'
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
}`}
>
Cash
setPaymentMethod('credit')}
className={`flex-1 py-3 rounded-lg border ${
paymentMethod === 'credit'
? 'bg-[#4fd1c5] text-white border-[#4fd1c5]'
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
}`}
>
Credit Card
{paymentMethod === 'cash' && (
Amount Received
{parseFloat(amountReceived) > 0 && (
Change: ${Math.max(0, parseFloat(amountReceived) - (calculateTotal() * 1.07)).toFixed(2)}
)}
)}
= calculateTotal() * 1.07))
? 'bg-[#ff6b6b] hover:bg-[#ff5252] text-white'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
>
Complete Purchase
)}
{/* Receipt Modal */}
{showReceiptModal && currentReceipt && (
Receipt
setShowReceiptModal(false)} className="text-gray-500 hover:text-gray-700">
Your Business Name
123 Main Street, City, State
Tel: (123) 456-7890
Receipt #:
{currentReceipt._id}
Date:
{new Date(currentReceipt.date).toLocaleString()}
{currentReceipt.items.map(item => (
{item.name}
{item.quantity}
${item.price.toFixed(2)}
${(item.price * item.quantity).toFixed(2)}
))}
Subtotal
${(currentReceipt.total).toFixed(2)}
Tax (7%)
${(currentReceipt.total * 0.07).toFixed(2)}
Total
${(currentReceipt.total * 1.07).toFixed(2)}
{currentReceipt.paymentMethod === 'cash' && (
<>
Amount Received
${parseFloat(currentReceipt.amountReceived).toFixed(2)}
Change
${currentReceipt.change.toFixed(2)}
>
)}
Payment Method
{currentReceipt.paymentMethod === 'cash' ? 'Cash' : 'Credit Card'}
Thank you for your purchase!
Please come again
setShowReceiptModal(false)}
className="w-full py-3 rounded-lg bg-[#4fd1c5] hover:bg-[#38b2ac] text-white"
>
Close
)}
{/* Add Product Modal */}
{showAddProductModal && (
Add New Product
setShowAddProductModal(false)} className="text-gray-500 hover:text-gray-700">
setShowAddProductModal(false)}
className="flex-1 py-2 border border-gray-300 rounded-lg text-gray-700 bg-white hover:bg-gray-50"
>
Cancel
Add Product
)}
{/* Register User Modal */}
{showRegisterModal && (
Register New User
setShowRegisterModal(false)} className="text-gray-500 hover:text-gray-700">
setShowRegisterModal(false)}
className="flex-1 py-2 border border-gray-300 rounded-lg text-gray-700 bg-white hover:bg-gray-50"
>
Cancel
Register User
)}
);
}
// Login Screen Component
function LoginScreen({ onLogin, registeredUsers }) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [showPassword, setShowPassword] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
// Simple validation
if (!username || !password) {
setError('Please enter both username and password');
return;
}
// Check if user exists
const user = registeredUsers.find(u => u.username === username && u.password === password);
if (user) {
// Real login
onLogin({ username: user.username, role: user.role }, false);
} else {
setError('Invalid username or password');
}
};
const handleDemoLogin = () => {
// Demo login
onLogin({ username: 'demo', role: 'admin' }, true);
};
return (
POS System Login
Enter your credentials to access the system
);
}