import React, { useState, useEffect } from 'react'; import { Bell, Menu, X, Home, Building, FileText, Wrench, Users, LogOut, ChevronDown, ChevronUp, Search, Plus, Edit, Trash, Check, AlertTriangle, Eye, Download, Mail, Phone, Calendar, DollarSign, BarChart2, PieChart, Clock, ArrowRight, Save, XCircle, CheckCircle, Filter, Printer, ExternalLink } from 'lucide-react'; import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title } from 'chart.js'; import { Pie, Line, Bar } from 'react-chartjs-2'; // Register ChartJS components ChartJS.register( ArcElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title ); // Mock data for demonstration const mockCompanies = [ { id: 1, name: "Madonoro Express", status: "Active", registrationNumber: "2020/123456/07", taxNumber: "9876543210", address: "123 Main Street, Johannesburg, 2000", contactPerson: "John Smith", email: "info@madonoro.com", phone: "+27 11 123 4567", documents: [ { id: 1, name: "Company Registration", type: "PDF", date: "2022-01-15" }, { id: 2, name: "Tax Clearance", type: "PDF", date: "2023-03-20" }, { id: 3, name: "Banking Confirmation", type: "PDF", date: "2023-02-10" } ], credentials: { cipc: { username: "madonoro_cipc", password: "********" }, sars: { username: "madonoro_sars", password: "********", efiling: "1234567890" }, csd: { username: "madonoro_csd", password: "********", supplierNumber: "MADONORO001" } } }, { id: 2, name: "Tech Solutions Ltd", status: "Active", registrationNumber: "2019/654321/07", taxNumber: "1234567890", address: "456 Tech Avenue, Cape Town, 8001", contactPerson: "Sarah Johnson", email: "info@techsolutions.co.za", phone: "+27 21 987 6543", documents: [ { id: 4, name: "Company Registration", type: "PDF", date: "2019-05-22" }, { id: 5, name: "Tax Clearance", type: "PDF", date: "2023-01-05" } ], credentials: { cipc: { username: "tech_cipc", password: "********" }, sars: { username: "tech_sars", password: "********", efiling: "0987654321" }, csd: { username: "tech_csd", password: "********", supplierNumber: "TECHSOL002" } } }, { id: 3, name: "Digital Innovations", status: "Inactive", registrationNumber: "2018/112233/07", taxNumber: "2233445566", address: "789 Innovation Park, Durban, 4001", contactPerson: "Michael Brown", email: "contact@digitalinnovations.co.za", phone: "+27 31 456 7890", documents: [ { id: 6, name: "Company Registration", type: "PDF", date: "2018-11-10" } ], credentials: { cipc: { username: "digital_cipc", password: "********" }, sars: { username: "digital_sars", password: "********", efiling: "5566778899" }, csd: { username: "digital_csd", password: "********", supplierNumber: "DIGINV003" } } } ]; const mockCustomers = [ { id: 1, name: "John Doe", email: "john.doe@example.com", phone: "+27 71 234 5678", address: "10 Customer Street, Johannesburg, 2000", company: "ABC Corporation", notes: "Regular customer, prefers email communication" }, { id: 2, name: "Jane Smith", email: "jane.smith@example.com", phone: "+27 82 345 6789", address: "20 Client Avenue, Pretoria, 0001", company: "XYZ Ltd", notes: "VIP customer, requires priority service" }, { id: 3, name: "Bob Johnson", email: "bob.johnson@example.com", phone: "+27 63 456 7890", address: "30 User Road, Cape Town, 8001", company: "123 Industries", notes: "New customer, referred by Jane Smith" }, { id: 4, name: "Alice Williams", email: "alice.williams@example.com", phone: "+27 74 567 8901", address: "40 Person Lane, Durban, 4001", company: "Williams Enterprises", notes: "Prefers phone calls over emails" }, { id: 5, name: "Charlie Brown", email: "charlie.brown@example.com", phone: "+27 85 678 9012", address: "50 Individual Boulevard, Bloemfontein, 9301", company: "Brown & Associates", notes: "Has multiple devices for repair" } ]; const mockInventory = [ { id: 1, name: "Laptop Screen 15.6\"", category: "Parts", quantity: 15, price: 1200.00, threshold: 5 }, { id: 2, name: "Laptop Battery", category: "Parts", quantity: 20, price: 850.00, threshold: 8 }, { id: 3, name: "Smartphone Screen Protector", category: "Accessories", quantity: 50, price: 150.00, threshold: 15 }, { id: 4, name: "Thermal Paste", category: "Consumables", quantity: 30, price: 75.00, threshold: 10 }, { id: 5, name: "Hard Drive 1TB", category: "Parts", quantity: 12, price: 950.00, threshold: 5 }, { id: 6, name: "SSD 500GB", category: "Parts", quantity: 8, price: 1500.00, threshold: 3 }, { id: 7, name: "RAM 8GB DDR4", category: "Parts", quantity: 25, price: 650.00, threshold: 10 }, { id: 8, name: "Screwdriver Set", category: "Tools", quantity: 5, price: 350.00, threshold: 2 }, { id: 9, name: "Anti-Static Wrist Strap", category: "Tools", quantity: 7, price: 120.00, threshold: 3 }, { id: 10, name: "Smartphone Battery", category: "Parts", quantity: 4, price: 450.00, threshold: 5 } ]; const mockRepairTickets = [ { id: 1, customer: "John Doe", customerId: 1, device: "Laptop", brand: "Dell", model: "Inspiron 15", serialNumber: "DL12345678", problem: "Won't boot", description: "Device won't power on at all. No lights, no fan noise.", diagnosis: "Faulty power supply or motherboard issue", solution: "Replaced power supply board", status: "Completed", priority: "Medium", assignedTo: "Tech User", estimatedCost: 850.00, actualCost: 950.00, partsUsed: [ { id: 2, name: "Laptop Battery", quantity: 1, price: 850.00 } ], laborHours: 1.5, laborRate: 350.00, createdDate: "2023-05-14", completedDate: "2023-05-16", notes: [ { id: 1, date: "2023-05-14", user: "Staff User", text: "Customer dropped off laptop" }, { id: 2, date: "2023-05-15", user: "Tech User", text: "Diagnosed as power supply issue" }, { id: 3, date: "2023-05-16", user: "Tech User", text: "Replaced power supply, tested and working" } ] }, { id: 2, customer: "Jane Smith", customerId: 2, device: "iPhone", brand: "Apple", model: "iPhone 13", serialNumber: "AP98765432", problem: "Cracked screen", description: "Screen is cracked in the bottom right corner. Touch still works.", diagnosis: "Physical damage to screen, needs replacement", solution: "", status: "In Progress", priority: "High", assignedTo: "Tech User", estimatedCost: 1500.00, actualCost: 0, partsUsed: [], laborHours: 0, laborRate: 350.00, createdDate: "2023-05-16", completedDate: "", notes: [ { id: 4, date: "2023-05-16", user: "Staff User", text: "Customer brought in iPhone with cracked screen" }, { id: 5, date: "2023-05-16", user: "Tech User", text: "Ordered replacement screen, ETA 2 days" } ] }, { id: 3, customer: "Bob Johnson", customerId: 3, device: "Desktop PC", brand: "Custom Build", model: "N/A", serialNumber: "N/A", problem: "Blue screen error", description: "Computer randomly shows blue screen with error code MEMORY_MANAGEMENT", diagnosis: "Possible RAM issue or driver conflict", solution: "", status: "Pending", priority: "Low", assignedTo: "", estimatedCost: 650.00, actualCost: 0, partsUsed: [], laborHours: 0, laborRate: 350.00, createdDate: "2023-05-15", completedDate: "", notes: [ { id: 6, date: "2023-05-15", user: "Staff User", text: "Customer reported blue screen errors" } ] }, { id: 4, customer: "Alice Williams", customerId: 4, device: "Printer", brand: "HP", model: "LaserJet Pro", serialNumber: "HP45678901", problem: "Paper jam", description: "Frequent paper jams, error message on display", diagnosis: "Worn roller assembly", solution: "", status: "Pending", priority: "Medium", assignedTo: "", estimatedCost: 450.00, actualCost: 0, partsUsed: [], laborHours: 0, laborRate: 350.00, createdDate: "2023-05-17", completedDate: "", notes: [ { id: 7, date: "2023-05-17", user: "Staff User", text: "Customer dropped off printer" } ] }, { id: 5, customer: "Charlie Brown", customerId: 5, device: "Tablet", brand: "Samsung", model: "Galaxy Tab S7", serialNumber: "SG12345678", problem: "Not charging", description: "Device won't charge with any cable or charger", diagnosis: "Damaged charging port", solution: "Replaced charging port assembly", status: "Completed", priority: "High", assignedTo: "Tech User", estimatedCost: 750.00, actualCost: 750.00, partsUsed: [], laborHours: 2, laborRate: 350.00, createdDate: "2023-05-13", completedDate: "2023-05-15", notes: [ { id: 8, date: "2023-05-13", user: "Staff User", text: "Customer reported charging issues" }, { id: 9, date: "2023-05-14", user: "Tech User", text: "Confirmed charging port damage" }, { id: 10, date: "2023-05-15", user: "Tech User", text: "Replaced charging port, tested and working" } ] } ]; const mockInvoices = [ { id: 1, invoiceNumber: "INV-0001", client: "ABC Corp", clientId: 1, amount: 1250.00, tax: 187.50, total: 1437.50, status: "Paid", date: "2023-05-10", dueDate: "2023-05-25", paidDate: "2023-05-15", items: [ { id: 1, description: "Laptop Repair Service", quantity: 1, unitPrice: 850.00, total: 850.00 }, { id: 2, description: "Replacement Battery", quantity: 1, unitPrice: 400.00, total: 400.00 } ], notes: "Payment received via bank transfer", terms: "Payment due within 15 days of invoice date" }, { id: 2, invoiceNumber: "INV-0002", client: "XYZ Ltd", clientId: 2, amount: 3450.75, tax: 517.61, total: 3968.36, status: "Unpaid", date: "2023-05-12", dueDate: "2023-05-27", paidDate: "", items: [ { id: 3, description: "Network Setup", quantity: 1, unitPrice: 2500.00, total: 2500.00 }, { id: 4, description: "Router Configuration", quantity: 3, unitPrice: 316.92, total: 950.75 } ], notes: "Client requested itemized receipt", terms: "Payment due within 15 days of invoice date" }, { id: 3, invoiceNumber: "INV-0003", client: "123 Industries", clientId: 3, amount: 875.50, tax: 131.33, total: 1006.83, status: "Partial", date: "2023-05-08", dueDate: "2023-05-23", paidDate: "", items: [ { id: 5, description: "Computer Diagnostics", quantity: 1, unitPrice: 350.00, total: 350.00 }, { id: 6, description: "Virus Removal", quantity: 1, unitPrice: 525.50, total: 525.50 } ], notes: "Partial payment of R500 received on 2023-05-15", terms: "Payment due within 15 days of invoice date" }, { id: 4, invoiceNumber: "INV-0004", client: "Williams Enterprises", clientId: 4, amount: 1750.00, tax: 262.50, total: 2012.50, status: "Unpaid", date: "2023-05-14", dueDate: "2023-05-29", paidDate: "", items: [ { id: 7, description: "Printer Repair", quantity: 1, unitPrice: 450.00, total: 450.00 }, { id: 8, description: "Annual Maintenance Contract", quantity: 1, unitPrice: 1300.00, total: 1300.00 } ], notes: "", terms: "Payment due within 15 days of invoice date" }, { id: 5, invoiceNumber: "INV-0005", client: "Brown & Associates", clientId: 5, amount: 2250.00, tax: 337.50, total: 2587.50, status: "Paid", date: "2023-05-05", dueDate: "2023-05-20", paidDate: "2023-05-18", items: [ { id: 9, description: "Tablet Repair", quantity: 1, unitPrice: 750.00, total: 750.00 }, { id: 10, description: "Data Recovery Service", quantity: 1, unitPrice: 1500.00, total: 1500.00 } ], notes: "Payment received via credit card", terms: "Payment due within 15 days of invoice date" } ]; const mockQuotations = [ { id: 1, quoteNumber: "Q-0001", client: "DEF Solutions", clientId: 1, amount: 2750.00, tax: 412.50, total: 3162.50, status: "Pending", date: "2023-05-11", validUntil: "2023-06-11", items: [ { id: 1, description: "Server Maintenance", quantity: 1, unitPrice: 1500.00, total: 1500.00 }, { id: 2, description: "Backup Solution Setup", quantity: 1, unitPrice: 1250.00, total: 1250.00 } ], notes: "Quote includes labor and parts", terms: "Quote valid for 30 days" }, { id: 2, quoteNumber: "Q-0002", client: "GHI Services", clientId: 2, amount: 1890.25, tax: 283.54, total: 2173.79, status: "Accepted", date: "2023-05-13", validUntil: "2023-06-13", items: [ { id: 3, description: "Website Development", quantity: 1, unitPrice: 1500.00, total: 1500.00 }, { id: 4, description: "Domain Registration", quantity: 1, unitPrice: 390.25, total: 390.25 } ], notes: "Client accepted quote on 2023-05-15", terms: "Quote valid for 30 days" }, { id: 3, quoteNumber: "Q-0003", client: "JKL Corp", clientId: 3, amount: 4320.00, tax: 648.00, total: 4968.00, status: "Rejected", date: "2023-05-09", validUntil: "2023-06-09", items: [ { id: 5, description: "Office Network Setup", quantity: 1, unitPrice: 3500.00, total: 3500.00 }, { id: 6, description: "Security Camera Installation", quantity: 1, unitPrice: 820.00, total: 820.00 } ], notes: "Client rejected quote on 2023-05-12 - too expensive", terms: "Quote valid for 30 days" }, { id: 4, quoteNumber: "Q-0004", client: "MNO Enterprises", clientId: 4, amount: 1250.00, tax: 187.50, total: 1437.50, status: "Pending", date: "2023-05-16", validUntil: "2023-06-16", items: [ { id: 7, description: "Computer Repair", quantity: 1, unitPrice: 750.00, total: 750.00 }, { id: 8, description: "Software Installation", quantity: 1, unitPrice: 500.00, total: 500.00 } ], notes: "Awaiting client response", terms: "Quote valid for 30 days" }, { id: 5, quoteNumber: "Q-0005", client: "PQR Ltd", clientId: 5, amount: 3750.00, tax: 562.50, total: 4312.50, status: "Accepted", date: "2023-05-08", validUntil: "2023-06-08", items: [ { id: 9, description: "Annual IT Support Contract", quantity: 1, unitPrice: 3750.00, total: 3750.00 } ], notes: "Client accepted quote on 2023-05-10", terms: "Quote valid for 30 days" } ]; // Dashboard chart data const revenueData = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], datasets: [ { label: 'Monthly Revenue', data: [12500, 15000, 18750, 16200, 21000, 19500], borderColor: 'rgb(229, 62, 62)', backgroundColor: 'rgba(229, 62, 62, 0.5)', }, ], }; const repairStatusData = { labels: ['Pending', 'In Progress', 'Completed'], datasets: [ { label: 'Repair Status', data: [8, 5, 15], backgroundColor: [ 'rgba(255, 206, 86, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(75, 192, 192, 0.6)', ], borderColor: [ 'rgba(255, 206, 86, 1)', 'rgba(54, 162, 235, 1)', 'rgba(75, 192, 192, 1)', ], borderWidth: 1, }, ], }; const quotationConversionData = { labels: ['Accepted', 'Rejected', 'Pending'], datasets: [ { label: 'Quotation Status', data: [12, 5, 8], backgroundColor: [ 'rgba(75, 192, 192, 0.6)', 'rgba(255, 99, 132, 0.6)', 'rgba(255, 206, 86, 0.6)', ], borderColor: [ 'rgba(75, 192, 192, 1)', 'rgba(255, 99, 132, 1)', 'rgba(255, 206, 86, 1)', ], borderWidth: 1, }, ], }; // Mock API service const ApiService = { login: (username, password) => { return new Promise((resolve, reject) => { setTimeout(() => { if (username === 'admin' && password === 'password') { resolve({ name: 'Admin User', role: 'admin', email: 'admin@madonoro.com' }); } else if (username === 'staff' && password === 'password') { resolve({ name: 'Staff User', role: 'staff', email: 'staff@madonoro.com' }); } else if (username === 'tech' && password === 'password') { resolve({ name: 'Tech User', role: 'technician', email: 'tech@madonoro.com' }); } else if (username === 'accounting' && password === 'password') { resolve({ name: 'Accounting User', role: 'accounting', email: 'accounting@madonoro.com' }); } else { reject(new Error('Invalid username or password')); } }, 1000); }); }, getCompanies: () => { return new Promise((resolve) => { setTimeout(() => { resolve(mockCompanies); }, 500); }); }, getCompanyById: (id) => { return new Promise((resolve, reject) => { setTimeout(() => { const company = mockCompanies.find(c => c.id === id); if (company) { resolve(company); } else { reject(new Error('Company not found')); } }, 500); }); }, createCompany: (company) => { return new Promise((resolve) => { setTimeout(() => { const newCompany = { id: mockCompanies.length + 1, ...company, documents: [], credentials: { cipc: { username: "", password: "" }, sars: { username: "", password: "", efiling: "" }, csd: { username: "", password: "", supplierNumber: "" } } }; mockCompanies.push(newCompany); resolve(newCompany); }, 500); }); }, updateCompany: (id, company) => { return new Promise((resolve, reject) => { setTimeout(() => { const index = mockCompanies.findIndex(c => c.id === id); if (index !== -1) { mockCompanies[index] = { ...mockCompanies[index], ...company }; resolve(mockCompanies[index]); } else { reject(new Error('Company not found')); } }, 500); }); }, deleteCompany: (id) => { return new Promise((resolve, reject) => { setTimeout(() => { const index = mockCompanies.findIndex(c => c.id === id); if (index !== -1) { mockCompanies.splice(index, 1); resolve({ success: true }); } else { reject(new Error('Company not found')); } }, 500); }); }, getCustomers: () => { return new Promise((resolve) => { setTimeout(() => { resolve(mockCustomers); }, 500); }); }, getRepairTickets: () => { return new Promise((resolve) => { setTimeout(() => { resolve(mockRepairTickets); }, 500); }); }, getRepairTicketById: (id) => { return new Promise((resolve, reject) => { setTimeout(() => { const ticket = mockRepairTickets.find(t => t.id === id); if (ticket) { resolve(ticket); } else { reject(new Error('Repair ticket not found')); } }, 500); }); }, createRepairTicket: (ticket) => { return new Promise((resolve) => { setTimeout(() => { const newTicket = { id: mockRepairTickets.length + 1, ...ticket, createdDate: new Date().toISOString().split('T')[0], completedDate: "", notes: [ { id: Math.max(...mockRepairTickets.flatMap(t => t.notes.map(n => n.id)), 0) + 1, date: new Date().toISOString().split('T')[0], user: "System", text: "Ticket created" } ] }; mockRepairTickets.push(newTicket); resolve(newTicket); }, 500); }); }, updateRepairTicket: (id, ticket) => { return new Promise((resolve, reject) => { setTimeout(() => { const index = mockRepairTickets.findIndex(t => t.id === id); if (index !== -1) { mockRepairTickets[index] = { ...mockRepairTickets[index], ...ticket }; resolve(mockRepairTickets[index]); } else { reject(new Error('Repair ticket not found')); } }, 500); }); }, getInvoices: () => { return new Promise((resolve) => { setTimeout(() => { resolve(mockInvoices); }, 500); }); }, getInvoiceById: (id) => { return new Promise((resolve, reject) => { setTimeout(() => { const invoice = mockInvoices.find(i => i.id === id); if (invoice) { resolve(invoice); } else { reject(new Error('Invoice not found')); } }, 500); }); }, createInvoice: (invoice) => { return new Promise((resolve) => { setTimeout(() => { const newInvoice = { id: mockInvoices.length + 1, invoiceNumber: `INV-${(mockInvoices.length + 1).toString().padStart(4, '0')}`, ...invoice, date: new Date().toISOString().split('T')[0] }; mockInvoices.push(newInvoice); resolve(newInvoice); }, 500); }); }, updateInvoice: (id, invoice) => { return new Promise((resolve, reject) => { setTimeout(() => { const index = mockInvoices.findIndex(i => i.id === id); if (index !== -1) { mockInvoices[index] = { ...mockInvoices[index], ...invoice }; resolve(mockInvoices[index]); } else { reject(new Error('Invoice not found')); } }, 500); }); }, getQuotations: () => { return new Promise((resolve) => { setTimeout(() => { resolve(mockQuotations); }, 500); }); }, getQuotationById: (id) => { return new Promise((resolve, reject) => { setTimeout(() => { const quotation = mockQuotations.find(q => q.id === id); if (quotation) { resolve(quotation); } else { reject(new Error('Quotation not found')); } }, 500); }); }, createQuotation: (quotation) => { return new Promise((resolve) => { setTimeout(() => { const newQuotation = { id: mockQuotations.length + 1, quoteNumber: `Q-${(mockQuotations.length + 1).toString().padStart(4, '0')}`, ...quotation, date: new Date().toISOString().split('T')[0], validUntil: new Date(new Date().setDate(new Date().getDate() + 30)).toISOString().split('T')[0] }; mockQuotations.push(newQuotation); resolve(newQuotation); }, 500); }); }, updateQuotation: (id, quotation) => { return new Promise((resolve, reject) => { setTimeout(() => { const index = mockQuotations.findIndex(q => q.id === id); if (index !== -1) { mockQuotations[index] = { ...mockQuotations[index], ...quotation }; resolve(mockQuotations[index]); } else { reject(new Error('Quotation not found')); } }, 500); }); }, convertQuotationToInvoice: (id) => { return new Promise((resolve, reject) => { setTimeout(() => { const quotation = mockQuotations.find(q => q.id === id); if (quotation) { const newInvoice = { id: mockInvoices.length + 1, invoiceNumber: `INV-${(mockInvoices.length + 1).toString().padStart(4, '0')}`, client: quotation.client, clientId: quotation.clientId, amount: quotation.amount, tax: quotation.tax, total: quotation.total, status: "Unpaid", date: new Date().toISOString().split('T')[0], dueDate: new Date(new Date().setDate(new Date().getDate() + 15)).toISOString().split('T')[0], paidDate: "", items: quotation.items, notes: `Generated from quotation ${quotation.quoteNumber}`, terms: "Payment due within 15 days of invoice date" }; mockInvoices.push(newInvoice); // Update quotation status const index = mockQuotations.findIndex(q => q.id === id); mockQuotations[index] = { ...mockQuotations[index], status: "Accepted" }; resolve(newInvoice); } else { reject(new Error('Quotation not found')); } }, 500); }); }, getInventory: () => { return new Promise((resolve) => { setTimeout(() => { resolve(mockInventory); }, 500); }); }, updateInventoryItem: (id, item) => { return new Promise((resolve, reject) => { setTimeout(() => { const index = mockInventory.findIndex(i => i.id === id); if (index !== -1) { mockInventory[index] = { ...mockInventory[index], ...item }; resolve(mockInventory[index]); } else { reject(new Error('Inventory item not found')); } }, 500); }); }, getDashboardStats: () => { return new Promise((resolve) => { setTimeout(() => { resolve({ activeCompanies: mockCompanies.filter(c => c.status === "Active").length, pendingRepairs: mockRepairTickets.filter(t => t.status === "Pending" || t.status === "In Progress").length, unpaidInvoices: mockInvoices.filter(i => i.status === "Unpaid" || i.status === "Partial").length, quotesThisMonth: mockQuotations.filter(q => { const quoteDate = new Date(q.date); const currentDate = new Date(); return quoteDate.getMonth() === currentDate.getMonth() && quoteDate.getFullYear() === currentDate.getFullYear(); }).length, revenueData, repairStatusData, quotationConversionData, lowStockItems: mockInventory.filter(i => i.quantity <= i.threshold).length }); }, 500); }); } }; // Utility functions const formatCurrency = (amount) => { return new Intl.NumberFormat('en-ZA', { style: 'currency', currency: 'ZAR' }).format(amount); }; const formatDate = (dateString) => { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('en-ZA'); }; // Toast notification component const Toast = ({ message, type, onClose }) => { const [visible, setVisible] = useState(true); useEffect(() => { const timer = setTimeout(() => { setVisible(false); setTimeout(onClose, 300); }, 5000); return () => clearTimeout(timer); }, [onClose]); return (
{type === 'success' ? ( ) : type === 'error' ? ( ) : ( )}

