import React, { useState, useMemo, useEffect } from 'react';
import {
Users, UserPlus, Package, ShoppingCart, Activity, DollarSign,
Search, Filter, Plus, Trash2, Edit, RefreshCw, X, Menu, LogOut, CheckCircle, AlertCircle, FileText
} from 'lucide-react';
// --- UTILITY COMPONENTS ---
const Modal = ({ isOpen, onClose, title, children }) => {
if (!isOpen) return null;
return (
);
};
const StatCard = ({ title, value, icon: Icon, colorClass, subtitle }) => (
{title}
{value}
{subtitle &&
{subtitle}
}
);
// --- CUSTOM SVG LINE CHART ---
const LineChart = ({ data, lines }) => {
if (!data || data.length === 0) return No data available
;
const width = 800;
const height = 300;
const padding = 40;
// Find global min and max for Y scale
let min = 0;
let max = 0;
data.forEach(d => {
lines.forEach(line => {
if (d[line.key] > max) max = d[line.key];
if (d[line.key] < min) min = d[line.key];
});
});
// Add 10% padding to max
max = max * 1.1 || 10;
const scaleX = (index) => padding + (index * (width - 2 * padding) / Math.max(1, data.length - 1));
const scaleY = (val) => height - padding - ((val - min) / (max - min) * (height - 2 * padding));
return (
{/* Legend */}
);
};
// --- INITIAL MOCK DATA ---
const initialUsers = [
{
id: "NOV-A1B2",
name: "John Doe",
cnic: "42101-1234567-1",
phone: "0300-1234567",
gender: "Male",
fingerprint: "FP-001",
isStaff: false,
notes: "Requires evening slots",
fileName: null,
history: [
{ id: 'h1', startDate: '2025-01-01', endDate: '2026-06-01', fee: 5000, regFee: 1000, dateAdded: '2025-01-01' }
]
},
{
id: "NOV-X9Y8",
name: "Jane Smith",
cnic: "42101-7654321-2",
phone: "0321-7654321",
gender: "Female",
fingerprint: "FP-002",
isStaff: true,
notes: "Staff Member - Trainer",
fileName: "id_card.pdf",
history: [
{ id: 'h2', startDate: '2025-01-15', endDate: '2025-12-15', fee: 0, regFee: 0, dateAdded: '2025-01-15' }
]
},
{
id: "NOV-Z3M4",
name: "Ahmed Ali",
cnic: "42101-1122334-1",
phone: "0333-1122334",
gender: "Male",
fingerprint: "FP-003",
isStaff: false,
notes: "",
fileName: null,
history: [
{ id: 'h3', startDate: '2024-03-01', endDate: '2024-04-01', fee: 4000, regFee: 1000, dateAdded: '2024-03-01' } // Expired
]
}
];
const initialInventory = [
{ id: 'p1', name: 'Optimum Nutrition Gold Standard Whey', category: 'Proteins', cost: 15000, price: 18000, stock: 24 },
{ id: 'p2', name: 'MuscleTech Creatine', category: 'Supplements', cost: 5000, price: 6500, stock: 15 },
{ id: 'p3', name: 'NovaFit Gym T-Shirt', category: 'Apparel', cost: 800, price: 1500, stock: 50 }
];
const initialCategories = ['Proteins', 'Supplements', 'Apparel', 'Accessories'];
const initialSales = [
{ id: 's1', productId: 'p1', productName: 'Optimum Nutrition Gold Standard Whey', qty: 2, pricePerPiece: 18000, costPerPiece: 15000, date: '2026-03-10T10:00:00' },
{ id: 's2', productId: 'p3', productName: 'NovaFit Gym T-Shirt', qty: 5, pricePerPiece: 1500, costPerPiece: 800, date: '2026-04-05T14:30:00' }
];
const initialExpenses = [
{
id: 'e1',
name: 'Treadmill Servicing',
category: 'Maintenance',
history: [
{ id: 'eh1', cost: 15000, startDate: '2026-01-01', endDate: '2026-01-05', notes: 'Replaced belts', date: '2026-01-01' },
{ id: 'eh2', cost: 8000, startDate: '2026-04-10', endDate: '2026-04-12', notes: 'Motor tuning', date: '2026-04-10' }
]
},
{
id: 'e2',
name: 'Electricity Bill',
category: 'Utilities',
history: [
{ id: 'eh3', cost: 45000, startDate: '2026-03-01', endDate: '2026-03-31', notes: 'March Bill', date: '2026-04-05' }
]
}
];
// --- MAIN APP COMPONENT ---
export default function App() {
const [isAuthenticated, setIsAuth] = useState(false);
const [activeTab, setActiveTab] = useState('B'); // Default to Users panel
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// Global State
const [users, setUsers] = useState(initialUsers);
const [inventory, setInventory] = useState(initialInventory);
const [categories, setCategories] = useState(initialCategories);
const [sales, setSales] = useState(initialSales);
const [expenses, setExpenses] = useState(initialExpenses);
// Login Check
const handleLogin = (e) => {
e.preventDefault();
const u = e.target.username.value;
const p = e.target.password.value;
if (u === 'NOVAFIT' && p === 'NOVAFIT2K26') {
setIsAuth(true);
} else {
alert('Invalid Credentials');
}
};
if (!isAuthenticated) {
return (
NOVAFIT
Gym Management Dashboard
);
}
// Helper: Check if a user's latest membership is active
const isUserActive = (user) => {
if (!user.history || user.history.length === 0) return false;
const latest = user.history[user.history.length - 1];
return new Date(latest.endDate) >= new Date();
};
const navItems = [
{ id: 'B', label: 'Users Dashboard', icon: Users },
{ id: 'A', label: 'Registration', icon: UserPlus },
{ id: 'C', label: 'Inventory', icon: Package },
{ id: 'D', label: 'Point of Sale', icon: ShoppingCart },
{ id: 'E', label: 'Finances', icon: DollarSign },
];
return (
{/* Sidebar */}
{/* Mobile Header */}
NOVAFIT
{/* Main Content */}
{activeTab === 'A' &&
}
{activeTab === 'B' &&
}
{activeTab === 'C' &&
}
{activeTab === 'D' &&
}
{activeTab === 'E' &&
}
);
}
// ============================================================================
// PANEL A: REGISTRATION
// ============================================================================
const PanelRegistration = ({ users, setUsers, setActiveTab }) => {
const [formData, setFormData] = useState({
name: '', cnic: '', phone: '', startDate: '', endDate: '', gender: 'Male',
fingerprint: '', fee: '', regFee: '', isStaff: false, notes: ''
});
const [fileName, setFileName] = useState('');
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value }));
};
const handleFileChange = (e) => {
if (e.target.files.length > 0) {
setFileName(e.target.files[0].name);
}
};
const handleSubmit = (e) => {
e.preventDefault();
const newId = `NOV-${Math.random().toString(36).substring(2, 6).toUpperCase()}`;
const newUser = {
id: newId,
name: formData.name,
cnic: formData.cnic,
phone: formData.phone,
gender: formData.gender,
fingerprint: formData.fingerprint,
isStaff: formData.isStaff,
notes: formData.notes,
fileName: fileName || null,
history: [
{
id: `h_${Date.now()}`,
startDate: formData.startDate,
endDate: formData.endDate,
fee: Number(formData.fee) || 0,
regFee: Number(formData.regFee) || 0,
dateAdded: new Date().toISOString()
}
]
};
setUsers(prev => [...prev, newUser]);
alert(`User Registered Successfully! Assigned ID: ${newId}`);
setActiveTab('B'); // Go back to user list
};
return (
New Registration
Add a new member or staff to the system.
);
};
// ============================================================================
// PANEL B: USERS
// ============================================================================
const PanelUsers = ({ users, setUsers, isUserActive }) => {
const [search, setSearch] = useState('');
const [filterGender, setFilterGender] = useState('All');
const [filterStatus, setFilterStatus] = useState('All');
const [selectedUser, setSelectedUser] = useState(null);
const [isRenewModalOpen, setIsRenewModalOpen] = useState(false);
const [renewData, setRenewData] = useState({ startDate: '', endDate: '', fee: '' });
// Metrics
const totalUsers = users.length;
const totalMales = users.filter(u => u.gender === 'Male').length;
const totalFemales = users.filter(u => u.gender === 'Female').length;
const activeUsers = users.filter(u => isUserActive(u)).length;
const expiredUsers = totalUsers - activeUsers;
const totalFeesEverPaid = users.reduce((total, user) => {
return total + (user.history || []).reduce((sum, h) => sum + Number(h.fee) + Number(h.regFee || 0), 0);
}, 0);
// Filtering
const filteredUsers = useMemo(() => {
return users.filter(u => {
const matchSearch = u.name.toLowerCase().includes(search.toLowerCase()) || u.id.toLowerCase().includes(search.toLowerCase());
const matchGender = filterGender === 'All' || u.gender === filterGender;
const isActive = isUserActive(u);
const matchStatus = filterStatus === 'All' ||
(filterStatus === 'Expired' && !isActive) ||
(filterStatus === 'Active' && isActive);
return matchSearch && matchGender && matchStatus;
});
}, [users, search, filterGender, filterStatus]);
const handleDelete = (id) => {
if(window.confirm('Are you sure you want to delete this user?')) {
setUsers(users.filter(u => u.id !== id));
}
};
const openRenewModal = (user) => {
setSelectedUser(user);
const today = new Date().toISOString().split('T')[0];
setRenewData({ startDate: today, endDate: '', fee: '' });
setIsRenewModalOpen(true);
};
const handleRenewSubmit = (e) => {
e.preventDefault();
const updatedUsers = users.map(u => {
if (u.id === selectedUser.id) {
return {
...u,
history: [...(u.history || []), {
id: `h_${Date.now()}`,
startDate: renewData.startDate,
endDate: renewData.endDate,
fee: Number(renewData.fee),
regFee: 0,
dateAdded: new Date().toISOString()
}]
};
}
return u;
});
setUsers(updatedUsers);
setIsRenewModalOpen(false);
alert('Membership Renewed!');
};
return (
Users Dashboard
Manage members, view statuses, and renew memberships.
{/* Top Metrics */}
{/* Filters & Search */}
setSearch(e.target.value)}
className="w-full bg-slate-950 border border-slate-700 rounded-lg pl-10 pr-4 py-2 text-sm focus:outline-none focus:border-cyan-500"
/>
{/* Users List */}
| User / ID |
Contact & Info |
Status |
Latest Expiry |
Actions |
{filteredUsers.map(user => {
const active = isUserActive(user);
const latestHistory = user.history && user.history.length > 0 ? user.history[user.history.length - 1] : null;
return (
|
{user.name}
{user.id}
{user.isStaff && STAFF}
|
{user.phone}
{user.gender} • FP: {user.fingerprint}
|
{active ? 'Active' : 'Expired'}
|
{latestHistory ? new Date(latestHistory.endDate).toLocaleDateString() : 'N/A'}
|
|
);
})}
{filteredUsers.length === 0 && (
| No users found matching filters. |
)}
{/* RENEW MODAL */}
setIsRenewModalOpen(false)} title={`Renew Membership: ${selectedUser?.name}`}>
{/* PROFILE MODAL */}
setSelectedUser(null)} title={`User Profile: ${selectedUser?.name}`}>
{selectedUser && (
System ID {selectedUser.id}
CNIC {selectedUser.cnic}
Phone {selectedUser.phone}
Gender {selectedUser.gender}
Fingerprint ID {selectedUser.fingerprint}
Role {selectedUser.isStaff ? 'Staff Member' : 'Regular Member'}
Attached File {selectedUser.fileName || 'No file attached'}
Notes {selectedUser.notes || 'None'}
Payment History
{[...selectedUser.history].reverse().map((h, i) => (
Period: {new Date(h.startDate).toLocaleDateString()} - {new Date(h.endDate).toLocaleDateString()}
Recorded on: {new Date(h.dateAdded).toLocaleString()}
${h.fee} Fee
{h.regFee > 0 &&
+${h.regFee} Reg
}
))}
)}
);
};
// ============================================================================
// PANEL C: INVENTORY
// ============================================================================
const PanelInventory = ({ inventory, setInventory, categories, setCategories, sales }) => {
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [isCategoryModalOpen, setIsCategoryModalOpen] = useState(false);
const [newCategory, setNewCategory] = useState('');
const [formData, setFormData] = useState({ name: '', category: categories[0] || '', cost: '', price: '', stock: '' });
const [selectedProduct, setSelectedProduct] = useState(null);
// Metrics
const totalProducts = inventory.length;
const totalStock = inventory.reduce((sum, item) => sum + Number(item.stock), 0);
const totalInventoryCost = inventory.reduce((sum, item) => sum + (Number(item.cost) * Number(item.stock)), 0);
// Realized profit from past sales
const totalRealizedProfit = sales.reduce((sum, s) => sum + ((s.pricePerPiece - s.costPerPiece) * s.qty), 0);
const handleAddSubmit = (e) => {
e.preventDefault();
const newId = `p_${Date.now()}`;
setInventory([...inventory, {
id: newId,
name: formData.name,
category: formData.category || categories[0],
cost: Number(formData.cost),
price: Number(formData.price),
stock: Number(formData.stock)
}]);
setIsAddModalOpen(false);
setFormData({ name: '', category: categories[0] || '', cost: '', price: '', stock: '' });
};
const handleAddCategory = (e) => {
e.preventDefault();
if(newCategory && !categories.includes(newCategory)) {
setCategories([...categories, newCategory]);
setNewCategory('');
}
};
const handleDelete = (id) => {
if(window.confirm('Delete this product?')) setInventory(inventory.filter(i => i.id !== id));
};
const getProductSales = (productId) => sales.filter(s => s.productId === productId);
return (
Inventory Management
Track products, stock, and view sales history.
| Product Name |
Category |
Cost Price |
Selling Price |
In Stock |
Actions |
{inventory.map(item => (
| {item.name} |
{item.category}
|
${item.cost} |
${item.price} |
{item.stock}
|
|
))}
{inventory.length === 0 && | No products in inventory. |
}
{/* ADD MODAL */}
setIsAddModalOpen(false)} title="Add New Product">
{/* CATEGORY MODAL */}
setIsCategoryModalOpen(false)} title="Manage Categories">
{categories.map(c => (
{c}
))}
{/* PRODUCT HISTORY MODAL */}
setSelectedProduct(null)} title={`Sales History: ${selectedProduct?.name}`}>
{selectedProduct && (() => {
const prodSales = getProductSales(selectedProduct.id);
return (
Current Stock
{selectedProduct.stock}
Unit Cost / Price
${selectedProduct.cost} / ${selectedProduct.price}
Transactions
{prodSales.map(s => (
{new Date(s.date).toLocaleString()}
Cost: ${s.costPerPiece} | Sold: ${s.pricePerPiece}
Qty: {s.qty}
Total: ${s.qty * s.pricePerPiece}
))}
{prodSales.length === 0 &&
No sales history yet.
}
)
})()}
);
};
// ============================================================================
// PANEL D: SELLING (POINT OF SALE)
// ============================================================================
const PanelSelling = ({ inventory, setInventory, sales, setSales, setActiveTab }) => {
const [selectedProductId, setSelectedProductId] = useState('');
const [qty, setQty] = useState(1);
const [sellingPrice, setSellingPrice] = useState('');
const selectedProduct = inventory.find(p => p.id === selectedProductId);
// Update default selling price when product changes
useEffect(() => {
if (selectedProduct) setSellingPrice(selectedProduct.price);
}, [selectedProductId, selectedProduct]);
const handleSale = (e) => {
e.preventDefault();
if (!selectedProduct) return alert('Select a product');
if (qty > selectedProduct.stock) return alert(`Not enough stock! Only ${selectedProduct.stock} left.`);
// Update Inventory
const updatedInventory = inventory.map(item =>
item.id === selectedProductId ? { ...item, stock: item.stock - qty } : item
);
setInventory(updatedInventory);
// Record Sale
const newSale = {
id: `sale_${Date.now()}`,
productId: selectedProduct.id,
productName: selectedProduct.name,
qty: Number(qty),
pricePerPiece: Number(sellingPrice),
costPerPiece: selectedProduct.cost,
date: new Date().toISOString()
};
setSales([...sales, newSale]);
alert('Sale recorded successfully!');
// Reset form
setSelectedProductId('');
setQty(1);
setSellingPrice('');
};
return (
Point of Sale
Record an inventory sale to a member or guest.
);
};
// ============================================================================
// PANEL E: PERSONAL FINANCES / EXPENSES
// ============================================================================
const PanelFinances = ({ users, inventory, sales, expenses, setExpenses }) => {
const [isAddExpenseModalOpen, setIsAddExpenseModalOpen] = useState(false);
const [isRenewModalOpen, setIsRenewModalOpen] = useState(false);
const [formData, setFormData] = useState({ name: '', category: '', cost: '', startDate: '', endDate: '', notes: '' });
const [selectedExpense, setSelectedExpense] = useState(null);
// --- FINANCIAL CALCULATIONS ---
// 1. Total Payment Received from Users (All history)
const totalUserPayments = users.reduce((total, user) => {
return total + (user.history || []).reduce((sum, h) => sum + Number(h.fee) + Number(h.regFee || 0), 0);
}, 0);
// 2. Profit Generated From Inventory
const totalInventoryProfit = sales.reduce((sum, s) => sum + ((s.pricePerPiece - s.costPerPiece) * s.qty), 0);
// 3. Total Costing (Inventory Base Cost of sold items + Personal Expenses)
const totalInventoryCost = sales.reduce((sum, s) => sum + (s.costPerPiece * s.qty), 0);
const totalPersonalExpenses = expenses.reduce((total, expense) => {
return total + expense.history.reduce((sum, h) => sum + Number(h.cost), 0);
}, 0);
const totalCosting = totalInventoryCost + totalPersonalExpenses;
// 4. Total Revenue Generated
const totalRevenue = totalUserPayments + (sales.reduce((sum, s) => sum + (s.pricePerPiece * s.qty), 0));
// 5. Total Final Profit (Total Revenue - Total Costing)
const totalFinalProfit = totalRevenue - totalCosting;
// --- CHART DATA GENERATION ---
// We need to group all transactions by month to create a line chart.
// We will mock this over the last 6 months based on our data.
const chartData = useMemo(() => {
const months = {};
const getMonthKey = (dStr) => { const d = new Date(dStr); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`; };
const getMonthLabel = (key) => { const d = new Date(`${key}-01`); return d.toLocaleDateString('default', {month:'short', year:'numeric'}); };
// Initialize map
[...users, ...sales, ...expenses].forEach(item => {
if(item.history) {
item.history.forEach(h => { if(h.dateAdded || h.date) months[getMonthKey(h.dateAdded || h.date)] = { revenue: 0, cost: 0, profit: 0 }; });
}
if(item.date) months[getMonthKey(item.date)] = { revenue: 0, cost: 0, profit: 0 };
});
// Populate data
users.forEach(u => u.history?.forEach(h => {
const k = getMonthKey(h.dateAdded);
if(months[k]) months[k].revenue += Number(h.fee) + Number(h.regFee || 0);
}));
sales.forEach(s => {
const k = getMonthKey(s.date);
if(months[k]) {
months[k].revenue += (s.pricePerPiece * s.qty);
months[k].cost += (s.costPerPiece * s.qty);
}
});
expenses.forEach(e => e.history?.forEach(h => {
const k = getMonthKey(h.date);
if(months[k]) months[k].cost += Number(h.cost);
}));
// Calculate profit per month and format for chart
return Object.keys(months).sort().map(key => {
months[key].profit = months[key].revenue - months[key].cost;
return { label: getMonthLabel(key), ...months[key] };
});
}, [users, sales, expenses]);
const handleAddExpense = (e) => {
e.preventDefault();
const newId = `exp_${Date.now()}`;
const newExpense = {
id: newId,
name: formData.name,
category: formData.category,
history: [{
id: `eh_${Date.now()}`,
cost: Number(formData.cost),
startDate: formData.startDate,
endDate: formData.endDate,
notes: formData.notes,
date: new Date().toISOString()
}]
};
setExpenses([...expenses, newExpense]);
setIsAddExpenseModalOpen(false);
setFormData({ name: '', category: '', cost: '', startDate: '', endDate: '', notes: '' });
};
const handleRenewExpense = (e) => {
e.preventDefault();
const updated = expenses.map(exp => {
if(exp.id === selectedExpense.id) {
return {
...exp,
history: [...exp.history, {
id: `eh_${Date.now()}`,
cost: Number(formData.cost),
startDate: formData.startDate,
endDate: formData.endDate,
notes: formData.notes,
date: new Date().toISOString()
}]
}
}
return exp;
});
setExpenses(updated);
setIsRenewModalOpen(false);
};
const openRenewModal = (expense) => {
setSelectedExpense(expense);
setFormData({ name: expense.name, category: expense.category, cost: '', startDate: '', endDate: '', notes: '' });
setIsRenewModalOpen(true);
};
return (
Financial Dashboard
Overview of revenue, profits, costs, and personal gym expenses.
{/* Primary Financial Overview Box */}
Total Revenue Generated
${totalRevenue.toLocaleString()}
(User Fees + Sales)
Total Costing
${totalCosting.toLocaleString()}
(Inventory COGS + Expenses)
Net Profit
= 0 ? 'text-green-400' : 'text-orange-500'}`}>
${totalFinalProfit.toLocaleString()}
Revenue - Costs
{/* Sub-Metrics requested in prompt */}
{/* LINE GRAPH */}
Financial Growth Over Time
{/* PERSONAL COSTING SECTION */}
Personal Gym Expenses
Manage maintenance, bills, equipment service, etc.
{expenses.map(expense => {
const totalExpCost = expense.history.reduce((s, h) => s + Number(h.cost), 0);
return (
{expense.name}
{expense.category}
Total Cost: ${totalExpCost.toLocaleString()}
{[...expense.history].reverse().map(h => (
{h.startDate} to {h.endDate}
{h.notes || 'No notes'}
${h.cost}
))}
);
})}
{expenses.length === 0 && (
No personal expense records added yet.
)}
{/* ADD EXPENSE MODAL */}
setIsAddExpenseModalOpen(false)} title="Add New Personal Costing">
{/* RENEW EXPENSE MODAL */}
setIsRenewModalOpen(false)} title={`Renew Costing: ${selectedExpense?.name}`}>
);
};