ptechcore_ui 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/eslint.config.js +28 -0
  2. package/index.html +78 -0
  3. package/package.json +42 -0
  4. package/postcss.config.js +6 -0
  5. package/src/App.tsx +156 -0
  6. package/src/assets/imgs/login_illustration.png +0 -0
  7. package/src/components/common/Buttons.tsx +39 -0
  8. package/src/components/common/Cards.tsx +18 -0
  9. package/src/components/common/FDrawer.tsx +2448 -0
  10. package/src/components/common/FDrawer.types.ts +191 -0
  11. package/src/components/common/Inputs.tsx +409 -0
  12. package/src/components/common/Modals.tsx +41 -0
  13. package/src/components/common/Navigations.tsx +0 -0
  14. package/src/components/common/Toast.tsx +0 -0
  15. package/src/components/demo/ToastDemo.tsx +73 -0
  16. package/src/components/layout/Header.tsx +202 -0
  17. package/src/components/layout/ModernDoubleSidebarLayout.tsx +727 -0
  18. package/src/components/layout/PrivateLayout.tsx +52 -0
  19. package/src/components/layout/Sidebar.tsx +182 -0
  20. package/src/components/ui/Toast.tsx +93 -0
  21. package/src/contexts/SessionContext.tsx +77 -0
  22. package/src/contexts/ThemeContext.tsx +58 -0
  23. package/src/contexts/ToastContext.tsx +94 -0
  24. package/src/index.css +3 -0
  25. package/src/main.tsx +10 -0
  26. package/src/models/Organization.ts +47 -0
  27. package/src/models/Plan.ts +42 -0
  28. package/src/models/User.ts +23 -0
  29. package/src/pages/Analytics.tsx +101 -0
  30. package/src/pages/CreateOrganization.tsx +215 -0
  31. package/src/pages/Dashboard.tsx +15 -0
  32. package/src/pages/Home.tsx +12 -0
  33. package/src/pages/Profile.tsx +313 -0
  34. package/src/pages/Settings.tsx +382 -0
  35. package/src/pages/Team.tsx +180 -0
  36. package/src/pages/auth/Login.tsx +140 -0
  37. package/src/pages/auth/Register.tsx +302 -0
  38. package/src/pages/organizations/DetailEntity.tsx +1002 -0
  39. package/src/pages/organizations/DetailOrganizations.tsx +1629 -0
  40. package/src/pages/organizations/ListOrganizations.tsx +270 -0
  41. package/src/pages/pricings/CartPlan.tsx +486 -0
  42. package/src/pages/pricings/ListPricing.tsx +321 -0
  43. package/src/pages/users/CreateUser.tsx +450 -0
  44. package/src/pages/users/ListUsers.tsx +0 -0
  45. package/src/services/AuthServices.ts +94 -0
  46. package/src/services/OrganizationServices.ts +61 -0
  47. package/src/services/PlanSubscriptionServices.tsx +137 -0
  48. package/src/services/UserServices.ts +36 -0
  49. package/src/services/api.ts +64 -0
  50. package/src/styles/theme.ts +383 -0
  51. package/src/utils/utils.ts +48 -0
  52. package/src/vite-env.d.ts +1 -0
  53. package/tailwind.config.js +158 -0
  54. package/tsconfig.app.json +24 -0
  55. package/tsconfig.json +7 -0
  56. package/tsconfig.node.json +22 -0
  57. package/vite.config.ts +10 -0