{message}

); }; // Modal component const Modal = ({ title, children, isOpen, onClose, size = 'md' }) => { if (!isOpen) return null; const sizeClasses = { sm: 'max-w-md', md: 'max-w-2xl', lg: 'max-w-4xl', xl: 'max-w-6xl' }; return (
​

{title}

{children}
); }; // Loading spinner component const LoadingSpinner = ({ size = 'md', color = 'red' }) => { const sizeClasses = { sm: 'h-4 w-4', md: 'h-8 w-8', lg: 'h-12 w-12' }; const colorClasses = { red: 'text-red-600', blue: 'text-blue-600', green: 'text-green-600', gray: 'text-gray-600' }; return (
); }; // Empty state component const EmptyState = ({ title, description, icon: Icon, action }) => { return (

{title}

{description}

{action && (
)}
); }; // Main App Component export default function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [currentUser, setCurrentUser] = useState(null); const [sidebarOpen, setSidebarOpen] = useState(true); const [activeModule, setActiveModule] = useState('dashboard'); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [loading, setLoading] = useState(false); const [toasts, setToasts] = useState([]); // Add toast notification const addToast = (message, type = 'info') => { const id = Date.now(); setToasts(prevToasts => [...prevToasts, { id, message, type }]); }; // Remove toast notification const removeToast = (id) => { setToasts(prevToasts => prevToasts.filter(toast => toast.id !== id)); }; // Login handler const handleLogin = async (username, password) => { setLoading(true); try { const userData = await ApiService.login(username, password); setCurrentUser(userData); setIsLoggedIn(true); addToast(`Welcome, ${userData.name}!`, 'success'); } catch (error) { addToast(error.message, 'error'); } finally { setLoading(false); } }; // Logout handler const handleLogout = () => { setCurrentUser(null); setIsLoggedIn(false); addToast('You have been logged out.', 'info'); }; return (
{/* Toast notifications */}
{toasts.map(toast => ( removeToast(toast.id)} /> ))}
{!isLoggedIn ? ( ) : (
{/* Sidebar */} {/* Main Content */}
{activeModule === 'dashboard' && } {activeModule === 'companies' && } {activeModule === 'quotations' && } {activeModule === 'invoices' && } {activeModule === 'repairs' && } {activeModule === 'customers' && }
)}
); } // Login Page Component function LoginPage({ onLogin, loading }) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const handleSubmit = (e) => { e.preventDefault(); if (!username || !password) { setError('Please enter both username and password'); return; } setError(''); onLogin(username, password); }; return (

