ptechcore_ui 1.0.1 → 1.0.2

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