@@ -0,0 +1,727 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { Outlet, useLocation, useNavigate } from 'react-router-dom';
3
+
4
+ import {
5
+ LayoutDashboard, FileText, Users, CreditCard, TrendingUp,
6
+ Settings, ChevronLeft, ChevronRight, Search, Bell, User,
7
+ LogOut, Menu, X, Moon, Sun, Palette, Building2,
8
+ Calculator, PiggyBank, FileBarChart, Shield, Package,
9
+ DollarSign, BarChart3, Briefcase, Receipt, UserCheck,
10
+ Database, Globe, HelpCircle, Activity, Lock, AlertTriangle,
11
+ CheckCircle, Clock, FileCheck, Bot, ScanLine,
12
+ MessageSquare, Smartphone, Workflow, RefreshCw, Wifi,
13
+ Eye, Target, BookOpen, Archive, Download,
14
+ Upload, Save, FolderOpen, Home, ChevronDown, Link, PieChart,
15
+ Video, Calendar, Folder
16
+ } from 'lucide-react';
17
+ import { cn } from '../../utils/utils';
18
+ import { useTheme } from '../../contexts/ThemeContext';
19
+
20
+ export interface MenuItem {
21
+ id: string;
22
+ label: string;
23
+ icon: React.ReactNode;
24
+ path?: string;
25
+ badge?: string | number;
26
+ submenu?: MenuItem[];
27
+ ariaLabel?: string;
28
+ }
29
+
30
+ export interface Notification {
31
+ id: string;
32
+ title: string;
33
+ message: string;
34
+ type: 'info' | 'warning' | 'error' | 'success';
35
+ timestamp: Date;
36
+ read: boolean;
37
+ }
38
+
39
+
40
+ interface PrivateLayoutProps {
41
+ children: React.ReactNode;
42
+ module_name: string;
43
+ primaryMenuItems: MenuItem[];
44
+ secondaryMenuItems: Record<string, MenuItem[]>;
45
+
46
+ }
47
+
48
+ const RewiseLayout: React.FC<PrivateLayoutProps> = ({ children, module_name='Rewise', primaryMenuItems, secondaryMenuItems }) => {
49
+ const location = useLocation();
50
+ const navigate = useNavigate();
51
+ const { theme, themeType, setTheme } = useTheme();
52
+
53
+ const [primaryCollapsed, setPrimaryCollapsed] = useState(false);
54
+ const [secondaryCollapsed, setSecondaryCollapsed] = useState(false);
55
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
56
+ const [selectedModule, setSelectedModule] = useState('dashboard');
57
+ const [searchQuery, setSearchQuery] = useState('');
58
+ const [showNotifications, setShowNotifications] = useState(false);
59
+ const [showUserMenu, setShowUserMenu] = useState(false);
60
+ const [showThemeMenu, setShowThemeMenu] = useState(false);
61
+ const [notifications, setNotifications] = useState<Notification[]>([
62
+ {
63
+ id: '1',
64
+ title: 'Nouvelle facture',
65
+ message: '3 nouvelles factures en attente de validation',
66
+ type: 'info',
67
+ timestamp: new Date(),
68
+ read: false
69
+ },
70
+ {
71
+ id: '2',
72
+ title: 'Clôture mensuelle',
73
+ message: 'La clôture de janvier est prête',
74
+ type: 'success',
75
+ timestamp: new Date(),
76
+ read: false
77
+ }
78
+ ]);
79
+
80
+ // Navigation clavier
81
+ useEffect(() => {
82
+ const handleKeyDown = (e: KeyboardEvent) => {
83
+ // Alt + M pour toggle menu mobile
84
+ if (e.altKey && e.key === 'm') {
85
+ e.preventDefault();
86
+ setMobileMenuOpen(prev => !prev);
87
+ }
88
+ // Alt + S pour recherche
89
+ if (e.altKey && e.key === 's') {
90
+ e.preventDefault();
91
+ document.getElementById('global-search')?.focus();
92
+ }
93
+ // Escape pour fermer les menus
94
+ if (e.key === 'Escape') {
95
+ setShowNotifications(false);
96
+ setShowUserMenu(false);
97
+ setShowThemeMenu(false);
98
+ setMobileMenuOpen(false);
99
+ }
100
+ };
101
+
102
+ document.addEventListener('keydown', handleKeyDown);
103
+ return () => document.removeEventListener('keydown', handleKeyDown);
104
+ }, []);
105
+
106
+
107
+
108
+ // Détection automatique du module actif basé sur l'URL
109
+ useEffect(() => {
110
+ const path = location.pathname;
111
+ const moduleMatch = path.match(/^\/([^/]+)/);
112
+ if (moduleMatch) {
113
+ const moduleId = moduleMatch[1];
114
+ // Mapping des routes vers les IDs de modules
115
+ const routeMapping: Record<string, string> = {
116
+ 'dashboard': 'dashboard',
117
+ 'organizations': 'organizations',
118
+ 'entities': 'entities',
119
+
120
+
121
+ };
122
+ setSelectedModule(routeMapping[moduleId] || 'dashboard');
123
+ }
124
+ }, [location]);
125
+
126
+
127
+
128
+
129
+ const handleThemeChange = (type: 'elegant' | 'fintech' | 'minimalist') => {
130
+ setTheme(type);
131
+ setShowThemeMenu(false);
132
+ };
133
+
134
+ const isActive = (path: string) => location.pathname === path;
135
+ const isModuleActive = (moduleId: string) => selectedModule === moduleId;
136
+
137
+ // Breadcrumbs
138
+ const getBreadcrumbs = () => {
139
+ const paths = location.pathname.split('/').filter(Boolean);
140
+ const breadcrumbs = [{ label: 'Accueil', path: '/' }];
141
+
142
+ paths.forEach((path, index) => {
143
+ const fullPath = '/' + paths.slice(0, index + 1).join('/');
144
+ const module = primaryMenuItems.find(m => m.id === path);
145
+ const label = module ? module.label : path.charAt(0).toUpperCase() + path.slice(1);
146
+ breadcrumbs.push({ label, path: fullPath });
147
+ });
148
+
149
+ return breadcrumbs;
150
+ };
151
+
152
+ // Mark notification as read
153
+ const markNotificationAsRead = (id: string) => {
154
+ setNotifications(prev =>
155
+ prev.map(n => n.id === id ? { ...n, read: true } : n)
156
+ );
157
+ };
158
+
159
+ return (
160
+ <div className="flex h-screen bg-[var(--color-background)] overflow-hidden">
161
+ {/* Skip to main content (Accessibility) */}
162
+ <a
163
+ href="#main-content"
164
+ className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 bg-[var(--color-primary)] text-[var(--color-background)] px-4 py-2 rounded"
165
+ >
166
+ Aller au contenu principal
167
+ </a>
168
+
169
+ {/* Primary Sidebar */}
170
+ <aside
171
+ className={cn(
172
+ 'hidden lg:flex flex-col bg-[var(--color-sidebar-bg)] transition-all duration-300',
173
+ primaryCollapsed ? 'w-20' : 'w-64'
174
+ )}
175
+ role="navigation"
176
+ aria-label="Navigation principale"
177
+ >
178
+ {/* Logo */}
179
+ <div className="h-16 flex items-center justify-between px-4 border-b border-[var(--color-sidebar-border)]">
180
+ <div className={cn(
181
+ 'flex items-center gap-3',
182
+ primaryCollapsed && 'justify-center'
183
+ )}>
184
+ <div className="w-10 h-10 bg-[var(--color-primary)] rounded-lg flex items-center justify-center">
185
+ <span className="text-[var(--color-background)] font-bold text-xl">W</span>
186
+ </div>
187
+ {!primaryCollapsed && (
188
+ <div>
189
+ <h1 className="text-[var(--color-sidebar-text)] font-bold text-lg">WiseBook</h1>
190
+ <p className="text-[var(--color-sidebar-text-secondary)] text-xs">ERP Next-Gen</p>
191
+ </div>
192
+ )}
193
+ </div>
194
+ <button
195
+ onClick={() => setPrimaryCollapsed(!primaryCollapsed)}
196
+ className="text-[var(--color-sidebar-text-secondary)] hover:text-[var(--color-sidebar-text)] transition-colors"
197
+ aria-label={primaryCollapsed ? 'Développer le menu' : 'Réduire le menu'}
198
+ aria-expanded={!primaryCollapsed}
199
+ >
200
+ <ChevronLeft className={cn(
201
+ "w-5 h-5 transition-transform",
202
+ primaryCollapsed && "rotate-180"
203
+ )} />
204
+ </button>
205
+ </div>
206
+
207
+ {/* Primary Navigation */}
208
+ <nav
209
+ className="flex-1 py-4 overflow-y-auto"
210
+ role="menubar"
211
+ aria-label="Modules principaux"
212
+ >
213
+ {primaryMenuItems.map((item) => (
214
+ <button
215
+ key={item.id}
216
+ onClick={() => {
217
+ if (item.path) {
218
+ navigate(item.path);
219
+ } else {
220
+ setSelectedModule(item.id);
221
+ }
222
+ }}
223
+ className={cn(
224
+ 'w-full flex items-center gap-3 px-4 py-3 transition-all duration-200',
225
+ 'hover:bg-[var(--color-sidebar-hover)] relative group',
226
+ isModuleActive(item.id) && 'bg-[var(--color-sidebar-active)] border-l-4 border-[var(--color-primary)]',
227
+ primaryCollapsed && 'justify-center'
228
+ )}
229
+ role="menuitem"
230
+ aria-label={item.ariaLabel || item.label}
231
+ aria-current={isModuleActive(item.id) ? 'page' : undefined}
232
+ >
233
+ <div className={cn(
234
+ 'transition-colors',
235
+ isModuleActive(item.id) ? 'text-[var(--color-primary)]' : 'text-[var(--color-sidebar-text-secondary)] group-hover:text-[var(--color-sidebar-text)]'
236
+ )}>
237
+ {item.icon}
238
+ </div>
239
+ {!primaryCollapsed && (
240
+ <>
241
+ <span className={cn(
242
+ 'flex-1 text-left text-sm font-medium transition-colors',
243
+ isModuleActive(item.id) ? 'text-[var(--color-sidebar-text)]' : 'text-[var(--color-sidebar-text-secondary)] group-hover:text-[var(--color-sidebar-text)]'
244
+ )}>
245
+ {item.label}
246
+ </span>
247
+ {item.badge && (
248
+ <span className="px-2 py-0.5 text-xs bg-[var(--color-primary)] text-[var(--color-background)] rounded-full">
249
+ {item.badge}
250
+ </span>
251
+ )}
252
+ </>
253
+ )}
254
+ {primaryCollapsed && (
255
+ <div className="absolute left-full ml-2 px-2 py-1 bg-[var(--color-sidebar-active)] text-[var(--color-sidebar-text)] text-xs rounded opacity-0 group-hover:opacity-100 pointer-events-none whitespace-nowrap z-50">
256
+ {item.label}
257
+ </div>
258
+ )}
259
+ </button>
260
+ ))}
261
+ </nav>
262
+
263
+ {/* User Section */}
264
+ <div className="p-4 border-t border-[var(--color-sidebar-border)]">
265
+ <div className={cn(
266
+ 'flex items-center gap-3',
267
+ primaryCollapsed && 'justify-center'
268
+ )}>
269
+ <div className="w-10 h-10 bg-[var(--color-sidebar-avatar-bg)] rounded-full flex items-center justify-center">
270
+ <User className="w-5 h-5 text-[var(--color-sidebar-text-secondary)]" />
271
+ </div>
272
+ {!primaryCollapsed && (
273
+ <div className="flex-1">
274
+ <p className="text-sm font-medium text-[var(--color-sidebar-text)]">Admin</p>
275
+ <p className="text-xs text-[var(--color-sidebar-text-secondary)]">admin@wisebook.com</p>
276
+ </div>
277
+ )}
278
+ </div>
279
+ </div>
280
+ </aside>
281
+
282
+ {/* Secondary Sidebar */}
283
+ {secondaryMenuItems[selectedModule] && (
284
+ <>
285
+ {/* Toggle button when collapsed */}
286
+ {secondaryCollapsed && (
287
+ <button
288
+ onClick={() => setSecondaryCollapsed(false)}
289
+ className="hidden lg:flex items-center justify-center w-12 h-full bg-[var(--color-background)] border-r border-[var(--color-border)] hover:bg-[var(--color-surface-hover)] transition-colors"
290
+ aria-label="Ouvrir le sous-menu"
291
+ >
292
+ <ChevronRight className="w-5 h-5 text-[var(--color-text-tertiary)]" />
293
+ </button>
294
+ )}
295
+
296
+ <aside
297
+ className={cn(
298
+ 'hidden lg:flex flex-col bg-[var(--color-background)] border-r border-[var(--color-border)] transition-all duration-300',
299
+ secondaryCollapsed ? 'w-0 overflow-hidden' : 'w-64'
300
+ )}
301
+ role="navigation"
302
+ aria-label="Navigation secondaire"
303
+ >
304
+ <div className="h-16 flex items-center justify-between px-4 border-b border-[var(--color-border)]">
305
+ <h2 className="text-sm font-semibold text-[var(--color-text-secondary)] uppercase tracking-wider whitespace-nowrap">
306
+ {primaryMenuItems.find(item => item.id === selectedModule)?.label}
307
+ </h2>
308
+ <button
309
+ onClick={() => setSecondaryCollapsed(!secondaryCollapsed)}
310
+ className="text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] flex-shrink-0"
311
+ aria-label={secondaryCollapsed ? 'Développer le sous-menu' : 'Réduire le sous-menu'}
312
+ >
313
+ <ChevronLeft className={cn(
314
+ "w-4 h-4 transition-transform",
315
+ secondaryCollapsed && "rotate-180"
316
+ )} />
317
+ </button>
318
+ </div>
319
+
320
+ <nav
321
+ className="flex-1 py-4 overflow-y-auto"
322
+ role="menu"
323
+ aria-label="Sous-navigation"
324
+ >
325
+ {secondaryMenuItems[selectedModule]?.map((item) => (
326
+ <button
327
+ key={item.id}
328
+ onClick={() => item.path && navigate(item.path)}
329
+ className={cn(
330
+ 'w-full flex items-center gap-3 px-4 py-2.5 transition-all duration-200',
331
+ 'hover:bg-[var(--color-surface-hover)]',
332
+ isActive(item.path || '') && 'bg-[var(--color-primary-light)] border-l-4 border-[var(--color-primary)]'
333
+ )}
334
+ role="menuitem"
335
+ aria-current={isActive(item.path || '') ? 'page' : undefined}
336
+ >
337
+ {item.icon && (
338
+ <div className={cn(
339
+ 'transition-colors',
340
+ isActive(item.path || '') ? 'text-[var(--color-primary)]' : 'text-[var(--color-text-tertiary)]'
341
+ )}>
342
+ {item.icon}
343
+ </div>
344
+ )}
345
+ <span className={cn(
346
+ 'flex-1 text-left text-sm',
347
+ isActive(item.path || '') ? 'text-[var(--color-primary)] font-medium' : 'text-[var(--color-text-secondary)]'
348
+ )}>
349
+ {item.label}
350
+ </span>
351
+ {item.badge && (
352
+ <span className="px-2 py-0.5 text-xs bg-[var(--color-primary)] text-white rounded-full">
353
+ {item.badge}
354
+ </span>
355
+ )}
356
+ </button>
357
+ ))}
358
+ </nav>
359
+ </aside>
360
+ </>
361
+ )}
362
+
363
+ {/* Mobile Sidebar Overlay */}
364
+ {mobileMenuOpen && (
365
+ <div
366
+ className="fixed inset-0 bg-black bg-opacity-50 z-50 lg:hidden"
367
+ onClick={() => setMobileMenuOpen(false)}
368
+ aria-hidden="true"
369
+ >
370
+ <aside
371
+ className="w-80 h-full bg-[var(--color-sidebar-bg)]"
372
+ onClick={(e) => e.stopPropagation()}
373
+ role="navigation"
374
+ aria-label="Navigation mobile"
375
+ >
376
+ <div className="h-16 flex items-center justify-between px-4 border-b border-[var(--color-sidebar-border)]">
377
+ <div className="flex items-center gap-3">
378
+ <div className="w-10 h-10 bg-[var(--color-primary)] rounded-lg flex items-center justify-center">
379
+ <span className="text-[var(--color-background)] font-bold text-xl">W</span>
380
+ </div>
381
+ <div>
382
+ <h1 className="text-white font-bold text-lg">WiseBook</h1>
383
+ <p className="text-gray-400 text-xs">ERP Next-Gen</p>
384
+ </div>
385
+ </div>
386
+ <button
387
+ onClick={() => setMobileMenuOpen(false)}
388
+ className="text-[var(--color-sidebar-text-secondary)]"
389
+ aria-label="Fermer le menu"
390
+ >
391
+ <X className="w-6 h-6" />
392
+ </button>
393
+ </div>
394
+ <nav className="py-4" role="menubar">
395
+ {primaryMenuItems.map((item) => (
396
+ <div key={item.id}>
397
+ <button
398
+ onClick={() => {
399
+ if (item.path) {
400
+ navigate(item.path);
401
+ setMobileMenuOpen(false);
402
+ } else {
403
+ setSelectedModule(item.id);
404
+ }
405
+ }}
406
+ className={cn(
407
+ 'w-full flex items-center gap-3 px-4 py-3 transition-all duration-200',
408
+ 'hover:bg-[var(--color-sidebar-hover)]',
409
+ isModuleActive(item.id) && 'bg-[var(--color-sidebar-active)] border-l-4 border-[var(--color-primary)]'
410
+ )}
411
+ role="menuitem"
412
+ aria-current={isModuleActive(item.id) ? 'page' : undefined}
413
+ >
414
+ <div className={cn(
415
+ 'transition-colors',
416
+ isModuleActive(item.id) ? 'text-[var(--color-primary)]' : 'text-[var(--color-sidebar-text-secondary)]'
417
+ )}>
418
+ {item.icon}
419
+ </div>
420
+ <span className={cn(
421
+ 'flex-1 text-left text-sm font-medium',
422
+ isModuleActive(item.id) ? 'text-[var(--color-sidebar-text)]' : 'text-[var(--color-sidebar-text-secondary)]'
423
+ )}>
424
+ {item.label}
425
+ </span>
426
+ {item.badge && (
427
+ <span className="px-2 py-0.5 text-xs bg-[var(--color-primary)] text-[var(--color-background)] rounded-full">
428
+ {item.badge}
429
+ </span>
430
+ )}
431
+ </button>
432
+
433
+ {/* Mobile Submenu */}
434
+ {isModuleActive(item.id) && secondaryMenuItems[item.id] && (
435
+ <div className="bg-[var(--color-sidebar-submenu-bg)] py-2">
436
+ {secondaryMenuItems[item.id].map((subItem) => (
437
+ <button
438
+ key={subItem.id}
439
+ onClick={() => {
440
+ if (subItem.path) {
441
+ navigate(subItem.path);
442
+ setMobileMenuOpen(false);
443
+ }
444
+ }}
445
+ className={cn(
446
+ 'w-full flex items-center gap-3 pl-12 pr-4 py-2 text-sm',
447
+ 'hover:bg-[var(--color-sidebar-hover)]',
448
+ isActive(subItem.path || '') && 'bg-[var(--color-sidebar-active)] text-[var(--color-primary)]'
449
+ )}
450
+ >
451
+ {subItem.icon}
452
+ <span className={cn(
453
+ isActive(subItem.path || '') ? 'text-[var(--color-primary)]' : 'text-[var(--color-sidebar-text-secondary)]'
454
+ )}>
455
+ {subItem.label}
456
+ </span>
457
+ </button>
458
+ ))}
459
+ </div>
460
+ )}
461
+ </div>
462
+ ))}
463
+ </nav>
464
+ </aside>
465
+ </div>
466
+ )}
467
+
468
+ {/* Main Content Area */}
469
+ <div className="flex-1 flex flex-col overflow-hidden">
470
+ {/* Header */}
471
+ <header
472
+ className="h-14 bg-[var(--color-background)] border-b border-[var(--color-border)] flex items-center justify-between px-3 lg:px-4"
473
+ role="banner"
474
+ >
475
+ <div className="flex items-center gap-4 flex-1">
476
+ <button
477
+ onClick={() => setMobileMenuOpen(true)}
478
+ className="lg:hidden text-[var(--color-text-primary)]"
479
+ aria-label="Ouvrir le menu mobile"
480
+ >
481
+ <Menu className="w-6 h-6" />
482
+ </button>
483
+
484
+ {/* Breadcrumbs */}
485
+ <nav
486
+ className="hidden sm:flex items-center gap-2 text-sm"
487
+ aria-label="Fil d'Ariane"
488
+ >
489
+ {getBreadcrumbs().map((crumb, index) => (
490
+ <React.Fragment key={crumb.path}>
491
+ {index > 0 && <ChevronRight className="w-4 h-4 text-[var(--color-text-tertiary)]" />}
492
+ <button
493
+ onClick={() => navigate(crumb.path)}
494
+ className={cn(
495
+ 'hover:text-[var(--color-primary)]',
496
+ index === getBreadcrumbs().length - 1
497
+ ? 'text-[var(--color-text-primary)] font-medium'
498
+ : 'text-[var(--color-text-tertiary)]'
499
+ )}
500
+ >
501
+ {crumb.label}
502
+ </button>
503
+ </React.Fragment>
504
+ ))}
505
+ </nav>
506
+
507
+ {/* Search */}
508
+ <div className="relative max-w-md flex-1 hidden lg:block">
509
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-tertiary)] w-5 h-5" />
510
+ <input
511
+ id="global-search"
512
+ type="text"
513
+ placeholder="Rechercher... (Alt+S)"
514
+ value={searchQuery}
515
+ onChange={(e) => setSearchQuery(e.target.value)}
516
+ className="w-full pl-10 pr-4 py-2 bg-[var(--color-surface-hover)] border border-[var(--color-border)] rounded-lg text-sm focus:outline-none focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary-light)]"
517
+ aria-label="Recherche globale"
518
+ />
519
+ </div>
520
+ </div>
521
+
522
+ <div className="flex items-center gap-3">
523
+ {/* Theme Selector */}
524
+ <div className="relative">
525
+ <button
526
+ onClick={() => setShowThemeMenu(!showThemeMenu)}
527
+ className="p-2 hover:bg-[var(--color-surface-hover)] rounded-lg transition-colors"
528
+ title="Changer le thème"
529
+ aria-label="Sélecteur de thème"
530
+ aria-expanded={showThemeMenu}
531
+ >
532
+ <Palette className="w-5 h-5 text-[var(--color-text-secondary)]" />
533
+ </button>
534
+ {showThemeMenu && (
535
+ <div
536
+ className="absolute right-0 mt-2 w-64 bg-[var(--color-background)] rounded-lg shadow-xl border border-[var(--color-border)] z-50"
537
+ role="menu"
538
+ aria-label="Sélection du thème"
539
+ >
540
+ <div className="p-2">
541
+ <p className="px-3 py-2 text-xs font-semibold text-[var(--color-text-tertiary)] uppercase">
542
+ Thèmes disponibles
543
+ </p>
544
+ <button
545
+ onClick={() => handleThemeChange('elegant')}
546
+ className={cn(
547
+ 'w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] transition-colors',
548
+ themeType === 'elegant' && 'bg-[var(--color-primary-light)]'
549
+ )}
550
+ role="menuitem"
551
+ >
552
+ <div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-accent)]" />
553
+ <div className="text-left">
554
+ <p className="text-sm font-medium">Élégance Sobre</p>
555
+ <p className="text-xs text-[var(--color-text-tertiary)]">Finance traditionnelle</p>
556
+ </div>
557
+ </button>
558
+ <button
559
+ onClick={() => handleThemeChange('fintech')}
560
+ className={cn(
561
+ 'w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] transition-colors',
562
+ themeType === 'fintech' && 'bg-[var(--color-primary-light)]'
563
+ )}
564
+ role="menuitem"
565
+ >
566
+ <div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[var(--color-success)] to-[var(--color-text-primary)]" />
567
+ <div className="text-left">
568
+ <p className="text-sm font-medium">Modern Fintech</p>
569
+ <p className="text-xs text-[var(--color-text-tertiary)]">Tableau de bord moderne</p>
570
+ </div>
571
+ </button>
572
+ <button
573
+ onClick={() => handleThemeChange('minimalist')}
574
+ className={cn(
575
+ 'w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] transition-colors',
576
+ themeType === 'minimalist' && 'bg-[var(--color-primary-light)]'
577
+ )}
578
+ role="menuitem"
579
+ >
580
+ <div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[var(--color-text-secondary)] to-[var(--color-accent)]" />
581
+ <div className="text-left">
582
+ <p className="text-sm font-medium">Minimaliste Premium</p>
583
+ <p className="text-xs text-[var(--color-text-tertiary)]">Élégance minimaliste</p>
584
+ </div>
585
+ </button>
586
+ </div>
587
+ </div>
588
+ )}
589
+ </div>
590
+
591
+ {/* Currency Display */}
592
+ <div className="flex items-center px-3 py-1.5 bg-[var(--color-surface)] rounded-lg border border-[var(--color-border)]">
593
+ <DollarSign className="w-4 h-4 text-[var(--color-primary)] mr-2" />
594
+ <span className="text-sm font-medium text-[var(--color-text-primary)]">FCFA</span>
595
+ </div>
596
+
597
+ {/* Notifications */}
598
+ <div className="relative">
599
+ <button
600
+ className="relative p-2 hover:bg-[var(--color-surface-hover)] rounded-lg transition-colors"
601
+ onClick={() => setShowNotifications(!showNotifications)}
602
+ aria-label={`Notifications ${notifications.filter(n => !n.read).length > 0 ? `(${notifications.filter(n => !n.read).length} non lues)` : ''}`}
603
+ aria-expanded={showNotifications}
604
+ >
605
+ <Bell className="w-5 h-5 text-[var(--color-text-secondary)]" />
606
+ {notifications.filter(n => !n.read).length > 0 && (
607
+ <span className="absolute top-1 right-1 w-2 h-2 bg-[var(--color-error)] rounded-full"></span>
608
+ )}
609
+ </button>
610
+
611
+ {showNotifications && (
612
+ <div
613
+ className="absolute right-0 mt-2 w-80 bg-[var(--color-background)] rounded-lg shadow-xl border border-[var(--color-border)] z-50 max-h-96 overflow-y-auto"
614
+ role="region"
615
+ aria-label="Centre de notifications"
616
+ >
617
+ <div className="p-4 border-b border-[var(--color-border)]">
618
+ <h3 className="font-semibold text-[var(--color-text-primary)]">Notifications</h3>
619
+ </div>
620
+ <div className="divide-y divide-[var(--color-border)]">
621
+ {notifications.map((notif) => (
622
+ <div
623
+ key={notif.id}
624
+ className={cn(
625
+ "p-4 hover:bg-[var(--color-surface-hover)] cursor-pointer",
626
+ !notif.read && "bg-[var(--color-primary-light)] bg-opacity-10"
627
+ )}
628
+ onClick={() => markNotificationAsRead(notif.id)}
629
+ >
630
+ <div className="flex items-start gap-3">
631
+ <div className={cn(
632
+ "w-2 h-2 rounded-full mt-2",
633
+ notif.type === 'error' && "bg-[var(--color-error)]",
634
+ notif.type === 'warning' && "bg-[var(--color-warning)]",
635
+ notif.type === 'success' && "bg-[var(--color-success)]",
636
+ notif.type === 'info' && "bg-[var(--color-info)]"
637
+ )} />
638
+ <div className="flex-1">
639
+ <p className="text-sm font-medium text-[var(--color-text-primary)]">{notif.title}</p>
640
+ <p className="text-xs text-[var(--color-text-secondary)] mt-1">{notif.message}</p>
641
+ <p className="text-xs text-[var(--color-text-tertiary)] mt-2">
642
+ {notif.timestamp.toLocaleTimeString()}
643
+ </p>
644
+ </div>
645
+ </div>
646
+ </div>
647
+ ))}
648
+ </div>
649
+ </div>
650
+ )}
651
+ </div>
652
+
653
+ {/* User Menu */}
654
+ <div className="relative">
655
+ <button
656
+ onClick={() => setShowUserMenu(!showUserMenu)}
657
+ className="flex items-center gap-2 p-2 hover:bg-[var(--color-surface-hover)] rounded-lg transition-colors"
658
+ aria-label="Menu utilisateur"
659
+ aria-expanded={showUserMenu}
660
+ >
661
+ <div className="w-8 h-8 bg-[var(--color-primary)] rounded-full flex items-center justify-center">
662
+ <User className="w-4 h-4 text-[var(--color-background)]" />
663
+ </div>
664
+ </button>
665
+ {showUserMenu && (
666
+ <div
667
+ className="absolute right-0 mt-2 w-56 bg-[var(--color-background)] rounded-lg shadow-xl border border-[var(--color-border)] z-50"
668
+ role="menu"
669
+ aria-label="Menu utilisateur"
670
+ >
671
+ <div className="p-2">
672
+ <button
673
+ className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] transition-colors"
674
+ role="menuitem"
675
+ >
676
+ <User className="w-4 h-4" />
677
+ <span className="text-sm">Mon profil</span>
678
+ </button>
679
+ <button
680
+ onClick={() => {
681
+ navigate('/settings');
682
+ setShowUserMenu(false);
683
+ }}
684
+ className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] transition-colors"
685
+ role="menuitem"
686
+ >
687
+ <Settings className="w-4 h-4" />
688
+ <span className="text-sm">Paramètres</span>
689
+ </button>
690
+ <button
691
+ className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] transition-colors"
692
+ role="menuitem"
693
+ >
694
+ <HelpCircle className="w-4 h-4" />
695
+ <span className="text-sm">Aide</span>
696
+ </button>
697
+ <hr className="my-2 border-[var(--color-border)]" />
698
+ <button
699
+ className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] text-[var(--color-error)] transition-colors"
700
+ role="menuitem"
701
+ >
702
+ <LogOut className="w-4 h-4" />
703
+ <span className="text-sm">Déconnexion</span>
704
+ </button>
705
+ </div>
706
+ </div>
707
+ )}
708
+ </div>
709
+ </div>
710
+ </header>
711
+
712
+ {/* Page Content */}
713
+ <main
714
+ id="main-content"
715
+ className="flex-1 overflow-y-auto bg-[var(--color-background)]"
716
+ role="main"
717
+ >
718
+ <div className="p-3 lg:p-4">
719
+ {children}
720
+ </div>
721
+ </main>
722
+ </div>
723
+ </div>
724
+ );
725
+ };
726
+
727
+ export default RewiseLayout;