Madonoro Express Services

Business Management System

{error && (

{error}

)}
setUsername(e.target.value)} className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" disabled={loading} />
setPassword(e.target.value)} className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" disabled={loading} />
Demo Credentials

Admin: admin / password

Staff: staff / password

Tech: tech / password

Accounting: accounting / password

); } // Sidebar Component function Sidebar({ isOpen, setIsOpen, activeModule, setActiveModule, onLogout, mobileMenuOpen, setMobileMenuOpen, userRole }) { const navItems = [ { id: 'dashboard', name: 'Dashboard', icon: Home, roles: ['admin', 'staff', 'technician', 'accounting'] }, { id: 'companies', name: 'Companies', icon: Building, roles: ['admin', 'staff'] }, { id: 'quotations', name: 'Quotations', icon: FileText, roles: ['admin', 'staff', 'accounting'] }, { id: 'invoices', name: 'Invoices', icon: FileText, roles: ['admin', 'staff', 'accounting'] }, { id: 'repairs', name: 'Repair Tickets', icon: Wrench, roles: ['admin', 'staff', 'technician'] }, { id: 'customers', name: 'Customers', icon: Users, roles: ['admin', 'staff', 'technician'] }, ]; // Filter nav items based on user role const filteredNavItems = navItems.filter(item => item.roles.includes(userRole)); return ( <> {/* Desktop sidebar */}
{/* Sidebar header */}
{isOpen && (
Madonoro
Express Services
)}
{/* Sidebar content */}
{/* Logout button */}
{/* Mobile menu */}
setMobileMenuOpen(false)}>
Madonoro
Express Services
); } // Header Component function Header({ setSidebarOpen, sidebarOpen, user, setMobileMenuOpen, addToast }) { const [notifications, setNotifications] = useState([ { id: 1, message: "New repair ticket assigned to you", time: "5 minutes ago", read: false }, { id: 2, message: "Invoice #INV-0002 is overdue", time: "1 hour ago", read: false }, { id: 3, message: "Low inventory alert: Laptop Battery", time: "3 hours ago", read: true } ]); const [showNotifications, setShowNotifications] = useState(false); const unreadCount = notifications.filter(n => !n.read).length; const markAsRead = (id) => { setNotifications(prevNotifications => prevNotifications.map(n => n.id === id ? { ...n, read: true } : n ) ); }; const markAllAsRead = () => { setNotifications(prevNotifications => prevNotifications.map(n => ({ ...n, read: true })) ); addToast('All notifications marked as read', 'success'); }; return (
{/* Notifications dropdown */} {showNotifications && (

Notifications

{unreadCount > 0 && ( )}
{notifications.length === 0 ? (
No notifications
) : ( notifications.map((notification) => (
markAsRead(notification.id)} >

{notification.message}

{notification.time}

{!notification.read && (
)}
)) )}
)}
{user?.name?.charAt(0)}
{user?.name}
{user?.role}
); } // Dashboard Component function Dashboard({ addToast }) { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchDashboardData = async () => { setLoading(true); try { const data = await ApiService.getDashboardStats(); setStats(data); } catch (error) { addToast('Failed to load dashboard data', 'error'); } finally { setLoading(false); } }; fetchDashboardData(); }, [addToast]); if (loading) { return (
); } return (

Dashboard

{/* Stats Cards */}
Active Companies
{stats.activeCompanies}
Pending Repairs
{stats.pendingRepairs}
Unpaid Invoices
{stats.unpaidInvoices}
Low Stock Items
{stats.lowStockItems}
{/* Charts */}

Monthly Revenue

Repair Status

{/* Recent Activity */}
{/* Recent Repair Tickets */}

Recent Repair Tickets

{mockRepairTickets.slice(0, 3).map((ticket) => ( ))}
Customer Device Status
{ticket.customer} {ticket.device} {ticket.status}
{/* Recent Invoices */}

Recent Invoices

{mockInvoices.slice(0, 3).map((invoice) => ( ))}
Client Amount Status
{invoice.client} {formatCurrency(invoice.amount)} {invoice.status}
); } // Company Management Component function CompanyManagement({ addToast }) { const [companies, setCompanies] = useState([]); const [loading, setLoading] = useState(true); const [showAddForm, setShowAddForm] = useState(false); const [showViewModal, setShowViewModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [currentCompany, setCurrentCompany] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [formData, setFormData] = useState({ name: '', registrationNumber: '', taxNumber: '', status: 'Active', address: '', contactPerson: '', email: '', phone: '' }); useEffect(() => { const fetchCompanies = async () => { setLoading(true); try { const data = await ApiService.getCompanies(); setCompanies(data); } catch (error) { addToast('Failed to load companies', 'error'); } finally { setLoading(false); } }; fetchCompanies(); }, [addToast]); const filteredCompanies = companies.filter(company => company.name.toLowerCase().includes(searchTerm.toLowerCase()) || company.registrationNumber.includes(searchTerm) || company.taxNumber.includes(searchTerm) ); const handleInputChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); try { const newCompany = await ApiService.createCompany(formData); setCompanies(prev => [...prev, newCompany]); setShowAddForm(false); setFormData({ name: '', registrationNumber: '', taxNumber: '', status: 'Active', address: '', contactPerson: '', email: '', phone: '' }); addToast('Company added successfully', 'success'); } catch (error) { addToast('Failed to add company', 'error'); } finally { setLoading(false); } }; const handleDelete = async () => { if (!currentCompany) return; setLoading(true); try { await ApiService.deleteCompany(currentCompany.id); setCompanies(prev => prev.filter(company => company.id !== currentCompany.id)); setShowDeleteModal(false); setCurrentCompany(null); addToast('Company deleted successfully', 'success'); } catch (error) { addToast('Failed to delete company', 'error'); } finally { setLoading(false); } }; const viewCompany = (company) => { setCurrentCompany(company); setShowViewModal(true); }; const confirmDelete = (company) => { setCurrentCompany(company); setShowDeleteModal(true); }; if (loading && companies.length === 0) { return (
); } return (

Company Management

{/* Search */}
setSearchTerm(e.target.value)} />
{/* Add Company Form */} {showAddForm && (

Add New Company

)} {/* Companies List */} {filteredCompanies.length === 0 ? ( setShowAddForm(true) }} /> ) : (
    {filteredCompanies.map((company) => (
  • {company.name}
    Reg: {company.registrationNumber}
    {company.status}
    Tax Number: {company.taxNumber}
  • ))}
)} {/* View Company Modal */} setShowViewModal(false)} size="lg" > {currentCompany && (

Company Name

{currentCompany.name}

Registration Number

{currentCompany.registrationNumber}

Tax Number

{currentCompany.taxNumber}

Status

{currentCompany.status}

Address

{currentCompany.address || 'N/A'}

Contact Person

{currentCompany.contactPerson || 'N/A'}

Email

{currentCompany.email || 'N/A'}

Phone

{currentCompany.phone || 'N/A'}

Secure Credentials

CIPC Login

Username: {currentCompany.credentials?.cipc?.username || 'Not set'}

Password: ********

SARS Information

Username: {currentCompany.credentials?.sars?.username || 'Not set'}

Password: ********

eFiling: {currentCompany.credentials?.sars?.efiling || 'Not set'}

CSD Credentials

Username: {currentCompany.credentials?.csd?.username || 'Not set'}

Password: ********

Supplier Number: {currentCompany.credentials?.csd?.supplierNumber || 'Not set'}

Documents

{currentCompany.documents && currentCompany.documents.length > 0 ? (
    {currentCompany.documents.map(doc => (
  • {doc.name}

    Uploaded on {formatDate(doc.date)}

  • ))}
) : (

No documents uploaded yet.

)}
)}
{/* Delete Confirmation Modal */} setShowDeleteModal(false)} size="sm" >

