// Comprehensive Mock Data Expansion export const CompanyInfo = { name: "Sage", email: "sage@gmail.com", adresse: "12 Avenue de l'Innovation", adresse2: "75011 Paris, France", code_postal: "75008", ville: "Paris", siret: "123 456 789 00012", tva_intra: "FR12345678901", telephone: "01 23 45 67 89", } export const currentUser = { id: 1, name: 'Jean Dupont', email: 'jean.dupont@bijou.com', role: 'Administrateur', department: 'Direction', phone: '+33 6 12 34 56 78', avatar: 'JD', preferences: { language: 'fr', currency: 'EUR', theme: 'light', notifications: { email: true, sms: false, inApp: true } } }; export const mockUsers = [ { id: 1, name: 'Jean Dupont', email: 'jean.dupont@bijou.com', role: 'Administrateur', department: 'Direction', avatar: 'JD', status: 'active' }, { id: 2, name: 'Marie Dubois', email: 'marie.dubois@bijou.com', role: 'Commercial', department: 'Ventes', avatar: 'MD', status: 'active' }, { id: 3, name: 'Thomas Robert', email: 'thomas.robert@bijou.com', role: 'Commercial', department: 'Ventes', avatar: 'TR', status: 'active' }, { id: 4, name: 'Sophie Martin', email: 'sophie.martin@bijou.com', role: 'Support', department: 'SAV', avatar: 'SM', status: 'active' }, { id: 5, name: 'Pierre Durand', email: 'pierre.durand@bijou.com', role: 'Comptabilité', department: 'Finance', avatar: 'PD', status: 'active' }, { id: 6, name: 'Julie Moreau', email: 'julie.moreau@bijou.com', role: 'Logistique', department: 'Opérations', avatar: 'JM', status: 'active' }, ]; export const mockProductFamilies = [ { id: 1, code: 'FAM-001', name: 'Ordinateurs Portables', color: 'bg-blue-100 text-blue-800', articleCount: 12, category: 'Matériel', status: 'active' }, { id: 2, code: 'FAM-002', name: 'Écrans & Moniteurs', color: 'bg-purple-100 text-purple-800', articleCount: 8, category: 'Matériel', status: 'active' }, { id: 3, code: 'FAM-003', name: 'Logiciels & Licences', color: 'bg-green-100 text-green-800', articleCount: 25, category: 'Software', status: 'active' }, { id: 4, code: 'FAM-004', name: 'Services & Maintenance', color: 'bg-orange-100 text-orange-800', articleCount: 5, category: 'Service', status: 'active' }, { id: 5, code: 'FAM-005', name: 'Accessoires', color: 'bg-gray-100 text-gray-800', articleCount: 45, category: 'Accessoire', status: 'inactive' }, ]; export const mockArticles = Array.from({ length: 50 }, (_, i) => { const family = mockProductFamilies[i % mockProductFamilies.length]; const costPrice = Math.floor(Math.random() * 1000) + 50; const margin = 1.3 + (Math.random() * 0.4); // 30% to 70% margin return { id: i + 1, reference: `ART-${(i + 1000).toString()}`, name: `${family.name.split(' ')[0]} Pro ${2025 + (i % 3)} - Modèle ${String.fromCharCode(65 + (i % 26))}`, familyId: family.id, familyName: family.name, familyColor: family.color, costPrice: costPrice, sellingPrice: Math.floor(costPrice * margin), vatRate: 20, unit: 'Pcs', stock: Math.floor(Math.random() * 100), minStock: 10, status: i % 10 === 0 ? 'inactive' : 'active', description: "Description détaillée de l'article pour les devis et factures.", createdAt: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString().split('T')[0], }; }); export const mockClients = Array.from({ length: 25 }, (_, i) => ({ id: i + 1, name: [`Sophie Martin`, `Pierre Durand`, `Marie Lambert`, `Lucas Bernard`, `Emma Petit`][i % 5], company: [`ACME Corp`, `TechStart SAS`, `Digital Plus`, `InnovCo`, `WebAgency`, `LogiTrans`, `BuildIt`, `GreenEnergy`, `FoodProc`, `HealthCare`][i % 10], email: `contact${i}@company.com`, phone: `+33 1 ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)}`, status: ['active', 'inactive', 'prospect', 'vip'][Math.floor(Math.random() * 4)], owner: mockUsers[Math.floor(Math.random() * 3)].name, industry: ['Technology', 'Finance', 'Retail', 'Manufacturing'][Math.floor(Math.random() * 4)], city: ['Paris', 'Lyon', 'Marseille', 'Bordeaux', 'Lille'][Math.floor(Math.random() * 5)], address: `${Math.floor(Math.random() * 100) + 1} Rue de l'Innovation, 75001 Paris`, createdAt: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString().split('T')[0], lastActivity: new Date(Date.now() - Math.floor(Math.random() * 1000000000)).toISOString().split('T')[0] })); export const mockContacts = Array.from({ length: 30 }, (_, i) => ({ id: i + 1, firstName: ['Jean', 'Marie', 'Paul', 'Sophie', 'Luc'][i % 5], lastName: ['Dupont', 'Durand', 'Martin', 'Bernard', 'Petit'][i % 5], email: `contact${i}@example.com`, phone: `+33 6 ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)}`, role: ['CEO', 'CTO', 'CFO', 'Manager', 'Buyer'][i % 5], clientId: (i % mockClients.length) + 1 })); export const mockSuppliers = Array.from({ length: 15 }, (_, i) => ({ id: i + 1, name: [`Office Depot`, `Dell`, `Microsoft`, `Amazon AWS`, `Google`, `Lyreco`, `Metro`, `Rexel`, `Manutan`, `Orange`][i % 10], contact: [`Jean Supply`, `Marie Vente`, `Paul Achat`][i % 3], email: `supplier${i}@partner.com`, phone: `+33 1 99 88 77 ${i.toString().padStart(2, '0')}`, status: ['active', 'inactive', 'pending'][Math.floor(Math.random() * 3)], category: ['Hardware', 'Software', 'Office', 'Services'][Math.floor(Math.random() * 4)], paymentTerms: '30 jours fin de mois' })); const stages = ['new', 'qualification', 'discovery', 'proposal', 'negotiation', 'won', 'lost']; export const mockOpportunities = Array.from({ length: 25 }, (_, i) => ({ id: i + 1, name: `Projet ${['Digital', 'Cloud', 'Migration', 'Audit', 'Formation', 'Maintenance'][i % 6]} ${2025 + i}`, client: mockClients[i % mockClients.length].company, clientId: (i % mockClients.length) + 1, contactId: (i % mockContacts.length) + 1, amount: Math.floor(Math.random() * 50000) + 5000, probability: Math.floor(Math.random() * 10) * 10, stage: stages[Math.floor(Math.random() * 7)], priority: ['low', 'medium', 'high'][Math.floor(Math.random() * 3)], owner: mockUsers[Math.floor(Math.random() * 3)].name, createdAt: new Date(Date.now() - Math.floor(Math.random() * 8000000000)).toISOString().split('T')[0], closeDate: new Date(Date.now() + Math.floor(Math.random() * 8000000000)).toISOString().split('T')[0], nextAction: 'Relance client', description: 'Projet stratégique pour le développement de la nouvelle infrastructure cloud du client.', quotes: [] })); export const mockQuotes = Array.from({ length: 15 }, (_, i) => ({ id: i + 1, number: `DEV-2025-${(i + 1).toString().padStart(3, '0')}`, client: mockClients[i % mockClients.length].company, clientId: (i % mockClients.length) + 1, opportunityId: (i % mockOpportunities.length) + 1, date: new Date(Date.now() - Math.floor(Math.random() * 5000000000)).toISOString().split('T')[0], amountHT: Math.floor(Math.random() * 20000) + 1000, status: ['draft', 'sent', 'accepted', 'refused', 'expired'][Math.floor(Math.random() * 5)], owner: mockUsers[Math.floor(Math.random() * 3)].name, items: [ { description: "Consulting Senior", quantity: 2, unitPrice: 1200, vat: 20 }, { description: "Licence Logiciel", quantity: 1, unitPrice: 5000, vat: 20 } ] })).map(q => ({ ...q, vat: q.amountHT * 0.2, amountTTC: q.amountHT * 1.2 })); export const mockOrders = mockQuotes.filter(q => q.status === 'accepted').map((q, i) => ({ id: i + 1, number: `CMD-2025-${(i + 1).toString().padStart(3, '0')}`, sourceQuote: q.number, client: q.client, clientId: q.clientId, date: new Date(new Date(q.date).getTime() + 86400000 * 2).toISOString().split('T')[0], deliveryDate: new Date(new Date(q.date).getTime() + 86400000 * 15).toISOString().split('T')[0], amountHT: q.amountHT, amountTTC: q.amountTTC, status: ['pending', 'validated', 'shipped', 'delivered'][Math.floor(Math.random() * 4)], items: q.items })); // ENRICHED INVOICES MOCK DATA const invoiceStatuses = ['draft', 'sent', 'paid', 'partial', 'overdue', 'cancelled']; export const mockInvoices = Array.from({ length: 25 }, (_, i) => { const client = mockClients[i % mockClients.length]; const status = invoiceStatuses[Math.floor(Math.random() * invoiceStatuses.length)]; const amountHT = Math.floor(Math.random() * 15000) + 500; const vat = amountHT * 0.2; const amountTTC = amountHT + vat; const date = new Date(Date.now() - Math.floor(Math.random() * 4000000000)).toISOString().split('T')[0]; const dueDate = new Date(new Date(date).getTime() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; let balanceDue = amountTTC; let payments = []; if (status === 'paid') { balanceDue = 0; payments.push({ id: 1, date: dueDate, amount: amountTTC, method: 'Virement', reference: 'VIR-12345' }); } else if (status === 'partial') { const partialAmount = Math.floor(amountTTC * 0.4); balanceDue = amountTTC - partialAmount; payments.push({ id: 1, date: new Date(date).toISOString().split('T')[0], amount: partialAmount, method: 'Virement', reference: 'ACCOMPTE' }); } return { id: i + 1, number: `FAC-2025-${(i + 100).toString().padStart(3, '0')}`, client: client.company, clientId: client.id, clientAvatar: client.name.charAt(0), sourceDocument: i % 3 === 0 ? `CMD-2025-00${(i % 5) + 1}` : null, date: date, dueDate: dueDate, amountHT: amountHT, vat: vat, amountTTC: amountTTC, balanceDue: balanceDue, status: status, items: [ { id: 1, description: "Prestation de service informatique", quantity: 1, unitPrice: amountHT * 0.6, vat: 20, total: amountHT * 0.6 }, { id: 2, description: "Matériel divers", quantity: 2, unitPrice: amountHT * 0.2, vat: 20, total: amountHT * 0.4 } ], payments: payments, timeline: [ { date: date, title: 'Facture créée', type: 'creation', user: 'Jean Dupont' }, ...(status !== 'draft' ? [{ date: date, title: 'Facture envoyée', type: 'email', user: 'Jean Dupont' }] : []), ...(status === 'paid' ? [{ date: dueDate, title: 'Paiement reçu', type: 'payment', user: 'Système' }] : []) ] }; }); export const mockPurchaseOrders = Array.from({ length: 12 }, (_, i) => ({ id: i + 1, number: `BC-2025-${(i + 100).toString()}`, supplier: mockSuppliers[i % mockSuppliers.length].name, supplierId: (i % mockSuppliers.length) + 1, date: new Date(Date.now() - Math.floor(Math.random() * 5000000000)).toISOString().split('T')[0], deliveryDate: new Date(Date.now() + Math.floor(Math.random() * 2000000000)).toISOString().split('T')[0], amountHT: Math.floor(Math.random() * 10000) + 500, status: ['draft', 'sent', 'received', 'invoiced'][Math.floor(Math.random() * 4)], items: [ { description: 'MacBook Pro 16"', quantity: 2, unitPrice: 2400 }, { description: 'Dell Monitor 27"', quantity: 5, unitPrice: 350 } ] })).map(o => ({ ...o, amountTTC: o.amountHT * 1.2 })); export const mockReceptionNotes = mockPurchaseOrders.filter(p => p.status === 'received' || p.status === 'invoiced').map((p, i) => ({ id: i + 1, number: `BR-2025-${(i + 100).toString()}`, purchaseOrder: p.number, supplier: p.supplier, date: new Date(new Date(p.date).getTime() + 86400000 * 5).toISOString().split('T')[0], status: 'validated', items: p.items })); export const mockPurchaseInvoices = mockReceptionNotes.slice(0, 8).map((br, i) => ({ id: i + 1, number: `FA-2025-${(i + 500).toString()}`, supplier: br.supplier, receptionNote: br.number, date: new Date(new Date(br.date).getTime() + 86400000 * 2).toISOString().split('T')[0], dueDate: new Date(new Date(br.date).getTime() + 86400000 * 32).toISOString().split('T')[0], amountTTC: Math.floor(Math.random() * 10000) + 500, status: ['pending', 'paid', 'overdue'][Math.floor(Math.random() * 3)] })); export const mockTickets = Array.from({ length: 15 }, (_, i) => ({ id: i + 1, number: `TKT-${(1000 + i)}`, client: mockClients[i % mockClients.length].company, clientId: (i % mockClients.length) + 1, subject: ['Erreur connexion', 'Demande évolution', 'Question facture', 'Bug critique', 'Formation', 'Accès perdu'][i % 6], priority: ['low', 'normal', 'high', 'critical'][Math.floor(Math.random() * 4)], status: ['open', 'in-progress', 'pending', 'resolved', 'closed'][Math.floor(Math.random() * 5)], assignedTo: mockUsers[3 + Math.floor(Math.random() * 3)].name, openDate: new Date(Date.now() - Math.floor(Math.random() * 1000000000)).toISOString().split('T')[0], category: ['Technique', 'Commercial', 'Administratif'][Math.floor(Math.random() * 3)], description: "Le client rencontre des difficultés pour accéder à la plateforme depuis la mise à jour de lundi.", comments: [ { user: 'Jean Dupont', date: '2025-12-01', text: 'J\'ai pris en compte la demande.' }, { user: 'Sophie Martin', date: '2025-12-02', text: 'Le problème semble venir du cache navigateur.' } ] })); export const mockTimeline = Array.from({ length: 20 }, (_, i) => ({ id: i, type: ['email', 'call', 'meeting', 'quote', 'task'][Math.floor(Math.random() * 5)], title: ['Email envoyé', 'Appel client', 'Réunion projet', 'Devis créé', 'Tâche terminée'][Math.floor(Math.random() * 5)], description: 'Détails de l\'activité simulée pour la démonstration.', date: new Date(Date.now() - Math.floor(Math.random() * 1000000000)).toISOString().split('T')[0], user: mockUsers[Math.floor(Math.random() * mockUsers.length)].name })); export const mockStats = { revenue: [ { name: 'Jan', value: 145000 }, { name: 'Fév', value: 168000 }, { name: 'Mar', value: 156000 }, { name: 'Avr', value: 178000 }, { name: 'Mai', value: 192000 }, { name: 'Juin', value: 215000 }, { name: 'Juil', value: 198000 }, { name: 'Août', value: 185000 }, { name: 'Sep', value: 225000 }, { name: 'Oct', value: 238000 }, { name: 'Nov', value: 245000 }, { name: 'Déc', value: 260000 } ], expenses: [ { name: 'Jan', value: 110000 }, { name: 'Fév', value: 115000 }, { name: 'Mar', value: 112000 }, { name: 'Avr', value: 125000 }, { name: 'Mai', value: 128000 }, { name: 'Juin', value: 135000 }, { name: 'Juil', value: 130000 }, { name: 'Août', value: 128000 }, { name: 'Sep', value: 140000 }, { name: 'Oct', value: 145000 }, { name: 'Nov', value: 150000 }, { name: 'Déc', value: 155000 } ], ticketPriority: [ { name: 'Basse', value: 25, fill: '#3b82f6' }, { name: 'Normale', value: 45, fill: '#9ca3af' }, { name: 'Haute', value: 20, fill: '#f97316' }, { name: 'Critique', value: 10, fill: '#ef4444' } ], ticketStatus: [ { name: 'Ouvert', value: 15, fill: '#ef4444' }, { name: 'En cours', value: 25, fill: '#f97316' }, { name: 'Attente', value: 10, fill: '#eab308' }, { name: 'Résolu', value: 40, fill: '#22c55e' }, { name: 'Fermé', value: 10, fill: '#9ca3af' } ], quoteStatus: [ { name: 'Brouillon', value: 15, fill: '#9ca3af' }, { name: 'Envoyé', value: 30, fill: '#3b82f6' }, { name: 'Accepté', value: 45, fill: '#22c55e' }, { name: 'Refusé', value: 10, fill: '#ef4444' } ], salesByRep: [ { name: 'Jean', value: 450000 }, { name: 'Marie', value: 380000 }, { name: 'Thomas', value: 320000 } ] }; // --- KPI HELPERS --- export const filterDataByPeriod = (data, dateField, period) => { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); // Handle Custom Range Object if (typeof period === 'object' && period?.id === 'custom') { if (!period.start || !period.end) return data; const startDate = new Date(period.start); const endDate = new Date(period.end); // Set end date to end of day for inclusive comparison endDate.setHours(23, 59, 59, 999); return data.filter(item => { const itemDate = new Date(item[dateField]); if (isNaN(itemDate)) return false; return itemDate >= startDate && itemDate <= endDate; }); } // Handle string identifiers return data.filter(item => { const itemDate = new Date(item[dateField]); if (isNaN(itemDate)) return false; switch (period) { case 'today': return itemDate >= today; case 'week': const startOfWeek = new Date(today); startOfWeek.setDate(today.getDate() - today.getDay()); // Sunday return itemDate >= startOfWeek; case 'month': return itemDate.getMonth() === today.getMonth() && itemDate.getFullYear() === today.getFullYear(); case 'quarter': const currentQuarter = Math.floor(today.getMonth() / 3); const itemQuarter = Math.floor(itemDate.getMonth() / 3); return itemQuarter === currentQuarter && itemDate.getFullYear() === today.getFullYear(); case 'year': return itemDate.getFullYear() === today.getFullYear(); case '30days': const thirtyDaysAgo = new Date(today); thirtyDaysAgo.setDate(today.getDate() - 30); return itemDate >= thirtyDaysAgo; default: // 'all' return true; } }); }; export const calculateKPIs = (data, period, config) => { const filtered = filterDataByPeriod(data, config.dateField || 'date', period); // Determine trend seed const seed = typeof period === 'object' ? (period.start?.length || 0) : period.length; return { filteredCount: filtered.length, totalAmount: filtered.reduce((acc, item) => acc + (item[config.amountField] || 0), 0), trend: seed % 2 === 0 ? 'up' : 'down', items: filtered }; };