Are you sure you want to delete {currentCompany?.name}? This action cannot be undone.

); } // Quotation Management Component function QuotationManagement({ addToast }) { const [quotations, setQuotations] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('All'); const [showViewModal, setShowViewModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false); const [currentQuotation, setCurrentQuotation] = useState(null); const [customers, setCustomers] = useState([]); useEffect(() => { const fetchData = async () => { setLoading(true); try { const [quotationsData, customersData] = await Promise.all([ ApiService.getQuotations(), ApiService.getCustomers() ]); setQuotations(quotationsData); setCustomers(customersData); } catch (error) { addToast('Failed to load quotations', 'error'); } finally { setLoading(false); } }; fetchData(); }, [addToast]); const filteredQuotations = quotations.filter(quote => (quote.client.toLowerCase().includes(searchTerm.toLowerCase())) && (statusFilter === 'All' || quote.status === statusFilter) ); const viewQuotation = (quotation) => { setCurrentQuotation(quotation); setShowViewModal(true); }; const convertToInvoice = async (quotationId) => { setLoading(true); try { const invoice = await ApiService.convertQuotationToInvoice(quotationId); // Update the quotation status in the local state setQuotations(prev => prev.map(q => q.id === quotationId ? { ...q, status: 'Accepted' } : q ) ); addToast('Quotation converted to invoice successfully', 'success'); setShowViewModal(false); } catch (error) { addToast('Failed to convert quotation to invoice', 'error'); } finally { setLoading(false); } }; if (loading && quotations.length === 0) { return (
); } return (

Quotation Management

{/* Filters */}
setSearchTerm(e.target.value)} />
{/* Quotations Table */} {filteredQuotations.length === 0 ? ( setShowCreateModal(true) }} /> ) : (
{filteredQuotations.map((quote) => ( ))}
Quote # Client Amount Date Valid Until Status Actions
{quote.quoteNumber} {quote.client} {formatCurrency(quote.total)} {formatDate(quote.date)} {formatDate(quote.validUntil)} {quote.status}
{quote.status === 'Pending' && ( )}
)} {/* View Quotation Modal */} setShowViewModal(false)} size="lg" > {currentQuotation && (

Client

{currentQuotation.client}

Status

{currentQuotation.status}

Date

{formatDate(currentQuotation.date)}

Valid Until

{formatDate(currentQuotation.validUntil)}

Items

{currentQuotation.items.map((item) => ( ))}
Description Quantity Unit Price Total
{item.description} {item.quantity} {formatCurrency(item.unitPrice)} {formatCurrency(item.total)}
Subtotal {formatCurrency(currentQuotation.amount)}
VAT (15%) {formatCurrency(currentQuotation.tax)}
Total {formatCurrency(currentQuotation.total)}
{currentQuotation.notes && (

Notes

{currentQuotation.notes}

)}

Terms

{currentQuotation.terms}

{currentQuotation.status === 'Pending' && ( )}
)}
{/* Create Quotation Modal */} setShowCreateModal(false)} size="xl" > setShowCreateModal(false)} onSuccess={(newQuotation) => { setQuotations(prev => [...prev, newQuotation]); setShowCreateModal(false); addToast('Quotation created successfully', 'success'); }} addToast={addToast} />
); } // Quotation Form Component function QuotationForm({ customers, onClose, onSuccess, addToast }) { const [loading, setLoading] = useState(false); const [formData, setFormData] = useState({ clientId: '', items: [{ id: 1, description: '', quantity: 1, unitPrice: 0, total: 0 }], notes: '', terms: 'Quote valid for 30 days' }); const calculateTotals = () => { const amount = formData.items.reduce((sum, item) => sum + item.total, 0); const tax = amount * 0.15; const total = amount + tax; return { amount, tax, total }; }; const { amount, tax, total } = calculateTotals(); const handleClientChange = (e) => { setFormData(prev => ({ ...prev, clientId: e.target.value })); }; const handleItemChange = (index, field, value) => { const updatedItems = [...formData.items]; updatedItems[index][field] = value; // Recalculate total for this item if (field === 'quantity' || field === 'unitPrice') { updatedItems[index].total = updatedItems[index].quantity * updatedItems[index].unitPrice; } setFormData(prev => ({ ...prev, items: updatedItems })); }; const addItem = () => { const newId = Math.max(...formData.items.map(item => item.id), 0) + 1; setFormData(prev => ({ ...prev, items: [...prev.items, { id: newId, description: '', quantity: 1, unitPrice: 0, total: 0 }] })); }; const removeItem = (index) => { if (formData.items.length === 1) return; const updatedItems = [...formData.items]; updatedItems.splice(index, 1); setFormData(prev => ({ ...prev, items: updatedItems })); }; const handleSubmit = async (e) => { e.preventDefault(); if (!formData.clientId) { addToast('Please select a client', 'error'); return; } if (formData.items.some(item => !item.description || item.quantity <= 0)) { addToast('Please fill in all item details', 'error'); return; } setLoading(true); try { const selectedCustomer = customers.find(c => c.id === parseInt(formData.clientId)); const quotationData = { client: selectedCustomer.name, clientId: parseInt(formData.clientId), amount, tax, total, status: ' import React, { useState, useEffect } from 'react'; import { Bell, Menu, X, Home, Building, FileText, Wrench, Users, LogOut, ChevronDown, ChevronUp, Search, Plus, Edit, Trash, Check, AlertTriangle } from 'lucide-react'; // Mock data for demonstration const mockCompanies = [ { id: 1, name: "Madonoro Express", status: "Active", registrationNumber: "2020/123456/07", taxNumber: "9876543210" }, { id: 2, name: "Tech Solutions Ltd", status: "Active", registrationNumber: "2019/654321/07", taxNumber: "1234567890" }, ]; const mockRepairTickets = [ { id: 1, customer: "John Doe", device: "Laptop", problem: "Won't boot", status: "Pending", date: "2023-05-15" }, { id: 2, customer: "Jane Smith", device: "iPhone", problem: "Cracked screen", status: "In Progress", date: "2023-05-16" }, { id: 3, customer: "Bob Johnson", device: "Desktop PC", problem: "Blue screen error", status: "Completed", date: "2023-05-14" }, ]; const mockInvoices = [ { id: 1, client: "ABC Corp", amount: 1250.00, status: "Paid", date: "2023-05-10" }, { id: 2, client: "XYZ Ltd", amount: 3450.75, status: "Unpaid", date: "2023-05-12" }, { id: 3, client: "123 Industries", amount: 875.50, status: "Partial", date: "2023-05-08" }, ]; const mockQuotations = [ { id: 1, client: "DEF Solutions", amount: 2750.00, status: "Pending", date: "2023-05-11" }, { id: 2, client: "GHI Services", amount: 1890.25, status: "Accepted", date: "2023-05-13" }, { id: 3, client: "JKL Corp", amount: 4320.00, status: "Rejected", date: "2023-05-09" }, ]; // Main App Component export default function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [currentUser, setCurrentUser] = useState(null); const [sidebarOpen, setSidebarOpen] = useState(true); const [activeModule, setActiveModule] = useState('dashboard'); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); // Login handler const handleLogin = (userData) => { setCurrentUser(userData); setIsLoggedIn(true); }; // Logout handler const handleLogout = () => { setCurrentUser(null); setIsLoggedIn(false); }; return (
{!isLoggedIn ? ( ) : (
{/* Sidebar */} {/* Main Content */}
{activeModule === 'dashboard' && } {activeModule === 'companies' && } {activeModule === 'quotations' && } {activeModule === 'invoices' && } {activeModule === 'repairs' && } {activeModule === 'customers' && }
)}
); } // Login Page Component function LoginPage({ onLogin }) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const handleSubmit = (e) => { e.preventDefault(); if (!username || !password) { setError('Please enter both username and password'); return; } // Mock login - in a real app, this would call an API if (username === 'admin' && password === 'password') { onLogin({ name: 'Admin User', role: 'admin', email: 'admin@madonoro.com' }); } else if (username === 'staff' && password === 'password') { onLogin({ name: 'Staff User', role: 'staff', email: 'staff@madonoro.com' }); } else if (username === 'tech' && password === 'password') { onLogin({ name: 'Tech User', role: 'technician', email: 'tech@madonoro.com' }); } else if (username === 'accounting' && password === 'password') { onLogin({ name: 'Accounting User', role: 'accounting', email: 'accounting@madonoro.com' }); } else { setError('Invalid username or password'); } }; return (

Madonoro Express Services

Business Management System

{error && (

{error}

)}
setUsername(e.target.value)} className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" />
setPassword(e.target.value)} className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" />
Demo Credentials

Admin: admin / password

Staff: staff / password

Tech: tech / password

Accounting: accounting / password

); } // Sidebar Component function Sidebar({ isOpen, setIsOpen, activeModule, setActiveModule, onLogout, mobileMenuOpen, setMobileMenuOpen, userRole }) { const navItems = [ { id: 'dashboard', name: 'Dashboard', icon: Home, roles: ['admin', 'staff', 'technician', 'accounting'] }, { id: 'companies', name: 'Companies', icon: Building, roles: ['admin', 'staff'] }, { id: 'quotations', name: 'Quotations', icon: FileText, roles: ['admin', 'staff', 'accounting'] }, { id: 'invoices', name: 'Invoices', icon: FileText, roles: ['admin', 'staff', 'accounting'] }, { id: 'repairs', name: 'Repair Tickets', icon: Wrench, roles: ['admin', 'staff', 'technician'] }, { id: 'customers', name: 'Customers', icon: Users, roles: ['admin', 'staff', 'technician'] }, ]; // Filter nav items based on user role const filteredNavItems = navItems.filter(item => item.roles.includes(userRole)); return ( <> {/* Desktop sidebar */}
{/* Sidebar header */}
{isOpen && (
Madonoro
Express Services
)}
{/* Sidebar content */}
{/* Logout button */}
{/* Mobile menu */}
setMobileMenuOpen(false)}>
Madonoro
Express Services
); } // Header Component function Header({ setSidebarOpen, sidebarOpen, user, setMobileMenuOpen }) { const [notifications, setNotifications] = useState(3); return (
{user?.name?.charAt(0)}
{user?.name}
{user?.role}
); } // Dashboard Component function Dashboard() { return (

Dashboard

{/* Stats Cards */}
Active Companies
2
Pending Repairs
3
Unpaid Invoices
5
Quotes This Month
8
{/* Recent Activity */}
{/* Recent Repair Tickets */}

Recent Repair Tickets

{mockRepairTickets.slice(0, 3).map((ticket) => ( ))}
Customer Device Status
{ticket.customer} {ticket.device} {ticket.status}
{/* Recent Invoices */}

Recent Invoices

{mockInvoices.slice(0, 3).map((invoice) => ( ))}
Client Amount Status
{invoice.client} R{invoice.amount.toFixed(2)} {invoice.status}
); } // Company Management Component function CompanyManagement() { const [companies, setCompanies] = useState(mockCompanies); const [showAddForm, setShowAddForm] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const filteredCompanies = companies.filter(company => company.name.toLowerCase().includes(searchTerm.toLowerCase()) || company.registrationNumber.includes(searchTerm) ); return (

Company Management

{/* Search */}
setSearchTerm(e.target.value)} />
{/* Add Company Form */} {showAddForm && (

Add New Company

)} {/* Companies List */}
    {filteredCompanies.map((company) => (
  • {company.name}
    Reg: {company.registrationNumber}
    {company.status}
    Tax Number: {company.taxNumber}
  • ))}
); } // Quotation Management Component function QuotationManagement() { const [quotations, setQuotations] = useState(mockQuotations); const [searchTerm, setSearchTerm] = useState(''); const filteredQuotations = quotations.filter(quote => quote.client.toLowerCase().includes(searchTerm.toLowerCase()) ); return (

Quotation Management

{/* Search */}
setSearchTerm(e.target.value)} />
{/* Quotations Table */}
{filteredQuotations.map((quote) => ( ))}
Quote # Client Amount Date Status Actions
Q-{quote.id.toString().padStart(4, '0')} {quote.client} R{quote.amount.toFixed(2)} {quote.date} {quote.status}
); } // Invoice Management Component function InvoiceManagement() { const [invoices, setInvoices] = useState(mockInvoices); const [searchTerm, setSearchTerm] = useState(''); const filteredInvoices = invoices.filter(invoice => invoice.client.toLowerCase().includes(searchTerm.toLowerCase()) ); return (

Invoice Management

{/* Search */}
setSearchTerm(e.target.value)} />
{/* Invoices Table */}
{filteredInvoices.map((invoice) => ( ))}
Invoice # Client Amount Date Status Actions
INV-{invoice.id.toString().padStart(4, '0')} {invoice.client} R{invoice.amount.toFixed(2)} {invoice.date} {invoice.status}
); } // Repair Management Component function RepairManagement() { const [tickets, setTickets] = useState(mockRepairTickets); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('All'); const filteredTickets = tickets.filter(ticket => (ticket.customer.toLowerCase().includes(searchTerm.toLowerCase()) || ticket.device.toLowerCase().includes(searchTerm.toLowerCase())) && (statusFilter === 'All' || ticket.status === statusFilter) ); return (

Repair Ticket Management

{/* Filters */}
setSearchTerm(e.target.value)} />
{/* Tickets Table */}
{filteredTickets.map((ticket) => ( ))}
Ticket # Customer Device Problem Status Date Actions
RT-{ticket.id.toString().padStart(4, '0')} {ticket.customer} {ticket.device} {ticket.problem} {ticket.status} {ticket.date}
); } // Customer Management Component function CustomerManagement() { return (

Customer Management

{/* Search */}
{/* Customer List Placeholder */}

Customer management module is under development.

This will include customer profiles, contact information, and repair history.

); }