@syuttechnologies/layout 1.0.21 → 1.0.23

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.
@@ -0,0 +1,4475 @@
1
+ /**
2
+ * @prima/layout - Enterprise React Layout Component
3
+ */
4
+
5
+ import React, {
6
+ useState,
7
+ useEffect,
8
+ useCallback,
9
+ useMemo,
10
+ useTransition,
11
+ useDeferredValue,
12
+ useId,
13
+ } from "react";
14
+ import notificationService, { Notification as AppNotification } from "../services/notificationService";
15
+ import { ChangePasswordModal } from "./ChangePasswordModal";
16
+
17
+ // Component Registry Type - passed as prop from consuming application
18
+ export type ComponentRegistry = Record<string, React.ComponentType<any>>;
19
+
20
+
21
+ // Type for module config stored in localStorage (without component reference)
22
+ export interface StoredModuleConfig {
23
+ showHeader: boolean;
24
+ title: string;
25
+ description: string;
26
+ componentName: string; // Name of component in COMPONENT_REGISTRY
27
+ actions: string[];
28
+ permissions: string[];
29
+ breadcrumb: string[];
30
+ }
31
+
32
+
33
+
34
+ // Type Definitions
35
+ export interface LogoConfig {
36
+ src?: string;
37
+ alt?: string;
38
+ width?: string;
39
+ height?: string;
40
+ showText?: boolean;
41
+ }
42
+
43
+ export interface Colors {
44
+ primary: string;
45
+ secondary: string;
46
+ success: string;
47
+ warning: string;
48
+ danger: string;
49
+ info: string;
50
+ }
51
+
52
+ export interface BrandingConfig {
53
+ appName: string;
54
+ logo?: LogoConfig;
55
+ favicon?: string | null;
56
+ colors: Colors;
57
+ }
58
+
59
+ export interface LayoutConfig {
60
+ headerHeight: string;
61
+ sidebarWidth: string;
62
+ sidebarCollapsedWidth: string;
63
+ footerHeight: string;
64
+ tabBarHeight: string;
65
+ enableTabMode: boolean;
66
+ enableDarkMode: boolean;
67
+ enableFooter: boolean;
68
+ defaultTheme: "light" | "dark";
69
+ responsive: boolean;
70
+ collapsibleSidebar: boolean;
71
+ showBreadcrumbs: boolean;
72
+ autoCollapseSidebar: boolean;
73
+ compactModeStrategy: "sections-only" | "all-items" | "smart-grouping";
74
+ }
75
+
76
+ export interface FooterConfig {
77
+ appVersion: string;
78
+ environment: string;
79
+ copyright: string;
80
+ supportLink: string;
81
+ supportText: string;
82
+ }
83
+
84
+ export interface UserConfig {
85
+ name: string;
86
+ role: string;
87
+ avatar: string;
88
+ permissions: string[];
89
+ }
90
+
91
+ // ENHANCED: Multi-level menu support
92
+ export interface NavigationItem {
93
+ id: string;
94
+ title: string;
95
+ icon: string;
96
+ badge?: string;
97
+ active?: boolean;
98
+ permissions?: string[];
99
+ children?: NavigationItem[];
100
+ isExpanded?: boolean;
101
+ sectionName?: string; // Added for compatibility with CompactModeItem
102
+ onClick?: () => void; // Added for handling custom click actions
103
+ }
104
+
105
+ export interface NavigationSection {
106
+ section: string;
107
+ items: NavigationItem[];
108
+ icon?: string;
109
+ priority?: number;
110
+ }
111
+
112
+ export interface CompactModeItem {
113
+ id: string;
114
+ title: string;
115
+ icon: string;
116
+ sectionName: string;
117
+ badge?: string;
118
+ active?: boolean;
119
+ permissions?: string[];
120
+ children?: CompactModeItem[]; // NEW: Support nested items in compact mode
121
+ // Section-specific properties
122
+ isSection?: boolean;
123
+ section?: string;
124
+ items?: NavigationItem[];
125
+ }
126
+
127
+ // Enhanced Type Definitions for Enterprise Toolbar
128
+ export interface ToolbarAction {
129
+ id: string;
130
+ icon: string;
131
+ tooltip: string;
132
+ permission: string;
133
+ variant?: "primary" | "secondary" | "danger" | "success";
134
+ separator?: boolean;
135
+ }
136
+
137
+ export interface ModuleConfig {
138
+ showHeader: boolean | true;
139
+ title: string;
140
+ description: string;
141
+ component: React.ComponentType<any> | null;
142
+ actions: string[]; // Legacy - kept for backward compatibility
143
+ toolbarActions?: ToolbarAction[]; // New enterprise toolbar actions
144
+ permissions: string[];
145
+ breadcrumb: string[];
146
+ }
147
+
148
+ export interface ModulesConfig {
149
+ [key: string]: ModuleConfig;
150
+ }
151
+
152
+ export interface HooksConfig {
153
+ onModuleChange?: ((moduleId: string, config?: ModuleConfig) => void) | null;
154
+ onThemeChange?: ((theme: string) => void) | null;
155
+ onUserAction?: ((action: string, data: any) => void) | null;
156
+ onNavigate?: ((path: string, params?: any) => void) | null;
157
+ onChangePassword?: (() => void) | null;
158
+ onLogout?: (() => void) | null;
159
+ // Enterprise toolbar event handlers
160
+ onToolbarAction?:
161
+ | ((actionId: string, moduleId: string, data?: any) => void)
162
+ | null;
163
+ }
164
+
165
+ export interface EnterpriseLayoutConfig {
166
+ branding: BrandingConfig;
167
+ layout: LayoutConfig;
168
+ footer: FooterConfig;
169
+ user: UserConfig;
170
+ navigation: NavigationSection[];
171
+ modules: ModulesConfig;
172
+ hooks: HooksConfig;
173
+ }
174
+
175
+ export interface TabData {
176
+ title: string;
177
+ config?: ModuleConfig;
178
+ }
179
+
180
+ export interface IconSystemType {
181
+ [key: string]: () => React.JSX.Element;
182
+ }
183
+
184
+ export interface EnterpriseLayoutProps {
185
+ config: EnterpriseLayoutConfig;
186
+ componentRegistry: ComponentRegistry;
187
+ children?: React.ReactNode;
188
+ router?: any;
189
+ apiClient?: any;
190
+ authProvider?: any;
191
+ onModuleChange?: ((moduleId: string, config?: ModuleConfig) => void) | null;
192
+ onThemeChange?: ((theme: string) => void) | null;
193
+ }
194
+
195
+
196
+ // Enterprise Layout Component
197
+ export const EnterpriseLayout: React.FC<EnterpriseLayoutProps> = ({
198
+ config,
199
+ componentRegistry,
200
+ children,
201
+ router = null,
202
+ apiClient = null,
203
+ authProvider = null,
204
+ onModuleChange = null,
205
+ onThemeChange = null,
206
+ }: EnterpriseLayoutProps) => {
207
+ // State Management
208
+ const initialOverviewConfig = config.modules["overview"] || config.modules["dashboard"] || { title: "Overview", description: "Dashboard overview", component: null, actions: [], permissions: [], breadcrumb: ["Dashboard", "Overview"], showHeader: false };
209
+ const [openTabs, setOpenTabs] = useState<Map<string, TabData>>(
210
+ new Map([["overview", { title: "Overview", config: initialOverviewConfig }]])
211
+ );
212
+ const [activeTab, setActiveTab] = useState<string>("overview");
213
+ const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(false);
214
+ const [isMobile, setIsMobile] = useState<boolean>(false);
215
+ const [mobileMenuOpen, setMobileMenuOpen] = useState<boolean>(false);
216
+ const [expandedSections, setExpandedSections] = useState<Set<string>>(
217
+ new Set(config.navigation.map((n) => n.section))
218
+ );
219
+
220
+ // NEW: State for managing multi-level menu expansion
221
+ const [expandedSubItems, setExpandedSubItems] = useState<Set<string>>(
222
+ new Set()
223
+ );
224
+
225
+ const [tabModeEnabled, setTabModeEnabled] = useState<boolean>(
226
+ config.layout.enableTabMode
227
+ );
228
+ const [darkMode, setDarkMode] = useState<boolean>(
229
+ config.layout.defaultTheme === "dark"
230
+ );
231
+ const [tabOverflowMenuOpen, setTabOverflowMenuOpen] = useState<boolean>(false);
232
+ const [footerVisible, setFooterVisible] = useState<boolean>(false);
233
+ const [breadcrumbs, setBreadcrumbs] = useState<string[]>([
234
+ "Dashboard",
235
+ "Overview",
236
+ ]);
237
+
238
+ // Entity and Financial Year information
239
+ const [currentEntity, setCurrentEntity] = useState<string>("Sample Entity A");
240
+ const [currentBusinessUnit, setCurrentBusinessUnit] = useState<string>("Sample Business Unit 1");
241
+ const [financialYear, setFinancialYear] = useState<string>("FY 2024-25");
242
+
243
+ // Available options for selection
244
+ const availableEntities = [
245
+ "Sample Entity A",
246
+ "Sample Entity B",
247
+ "Sample Entity C",
248
+ "Test Entity 1",
249
+ "Test Entity 2"
250
+ ];
251
+
252
+ const availableFinancialYears = [
253
+ "FY 2024-25",
254
+ "FY 2023-24",
255
+ "FY 2022-23",
256
+ "FY 2021-22"
257
+ ];
258
+
259
+ // Dropdown states
260
+ const [entityDropdownOpen, setEntityDropdownOpen] = useState<boolean>(false);
261
+ const [fyDropdownOpen, setFyDropdownOpen] = useState<boolean>(false);
262
+
263
+ // FIX: Initialize persona panel as closed to prevent initial popup
264
+ const [personaPanelOpen, setPersonaPanelOpen] = useState<boolean>(false);
265
+ const [isInitialized, setIsInitialized] = useState<boolean>(false); // NEW: Track initialization
266
+
267
+ // Notification panel state
268
+ const [notificationPanelOpen, setNotificationPanelOpen] = useState<boolean>(false);
269
+
270
+ // Change Password Modal state
271
+ const [changePasswordModalOpen, setChangePasswordModalOpen] = useState<boolean>(false);
272
+ const [isChangingPassword, setIsChangingPassword] = useState<boolean>(false);
273
+ const [notifications, setNotifications] = useState([
274
+ { id: 1, type: 'success', title: 'Welcome!', message: 'Your account has been successfully verified', time: '5 min ago', read: false },
275
+ { id: 2, type: 'info', title: 'System Update', message: 'New features are now available in the dashboard', time: '1 hour ago', read: false },
276
+ { id: 3, type: 'warning', title: 'Action Required', message: 'Please update your profile information', time: '2 hours ago', read: true },
277
+ { id: 4, type: 'error', title: 'Failed Transaction', message: 'Your recent transaction could not be processed', time: '1 day ago', read: true }
278
+ ]);
279
+ const [notificationFilter, setNotificationFilter] = useState<'all' | 'unread' | 'read'>('all');
280
+ const [notificationGrouping, setNotificationGrouping] = useState<'none' | 'type' | 'time'>('none');
281
+
282
+ // Dynamic navigation menu from localStorage
283
+ const [dynamicNavigation, setDynamicNavigation] = useState<NavigationSection[]>(config.navigation);
284
+
285
+ // Dynamic modules configuration from localStorage
286
+ const [dynamicModules, setDynamicModules] = useState<ModulesConfig>(config.modules);
287
+
288
+ // Dynamic user data from localStorage
289
+ const [dynamicUser, setDynamicUser] = useState<UserConfig>(config.user);
290
+
291
+ // Dynamic app title from localStorage
292
+ const [dynamicAppTitle, setDynamicAppTitle] = useState<string>(config.branding.appName);
293
+
294
+ // Load all dynamic config from localStorage on mount
295
+ useEffect(() => {
296
+ // Load user data
297
+ const storedUser = localStorage.getItem('user');
298
+ if (storedUser) {
299
+ try {
300
+ const parsedUser = JSON.parse(storedUser);
301
+ setDynamicUser({
302
+ name: parsedUser.name || config.user.name,
303
+ role: parsedUser.role || config.user.role,
304
+ avatar: parsedUser.avatar || parsedUser.name?.split(' ').map((n: string) => n[0]).join('').toUpperCase() || config.user.avatar,
305
+ permissions: parsedUser.permissions || config.user.permissions,
306
+ });
307
+ } catch (error) {
308
+ console.error('Failed to parse user data from localStorage:', error);
309
+ }
310
+ }
311
+
312
+ // Load app config (title)
313
+ const storedAppConfig = localStorage.getItem('APP_CONFIG');
314
+ if (storedAppConfig) {
315
+ try {
316
+ const parsedAppConfig = JSON.parse(storedAppConfig);
317
+ if (parsedAppConfig.appTitle) {
318
+ setDynamicAppTitle(parsedAppConfig.appTitle);
319
+ }
320
+ } catch (error) {
321
+ console.error('Failed to parse app config from localStorage:', error);
322
+ }
323
+ }
324
+
325
+ // Load navigation menu
326
+ const storedNavigation = localStorage.getItem('NAVIGATION_MENU');
327
+ if (storedNavigation) {
328
+ try {
329
+ const parsedNavigation = JSON.parse(storedNavigation);
330
+ if (Array.isArray(parsedNavigation) && parsedNavigation.length > 0) {
331
+ setDynamicNavigation(parsedNavigation);
332
+ // Update expanded sections for the new navigation
333
+ setExpandedSections(new Set(parsedNavigation.map((n: NavigationSection) => n.section)));
334
+ }
335
+ } catch (error) {
336
+ console.error('Failed to parse navigation menu from localStorage:', error);
337
+ }
338
+ }
339
+
340
+ // Load modules configuration
341
+ const storedModules = localStorage.getItem('MODULES_CONFIG');
342
+ if (storedModules) {
343
+ try {
344
+ const parsedModules: Record<string, StoredModuleConfig> = JSON.parse(storedModules);
345
+ // Convert stored module configs to actual ModuleConfig with component references
346
+ const modulesWithComponents: ModulesConfig = {};
347
+
348
+ for (const [moduleId, storedConfig] of Object.entries(parsedModules)) {
349
+ modulesWithComponents[moduleId] = {
350
+ showHeader: storedConfig.showHeader,
351
+ title: storedConfig.title,
352
+ description: storedConfig.description,
353
+ component: componentRegistry[storedConfig.componentName] || null,
354
+ actions: storedConfig.actions,
355
+ permissions: storedConfig.permissions,
356
+ breadcrumb: storedConfig.breadcrumb,
357
+ };
358
+ }
359
+
360
+ setDynamicModules(modulesWithComponents);
361
+ } catch (error) {
362
+ console.error('Failed to parse modules config from localStorage:', error);
363
+ }
364
+ }
365
+ }, []);
366
+
367
+ // Sync dynamicModules when config.modules prop changes (if no localStorage override)
368
+ useEffect(() => {
369
+ const storedModules = localStorage.getItem('MODULES_CONFIG');
370
+ if (!storedModules && config.modules) {
371
+ setDynamicModules(config.modules);
372
+ }
373
+ }, [config.modules]);
374
+
375
+ // Sync dynamicNavigation when config.navigation prop changes (if no localStorage override)
376
+ useEffect(() => {
377
+ const storedNavigation = localStorage.getItem('NAVIGATION_MENU');
378
+ if (!storedNavigation && config.navigation) {
379
+ setDynamicNavigation(config.navigation);
380
+ setExpandedSections(new Set(config.navigation.map((n: NavigationSection) => n.section)));
381
+ }
382
+ }, [config.navigation]);
383
+
384
+ // User preference for auto-collapse (configurable)
385
+ const [autoCollapseEnabled, setAutoCollapseEnabled] = useState<boolean>(
386
+ config.layout.autoCollapseSidebar
387
+ );
388
+
389
+ // Overview tab section management
390
+ const [expandedOverviewSections, setExpandedOverviewSections] = useState<Set<string>>(
391
+ new Set()
392
+ );
393
+
394
+ // React 18 Concurrent Features
395
+ const [isPending, startTransition] = useTransition();
396
+ const deferredActiveTab = useDeferredValue(activeTab);
397
+ const componentId = useId();
398
+
399
+ // FIX: Mark as initialized after first render to prevent initial popup flashes
400
+ useEffect(() => {
401
+ const timer = setTimeout(() => {
402
+ setIsInitialized(true);
403
+ }, 100); // Small delay to prevent initial flashes
404
+
405
+ return () => clearTimeout(timer);
406
+ }, []);
407
+
408
+ // Subscribe to notification events from any component
409
+ useEffect(() => {
410
+ const unsubscribe = notificationService.subscribe((notification: AppNotification) => {
411
+ setNotifications(prev => [notification, ...prev]);
412
+ });
413
+
414
+ return () => unsubscribe();
415
+ }, []);
416
+
417
+ // Dynamic Icon System - Enhanced with enterprise action icons
418
+ const IconSystem: IconSystemType = useMemo(
419
+ () => ({
420
+ menu: () => (
421
+ <svg
422
+ width="18"
423
+ height="18"
424
+ viewBox="0 0 24 24"
425
+ fill="none"
426
+ stroke="currentColor"
427
+ strokeWidth="2"
428
+ >
429
+ <line x1="3" y1="6" x2="21" y2="6"></line>
430
+ <line x1="3" y1="12" x2="21" y2="12"></line>
431
+ <line x1="3" y1="18" x2="21" y2="18"></line>
432
+ </svg>
433
+ ),
434
+ notification: () => (
435
+ <svg
436
+ width="18"
437
+ height="18"
438
+ viewBox="0 0 24 24"
439
+ fill="none"
440
+ stroke="currentColor"
441
+ strokeWidth="2"
442
+ >
443
+ <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
444
+ <path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
445
+ </svg>
446
+ ),
447
+ settings: () => (
448
+ <svg
449
+ width="18"
450
+ height="18"
451
+ viewBox="0 0 24 24"
452
+ fill="none"
453
+ stroke="currentColor"
454
+ strokeWidth="2"
455
+ >
456
+ <circle cx="12" cy="12" r="5"></circle>
457
+ <line x1="12" y1="1" x2="12" y2="3"></line>
458
+ <line x1="12" y1="21" x2="12" y2="23"></line>
459
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
460
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
461
+ <line x1="1" y1="12" x2="3" y2="12"></line>
462
+ <line x1="21" y1="12" x2="23" y2="12"></line>
463
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
464
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
465
+ </svg>
466
+ ),
467
+ chevronDown: () => (
468
+ <svg
469
+ width="16"
470
+ height="16"
471
+ viewBox="0 0 24 24"
472
+ fill="none"
473
+ stroke="currentColor"
474
+ strokeWidth="2"
475
+ >
476
+ <polyline points="6,9 12,15 18,9"></polyline>
477
+ </svg>
478
+ ),
479
+ home: () => (
480
+ <svg
481
+ style={{ width: "1.125rem", height: "1.125rem", opacity: 0.85 }}
482
+ viewBox="0 0 24 24"
483
+ fill="none"
484
+ stroke="currentColor"
485
+ strokeWidth="2"
486
+ >
487
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
488
+ </svg>
489
+ ),
490
+ user: () => (
491
+ <svg
492
+ style={{ width: "1.125rem", height: "1.125rem", opacity: 0.85 }}
493
+ viewBox="0 0 24 24"
494
+ fill="none"
495
+ stroke="currentColor"
496
+ strokeWidth="2"
497
+ >
498
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
499
+ <circle cx="12" cy="7" r="4"></circle>
500
+ </svg>
501
+ ),
502
+ users: () => (
503
+ <svg
504
+ style={{ width: "1.125rem", height: "1.125rem", opacity: 0.85 }}
505
+ viewBox="0 0 24 24"
506
+ fill="none"
507
+ stroke="currentColor"
508
+ strokeWidth="2"
509
+ >
510
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
511
+ <circle cx="9" cy="7" r="4"></circle>
512
+ <path d="m22 21-3-3m0 0a4 4 0 0 0 0-4 4 4 0 0 0 0 4Z"></path>
513
+ </svg>
514
+ ),
515
+ tabs: () => (
516
+ <svg
517
+ width="16"
518
+ height="16"
519
+ viewBox="0 0 24 24"
520
+ fill="none"
521
+ stroke="currentColor"
522
+ strokeWidth="2"
523
+ >
524
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
525
+ <line x1="9" y1="9" x2="15" y2="9"></line>
526
+ <line x1="9" y1="12" x2="15" y2="12"></line>
527
+ <line x1="9" y1="15" x2="15" y2="15"></line>
528
+ </svg>
529
+ ),
530
+ window: () => (
531
+ <svg
532
+ width="16"
533
+ height="16"
534
+ viewBox="0 0 24 24"
535
+ fill="none"
536
+ stroke="currentColor"
537
+ strokeWidth="2"
538
+ >
539
+ <rect x="2" y="4" width="20" height="16" rx="2"></rect>
540
+ <path d="M10 4v16"></path>
541
+ </svg>
542
+ ),
543
+ chevronRight: () => (
544
+ <svg
545
+ width="14"
546
+ height="14"
547
+ viewBox="0 0 24 24"
548
+ fill="none"
549
+ stroke="currentColor"
550
+ strokeWidth="2"
551
+ >
552
+ <polyline points="9,18 15,12 9,6"></polyline>
553
+ </svg>
554
+ ),
555
+ arrowLeft: () => (
556
+ <svg
557
+ width="16"
558
+ height="16"
559
+ viewBox="0 0 24 24"
560
+ fill="none"
561
+ stroke="currentColor"
562
+ strokeWidth="2"
563
+ >
564
+ <line x1="19" y1="12" x2="5" y2="12"></line>
565
+ <polyline points="12,19 5,12 12,5"></polyline>
566
+ </svg>
567
+ ),
568
+ plus: () => (
569
+ <svg
570
+ width="16"
571
+ height="16"
572
+ viewBox="0 0 24 24"
573
+ fill="none"
574
+ stroke="currentColor"
575
+ strokeWidth="2"
576
+ >
577
+ <circle cx="12" cy="12" r="10"></circle>
578
+ <line x1="12" y1="8" x2="12" y2="16"></line>
579
+ <line x1="8" y1="12" x2="16" y2="12"></line>
580
+ </svg>
581
+ ),
582
+ file: () => (
583
+ <svg
584
+ width="16"
585
+ height="16"
586
+ viewBox="0 0 24 24"
587
+ fill="none"
588
+ stroke="currentColor"
589
+ strokeWidth="2"
590
+ >
591
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
592
+ <polyline points="14,2 14,8 20,8"></polyline>
593
+ </svg>
594
+ ),
595
+ breadcrumbSeparator: () => (
596
+ <svg
597
+ width="12"
598
+ height="12"
599
+ viewBox="0 0 24 24"
600
+ fill="none"
601
+ stroke="currentColor"
602
+ strokeWidth="2"
603
+ >
604
+ <polyline points="9,18 15,12 9,6"></polyline>
605
+ </svg>
606
+ ),
607
+ lock: () => (
608
+ <svg
609
+ width="16"
610
+ height="16"
611
+ viewBox="0 0 24 24"
612
+ fill="none"
613
+ stroke="currentColor"
614
+ strokeWidth="2"
615
+ >
616
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
617
+ <circle cx="12" cy="16" r="1"></circle>
618
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
619
+ </svg>
620
+ ),
621
+ logout: () => (
622
+ <svg
623
+ width="16"
624
+ height="16"
625
+ viewBox="0 0 24 24"
626
+ fill="none"
627
+ stroke="currentColor"
628
+ strokeWidth="2"
629
+ >
630
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
631
+ <polyline points="16,17 21,12 16,7"></polyline>
632
+ <line x1="21" y1="12" x2="9" y2="12"></line>
633
+ </svg>
634
+ ),
635
+ footer: () => (
636
+ <svg
637
+ width="16"
638
+ height="16"
639
+ viewBox="0 0 24 24"
640
+ fill="none"
641
+ stroke="currentColor"
642
+ strokeWidth="2"
643
+ >
644
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
645
+ <line x1="8" y1="21" x2="16" y2="21"></line>
646
+ <line x1="12" y1="17" x2="12" y2="21"></line>
647
+ </svg>
648
+ ),
649
+ folder: () => (
650
+ <svg
651
+ width="16"
652
+ height="16"
653
+ viewBox="0 0 24 24"
654
+ fill="none"
655
+ stroke="currentColor"
656
+ strokeWidth="2"
657
+ >
658
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
659
+ </svg>
660
+ ),
661
+ collapse: () => (
662
+ <svg
663
+ width="16"
664
+ height="16"
665
+ viewBox="0 0 24 24"
666
+ fill="none"
667
+ stroke="currentColor"
668
+ strokeWidth="2"
669
+ >
670
+ <polyline points="11,19 2,12 11,5"></polyline>
671
+ <polyline points="22,19 13,12 22,5"></polyline>
672
+ </svg>
673
+ ),
674
+ service: () => (
675
+ <svg
676
+ width="18"
677
+ height="18"
678
+ viewBox="0 0 24 24"
679
+ fill="none"
680
+ stroke="currentColor"
681
+ strokeWidth="2"
682
+ >
683
+ <circle cx="12" cy="10" r="3"></circle>
684
+ <path d="M3 20c1.5-4.5 4.5-7.5 9-7.5s7.5 3 9 7.5"></path>
685
+ <circle cx="17" cy="5" r="1"></circle>
686
+ <circle cx="19" cy="3" r="1"></circle>
687
+ </svg>
688
+ ),
689
+ currency: () => (
690
+ <svg
691
+ width="18"
692
+ height="18"
693
+ viewBox="0 0 24 24"
694
+ fill="none"
695
+ stroke="currentColor"
696
+ strokeWidth="2"
697
+ >
698
+ <circle cx="12" cy="12" r="10"></circle>
699
+ <path d="M12 6v12"></path>
700
+ <path d="M15 9.5a3 3 0 1 0 0 5"></path>
701
+ <path d="M9 9.5a3 3 0 1 1 0 5"></path>
702
+ </svg>
703
+ ),
704
+ chartofAccount: () => (
705
+ <svg
706
+ width="18"
707
+ height="18"
708
+ viewBox="0 0 24 24"
709
+ fill="none"
710
+ stroke="currentColor"
711
+ strokeWidth="2"
712
+ >
713
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
714
+ <line x1="9" y1="7" x2="9" y2="17"></line>
715
+ <line x1="15" y1="7" x2="15" y2="17"></line>
716
+ <line x1="7" y1="11" x2="17" y2="11"></line>
717
+ <line x1="7" y1="15" x2="17" y2="15"></line>
718
+ <line x1="7" y1="7" x2="17" y2="7"></line>
719
+ </svg>
720
+ ),
721
+ configure: () => (
722
+ <svg
723
+ width="18"
724
+ height="18"
725
+ viewBox="0 0 24 24"
726
+ fill="none"
727
+ stroke="currentColor"
728
+ strokeWidth="2"
729
+ >
730
+ <circle cx="12" cy="12" r="3"></circle>
731
+ <path d="M12 1v6m0 6v6m11-7h-6m-6 0H1m15.5-6.5L19 5l-1.5-1.5M6.5 17.5L5 19l-1.5-1.5M17.5 17.5L19 19l1.5-1.5M6.5 6.5L5 5 3.5 6.5"></path>
732
+ </svg>
733
+ ),
734
+ search: () => (
735
+ <svg
736
+ width="18"
737
+ height="18"
738
+ viewBox="0 0 24 24"
739
+ fill="none"
740
+ stroke="currentColor"
741
+ strokeWidth="2"
742
+ >
743
+ <circle cx="11" cy="11" r="8"></circle>
744
+ <path d="m21 21-4.35-4.35"></path>
745
+ </svg>
746
+ ),
747
+ }),
748
+ []
749
+ );
750
+
751
+ // CSS Styles
752
+ const dynamicStyles = useMemo(() => {
753
+ const colors = config.branding.colors;
754
+ const footerHeight =
755
+ footerVisible && config.layout.enableFooter
756
+ ? config.layout.footerHeight
757
+ : "0px";
758
+
759
+ return `
760
+ * {
761
+ margin: 0;
762
+ padding: 0;
763
+ box-sizing: border-box;
764
+ }
765
+
766
+ :root {
767
+ --primary: ${colors.primary};
768
+ --secondary: ${colors.secondary};
769
+ --success: ${colors.success};
770
+ --warning: ${colors.warning};
771
+ --danger: ${colors.danger};
772
+ --info: ${colors.info};
773
+
774
+ --bg-primary: #ffffff;
775
+ --bg-secondary: #f8fafc;
776
+ --bg-gradient: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
777
+ --text-primary: #1e293b;
778
+ --text-secondary: #64748b;
779
+ --text-muted: #94a3b8;
780
+ --border-color: rgba(226, 232, 240, 0.5);
781
+ --glass-bg: rgba(255, 255, 255, 0.95);
782
+ --shadow: 0 8px 12px -2px rgba(0, 0, 0, 0.1);
783
+ --accent-primary: ${colors.primary};
784
+ --accent-gradient: linear-gradient(135deg, ${colors.primary}, ${colors.info});
785
+
786
+ --header-height: ${config.layout.headerHeight};
787
+ --sidebar-width: ${config.layout.sidebarWidth};
788
+ --sidebar-collapsed-width: ${config.layout.sidebarCollapsedWidth};
789
+ --footer-height: ${footerHeight};
790
+ --tab-bar-height: ${config.layout.tabBarHeight};
791
+ }
792
+
793
+ [data-theme="dark"] {
794
+ --bg-primary: #0f172a;
795
+ --bg-secondary: #1e293b;
796
+ --bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
797
+ --text-primary: #f1f5f9;
798
+ --text-secondary: #cbd5e1;
799
+ --text-muted: #64748b;
800
+ --border-color: rgba(71, 85, 105, 0.5);
801
+ --glass-bg: rgba(15, 23, 42, 0.95);
802
+ --shadow: 0 8px 12px -2px rgba(0, 0, 0, 0.3);
803
+ }
804
+
805
+ html {
806
+ margin: 0;
807
+ padding: 0;
808
+ width: 100%;
809
+ overflow: hidden;
810
+ }
811
+
812
+ body {
813
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
814
+ background: var(--bg-gradient);
815
+ color: var(--text-primary);
816
+ line-height: 1.5;
817
+ height: 100vh;
818
+ width: 100%;
819
+ margin: 0;
820
+ padding: 0;
821
+ overflow: hidden;
822
+ transition: background 0.3s ease, color 0.3s ease;
823
+ }
824
+
825
+ .enterprise-layout {
826
+ width: 100%;
827
+ height: 100vh;
828
+ display: flex;
829
+ flex-direction: column;
830
+ overflow: hidden;
831
+ }
832
+
833
+ .layout-header {
834
+ position: fixed;
835
+ top: 0;
836
+ left: 0;
837
+ right: 0;
838
+ height: var(--header-height);
839
+ background: var(--glass-bg);
840
+ backdrop-filter: blur(15px);
841
+ border-bottom: 1px solid var(--border-color);
842
+ display: flex;
843
+ align-items: center;
844
+ justify-content: space-between;
845
+ padding: 0 1rem;
846
+ z-index: 1000;
847
+ box-shadow: var(--shadow);
848
+ }
849
+
850
+ .header-left {
851
+ display: flex;
852
+ align-items: center;
853
+ gap: 0.75rem;
854
+ flex: 0 0 auto;
855
+ min-width: 0;
856
+ }
857
+
858
+ .header-center {
859
+ display: flex;
860
+ align-items: center;
861
+ flex: 1;
862
+ justify-content: center;
863
+ min-width: 0;
864
+ }
865
+
866
+ .header-right {
867
+ display: flex;
868
+ align-items: center;
869
+ gap: 0.75rem;
870
+ flex-shrink: 0;
871
+ position: relative;
872
+ }
873
+
874
+ .header-info-section {
875
+ display: flex;
876
+ align-items: center;
877
+ gap: 0.5rem;
878
+ height: fit-content;
879
+ max-height: calc(var(--header-height) - 1rem);
880
+ }
881
+
882
+ .header-info-card {
883
+ display: flex;
884
+ align-items: center;
885
+ gap: 0.5rem;
886
+ padding: 0.375rem 0.875rem;
887
+ border-radius: 1.25rem;
888
+ backdrop-filter: blur(10px);
889
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
890
+ cursor: pointer;
891
+ position: relative;
892
+ overflow: visible;
893
+ height: fit-content;
894
+ max-height: 2.25rem;
895
+ }
896
+
897
+ .header-info-card-blue {
898
+ background: linear-gradient(135deg, rgba(37, 99, 235, 0.1) 0%, rgba(59, 130, 246, 0.06) 100%);
899
+ border: 1px solid rgba(37, 99, 235, 0.2);
900
+ box-shadow: 0 2px 6px rgba(37, 99, 235, 0.08),
901
+ 0 1px 2px rgba(0, 0, 0, 0.04);
902
+ }
903
+
904
+ .header-info-card-green {
905
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(34, 197, 94, 0.06) 100%);
906
+ border: 1px solid rgba(16, 185, 129, 0.2);
907
+ box-shadow: 0 2px 6px rgba(16, 185, 129, 0.08),
908
+ 0 1px 2px rgba(0, 0, 0, 0.04);
909
+ }
910
+
911
+ .header-info-card::before {
912
+ content: '';
913
+ position: absolute;
914
+ top: 50%;
915
+ left: 50%;
916
+ width: 100%;
917
+ height: 100%;
918
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.12) 0%, transparent 70%);
919
+ transform: translate(-50%, -50%) scale(0);
920
+ transition: transform 0.5s ease-out;
921
+ pointer-events: none;
922
+ }
923
+
924
+ .header-info-card:hover::before {
925
+ transform: translate(-50%, -50%) scale(2);
926
+ }
927
+
928
+ .header-info-card-blue:hover {
929
+ transform: translateY(-2px);
930
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.18),
931
+ 0 2px 4px rgba(0, 0, 0, 0.06);
932
+ border-color: rgba(37, 99, 235, 0.35);
933
+ background: linear-gradient(135deg, rgba(37, 99, 235, 0.14) 0%, rgba(59, 130, 246, 0.08) 100%);
934
+ }
935
+
936
+ .header-info-card-green:hover {
937
+ transform: translateY(-2px);
938
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.18),
939
+ 0 2px 4px rgba(0, 0, 0, 0.06);
940
+ border-color: rgba(16, 185, 129, 0.35);
941
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.14) 0%, rgba(34, 197, 94, 0.08) 100%);
942
+ }
943
+
944
+ .header-info-card:active {
945
+ transform: translateY(0) scale(0.98);
946
+ transition: all 0.1s ease;
947
+ }
948
+
949
+ .header-info-icon {
950
+ display: flex;
951
+ align-items: center;
952
+ justify-content: center;
953
+ width: 1.25rem;
954
+ height: 1.25rem;
955
+ flex-shrink: 0;
956
+ }
957
+
958
+ .header-info-card-blue .header-info-icon {
959
+ color: #2563eb;
960
+ }
961
+
962
+ .header-info-card-green .header-info-icon {
963
+ color: #10b981;
964
+ }
965
+
966
+ .header-info-content {
967
+ display: flex;
968
+ flex-direction: column;
969
+ gap: 0.0625rem;
970
+ min-width: 0;
971
+ }
972
+
973
+ .header-info-label {
974
+ font-size: 0.5625rem;
975
+ text-transform: uppercase;
976
+ letter-spacing: 0.08em;
977
+ font-weight: 700;
978
+ line-height: 1;
979
+ white-space: nowrap;
980
+ }
981
+
982
+ .header-info-card-blue .header-info-label {
983
+ color: #1d4ed8;
984
+ opacity: 0.85;
985
+ }
986
+
987
+ .header-info-card-green .header-info-label {
988
+ color: #059669;
989
+ opacity: 0.85;
990
+ }
991
+
992
+ .header-info-value {
993
+ font-size: 0.8125rem;
994
+ font-weight: 700;
995
+ white-space: nowrap;
996
+ letter-spacing: -0.01em;
997
+ line-height: 1.2;
998
+ }
999
+
1000
+ .header-info-card-blue .header-info-value {
1001
+ color: #1e3a8a;
1002
+ }
1003
+
1004
+ .header-info-card-green .header-info-value {
1005
+ color: #065f46;
1006
+ }
1007
+
1008
+ .header-info-card:hover .header-info-label,
1009
+ .header-info-card:hover .header-info-value {
1010
+ opacity: 1;
1011
+ }
1012
+
1013
+ .header-info-card.selectable {
1014
+ cursor: pointer;
1015
+ padding-right: 0.625rem;
1016
+ }
1017
+
1018
+ .header-info-chevron {
1019
+ display: flex;
1020
+ align-items: center;
1021
+ justify-content: center;
1022
+ margin-left: 0.25rem;
1023
+ transition: transform 0.3s ease;
1024
+ opacity: 0.7;
1025
+ }
1026
+
1027
+ .header-info-card:hover .header-info-chevron {
1028
+ opacity: 1;
1029
+ }
1030
+
1031
+ .header-info-dropdown {
1032
+ position: absolute;
1033
+ top: calc(100% + 0.5rem);
1034
+ left: 0;
1035
+ right: 0;
1036
+ background: var(--glass-bg);
1037
+ border-radius: 0.75rem;
1038
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15),
1039
+ 0 4px 8px rgba(0, 0, 0, 0.1);
1040
+ backdrop-filter: blur(12px);
1041
+ z-index: 1000;
1042
+ overflow: hidden;
1043
+ animation: dropdownSlideIn 0.2s ease-out;
1044
+ min-width: 180px;
1045
+ }
1046
+
1047
+ @keyframes dropdownSlideIn {
1048
+ from {
1049
+ opacity: 0;
1050
+ transform: translateY(-8px);
1051
+ }
1052
+ to {
1053
+ opacity: 1;
1054
+ transform: translateY(0);
1055
+ }
1056
+ }
1057
+
1058
+ .header-info-dropdown-blue {
1059
+ border: 1px solid rgba(37, 99, 235, 0.3);
1060
+ }
1061
+
1062
+ .header-info-dropdown-green {
1063
+ border: 1px solid rgba(16, 185, 129, 0.3);
1064
+ }
1065
+
1066
+ .header-info-dropdown-item {
1067
+ display: flex;
1068
+ align-items: center;
1069
+ justify-content: space-between;
1070
+ padding: 0.625rem 0.875rem;
1071
+ font-size: 0.8125rem;
1072
+ font-weight: 500;
1073
+ color: var(--text-primary);
1074
+ transition: all 0.2s ease;
1075
+ cursor: pointer;
1076
+ gap: 0.5rem;
1077
+ }
1078
+
1079
+ .header-info-dropdown-item:hover {
1080
+ background: rgba(37, 99, 235, 0.08);
1081
+ }
1082
+
1083
+ .header-info-dropdown-blue .header-info-dropdown-item:hover {
1084
+ background: rgba(37, 99, 235, 0.12);
1085
+ color: #1e40af;
1086
+ }
1087
+
1088
+ .header-info-dropdown-green .header-info-dropdown-item:hover {
1089
+ background: rgba(16, 185, 129, 0.12);
1090
+ color: #047857;
1091
+ }
1092
+
1093
+ .header-info-dropdown-item.active {
1094
+ font-weight: 700;
1095
+ background: rgba(37, 99, 235, 0.1);
1096
+ }
1097
+
1098
+ .header-info-dropdown-blue .header-info-dropdown-item.active {
1099
+ background: rgba(37, 99, 235, 0.15);
1100
+ color: #1e40af;
1101
+ }
1102
+
1103
+ .header-info-dropdown-green .header-info-dropdown-item.active {
1104
+ background: rgba(16, 185, 129, 0.15);
1105
+ color: #047857;
1106
+ }
1107
+
1108
+ .header-info-dropdown-item svg {
1109
+ flex-shrink: 0;
1110
+ }
1111
+
1112
+ .app-logo {
1113
+ display: flex;
1114
+ align-items: center;
1115
+ gap: 0.5rem;
1116
+ font-size: 1.125rem;
1117
+ font-weight: 700;
1118
+ background: var(--accent-gradient);
1119
+ -webkit-background-clip: text;
1120
+ -webkit-text-fill-color: transparent;
1121
+ background-clip: text;
1122
+ white-space: nowrap;
1123
+ flex-shrink: 0;
1124
+ }
1125
+
1126
+ .logo-image {
1127
+ flex-shrink: 0;
1128
+ border-radius: 0.375rem;
1129
+ }
1130
+
1131
+ .breadcrumb-container {
1132
+ display: flex;
1133
+ align-items: center;
1134
+ font-size: 0.75rem;
1135
+ opacity: 0.8;
1136
+ margin-left: 1rem;
1137
+ }
1138
+
1139
+ .breadcrumb {
1140
+ display: flex;
1141
+ align-items: center;
1142
+ gap: 0.375rem;
1143
+ color: var(--text-secondary);
1144
+ }
1145
+
1146
+ .breadcrumb-item {
1147
+ display: flex;
1148
+ align-items: center;
1149
+ gap: 0.25rem;
1150
+ }
1151
+
1152
+ .breadcrumb-link {
1153
+ color: var(--text-secondary);
1154
+ text-decoration: none;
1155
+ transition: color 0.2s ease;
1156
+ }
1157
+
1158
+ .breadcrumb-link:hover {
1159
+ color: var(--accent-primary);
1160
+ }
1161
+
1162
+ .breadcrumb-current {
1163
+ color: var(--accent-primary);
1164
+ font-weight: 500;
1165
+ }
1166
+
1167
+ .breadcrumb-separator {
1168
+ color: var(--text-muted);
1169
+ opacity: 0.6;
1170
+ }
1171
+
1172
+ .layout-body {
1173
+ display: flex;
1174
+ height: calc(100vh - var(--header-height) - var(--footer-height));
1175
+ margin-top: var(--header-height);
1176
+ overflow: hidden;
1177
+ }
1178
+
1179
+ .layout-body.footer-hidden {
1180
+ height: calc(100vh - var(--header-height));
1181
+ }
1182
+
1183
+ .layout-sidebar {
1184
+ width: var(--sidebar-width);
1185
+ background: var(--glass-bg);
1186
+ backdrop-filter: blur(15px);
1187
+ border-right: 1px solid var(--border-color);
1188
+ overflow-y: auto;
1189
+ overflow-x: hidden;
1190
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1191
+ flex-shrink: 0;
1192
+ will-change: width;
1193
+ backface-visibility: hidden;
1194
+ contain: layout style paint;
1195
+ }
1196
+
1197
+ .layout-sidebar.collapsed {
1198
+ width: var(--sidebar-collapsed-width);
1199
+ overflow-y: hidden;
1200
+ }
1201
+
1202
+ .layout-sidebar.mobile-hidden {
1203
+ transform: translateX(-100%);
1204
+ width: 0;
1205
+ }
1206
+
1207
+ .layout-main {
1208
+ flex: 1;
1209
+ display: flex;
1210
+ flex-direction: column;
1211
+ background: var(--bg-gradient);
1212
+ min-width: 0;
1213
+ width: 100%;
1214
+ height: 100%;
1215
+ overflow: hidden;
1216
+ box-sizing: border-box;
1217
+ }
1218
+
1219
+ .layout-footer {
1220
+ position: fixed;
1221
+ bottom: 0;
1222
+ left: 0;
1223
+ right: 0;
1224
+ height: var(--footer-height);
1225
+ background: var(--glass-bg);
1226
+ backdrop-filter: blur(15px);
1227
+ border-top: 1px solid var(--border-color);
1228
+ display: flex;
1229
+ align-items: center;
1230
+ justify-content: space-between;
1231
+ padding: 0 1rem;
1232
+ z-index: 1000;
1233
+ box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
1234
+ font-size: 0.7rem;
1235
+ color: var(--text-secondary);
1236
+ transition: transform 0.3s ease;
1237
+ }
1238
+
1239
+ .layout-footer.hidden {
1240
+ transform: translateY(100%);
1241
+ visibility: hidden;
1242
+ opacity: 0;
1243
+ }
1244
+
1245
+ .footer-left {
1246
+ display: flex;
1247
+ align-items: center;
1248
+ gap: 0.75rem;
1249
+ }
1250
+
1251
+ .footer-environment {
1252
+ display: flex;
1253
+ align-items: center;
1254
+ gap: 0.25rem;
1255
+ padding: 0.125rem 0.375rem;
1256
+ border-radius: 0.375rem;
1257
+ font-weight: 500;
1258
+ font-size: 0.625rem;
1259
+ }
1260
+
1261
+ .env-development {
1262
+ background: var(--danger);
1263
+ color: white;
1264
+ }
1265
+
1266
+ .env-staging {
1267
+ background: var(--warning);
1268
+ color: white;
1269
+ }
1270
+
1271
+ .env-production {
1272
+ background: var(--success);
1273
+ color: white;
1274
+ }
1275
+
1276
+ .env-uat {
1277
+ background: var(--info);
1278
+ color: white;
1279
+ }
1280
+
1281
+ .footer-right {
1282
+ display: flex;
1283
+ align-items: center;
1284
+ gap: 0.75rem;
1285
+ }
1286
+
1287
+ .footer-support {
1288
+ color: var(--accent-primary);
1289
+ text-decoration: none;
1290
+ transition: opacity 0.2s ease;
1291
+ }
1292
+
1293
+ .footer-support:hover {
1294
+ opacity: 0.8;
1295
+ }
1296
+
1297
+ .content-wrapper {
1298
+ display: flex;
1299
+ flex-direction: column;
1300
+ height: 100%;
1301
+ overflow: hidden;
1302
+ width: 100%;
1303
+ }
1304
+
1305
+ .tab-bar {
1306
+ display: flex;
1307
+ align-items: center;
1308
+ background: var(--glass-bg);
1309
+ border-bottom: 1px solid var(--border-color);
1310
+ padding: 0;
1311
+ margin: 0;
1312
+ min-height: var(--tab-bar-height);
1313
+ overflow-x: hidden;
1314
+ flex-shrink: 0;
1315
+ gap: 0.25rem;
1316
+ transition: all 0.3s ease;
1317
+ width: 100%;
1318
+ box-sizing: border-box;
1319
+ }
1320
+
1321
+ .tab-bar.hidden {
1322
+ display: none;
1323
+ }
1324
+
1325
+ .single-page-header {
1326
+ display: flex;
1327
+ align-items: center;
1328
+ justify-content: space-between;
1329
+ background: var(--glass-bg);
1330
+ border-bottom: 1px solid var(--border-color);
1331
+ padding: 0.5rem 1.25rem;
1332
+ flex-shrink: 0;
1333
+ }
1334
+
1335
+ .page-title {
1336
+ font-size: 1.125rem;
1337
+ font-weight: 600;
1338
+ color: var(--text-primary);
1339
+ }
1340
+
1341
+ .back-button {
1342
+ display: flex;
1343
+ align-items: center;
1344
+ gap: 0.5rem;
1345
+ padding: 0.375rem 0.75rem;
1346
+ background: transparent;
1347
+ border: 1px solid var(--border-color);
1348
+ border-radius: 0.5rem;
1349
+ color: var(--text-secondary);
1350
+ text-decoration: none;
1351
+ font-size: 0.8rem;
1352
+ transition: all 0.3s ease;
1353
+ cursor: pointer;
1354
+ }
1355
+
1356
+ .back-button:hover {
1357
+ background: var(--bg-secondary);
1358
+ color: var(--text-primary);
1359
+ border-color: var(--border-color);
1360
+ }
1361
+
1362
+ .tab {
1363
+ display: flex;
1364
+ align-items: center;
1365
+ gap: 0.5rem;
1366
+ padding: 0.375rem 0.5rem;
1367
+ margin: 0;
1368
+ background: var(--bg-secondary);
1369
+ border: 1px solid var(--border-color);
1370
+ border-bottom: none;
1371
+ border-radius: 0.75rem 0.75rem 0 0;
1372
+ cursor: pointer;
1373
+ transition: all 0.3s ease;
1374
+ white-space: nowrap;
1375
+ min-width: 100px;
1376
+ max-width: 180px;
1377
+ font-size: 0.8rem;
1378
+ color: var(--text-secondary);
1379
+ }
1380
+
1381
+ .tab:hover {
1382
+ background: var(--bg-primary);
1383
+ color: var(--text-primary);
1384
+ }
1385
+
1386
+ .tab.active {
1387
+ background: var(--bg-primary);
1388
+ border-color: var(--accent-primary);
1389
+ color: var(--accent-primary);
1390
+ box-shadow: 0 -2px 4px rgba(37, 99, 235, 0.1);
1391
+ }
1392
+
1393
+ .tab-title {
1394
+ flex: 1;
1395
+ overflow: hidden;
1396
+ text-overflow: ellipsis;
1397
+ white-space: nowrap;
1398
+ min-width: 0;
1399
+ }
1400
+
1401
+ .tab-close {
1402
+ display: flex;
1403
+ align-items: center;
1404
+ justify-content: center;
1405
+ width: 1rem;
1406
+ height: 1rem;
1407
+ border-radius: 50%;
1408
+ background: none;
1409
+ border: none;
1410
+ cursor: pointer;
1411
+ color: var(--text-muted);
1412
+ transition: all 0.3s ease;
1413
+ flex-shrink: 0;
1414
+ }
1415
+
1416
+ .tab-close:hover {
1417
+ background: var(--border-color);
1418
+ color: var(--text-secondary);
1419
+ }
1420
+
1421
+ .tab.active .tab-close:hover {
1422
+ background: rgba(37, 99, 235, 0.1);
1423
+ color: var(--accent-primary);
1424
+ }
1425
+
1426
+ /* Tab overflow menu styles for mobile */
1427
+ .tab-bar-wrapper {
1428
+ display: flex;
1429
+ align-items: center;
1430
+ width: 100%;
1431
+ background: var(--glass-bg);
1432
+ border-bottom: 1px solid var(--border-color);
1433
+ min-height: var(--tab-bar-height);
1434
+ }
1435
+
1436
+ .tab-bar-visible {
1437
+ display: flex;
1438
+ align-items: center;
1439
+ gap: 0.25rem;
1440
+ flex: 1;
1441
+ overflow: hidden;
1442
+ min-height: var(--tab-bar-height);
1443
+ }
1444
+
1445
+ .tab-more-container {
1446
+ position: relative;
1447
+ flex-shrink: 0;
1448
+ }
1449
+
1450
+ .tab-more-btn {
1451
+ display: none;
1452
+ align-items: center;
1453
+ gap: 0.25rem;
1454
+ padding: 0.375rem 0.75rem;
1455
+ background: var(--bg-secondary);
1456
+ border: 1px solid var(--border-color);
1457
+ border-radius: 0.5rem;
1458
+ cursor: pointer;
1459
+ font-size: 0.8rem;
1460
+ color: var(--text-secondary);
1461
+ transition: all 0.3s ease;
1462
+ margin-right: 0.5rem;
1463
+ white-space: nowrap;
1464
+ }
1465
+
1466
+ .tab-more-btn:hover {
1467
+ background: var(--bg-primary);
1468
+ color: var(--accent-primary);
1469
+ border-color: var(--accent-primary);
1470
+ }
1471
+
1472
+ .tab-more-btn.active {
1473
+ background: var(--accent-primary);
1474
+ color: white;
1475
+ border-color: var(--accent-primary);
1476
+ }
1477
+
1478
+ .tab-overflow-menu {
1479
+ position: absolute;
1480
+ top: 100%;
1481
+ right: 0;
1482
+ min-width: 200px;
1483
+ max-width: 280px;
1484
+ background: var(--glass-bg);
1485
+ backdrop-filter: blur(15px);
1486
+ border: 1px solid var(--border-color);
1487
+ border-radius: 0.75rem;
1488
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
1489
+ padding: 0.5rem;
1490
+ z-index: 1002;
1491
+ margin-top: 0.25rem;
1492
+ opacity: 0;
1493
+ visibility: hidden;
1494
+ transform: translateY(-10px);
1495
+ transition: all 0.3s ease;
1496
+ }
1497
+
1498
+ .tab-overflow-menu.open {
1499
+ opacity: 1;
1500
+ visibility: visible;
1501
+ transform: translateY(0);
1502
+ }
1503
+
1504
+ .tab-overflow-item {
1505
+ display: flex;
1506
+ align-items: center;
1507
+ justify-content: space-between;
1508
+ padding: 0.5rem 0.75rem;
1509
+ border-radius: 0.5rem;
1510
+ cursor: pointer;
1511
+ font-size: 0.8rem;
1512
+ color: var(--text-secondary);
1513
+ transition: all 0.3s ease;
1514
+ gap: 0.5rem;
1515
+ }
1516
+
1517
+ .tab-overflow-item:hover {
1518
+ background: var(--bg-secondary);
1519
+ color: var(--text-primary);
1520
+ }
1521
+
1522
+ .tab-overflow-item.active {
1523
+ background: rgba(37, 99, 235, 0.1);
1524
+ color: var(--accent-primary);
1525
+ }
1526
+
1527
+ .tab-overflow-item-title {
1528
+ flex: 1;
1529
+ overflow: hidden;
1530
+ text-overflow: ellipsis;
1531
+ white-space: nowrap;
1532
+ }
1533
+
1534
+ .tab-overflow-item-close {
1535
+ display: flex;
1536
+ align-items: center;
1537
+ justify-content: center;
1538
+ width: 1.25rem;
1539
+ height: 1.25rem;
1540
+ border-radius: 50%;
1541
+ background: none;
1542
+ border: none;
1543
+ cursor: pointer;
1544
+ color: var(--text-muted);
1545
+ transition: all 0.3s ease;
1546
+ flex-shrink: 0;
1547
+ }
1548
+
1549
+ .tab-overflow-item-close:hover {
1550
+ background: var(--border-color);
1551
+ color: var(--danger);
1552
+ }
1553
+
1554
+ .tab-content-area {
1555
+ flex: 1;
1556
+ overflow: auto;
1557
+ width: 100%;
1558
+ height: 100%;
1559
+ min-height: 0;
1560
+ background: var(--bg-gradient);
1561
+ box-sizing: border-box;
1562
+ }
1563
+
1564
+ .enterprise-module {
1565
+ display: none;
1566
+ width: 100%;
1567
+ height: 100%;
1568
+ overflow: auto;
1569
+ box-sizing: border-box;
1570
+ }
1571
+
1572
+ .enterprise-module.active {
1573
+ display: block;
1574
+ }
1575
+
1576
+ .module-header {
1577
+ background: var(--glass-bg);
1578
+ backdrop-filter: blur(15px);
1579
+ border: 1px solid var(--border-color);
1580
+ border-radius: 1rem;
1581
+ padding: 0.75rem 1.25rem;
1582
+ margin: 0.75rem 1.25rem 0 1.25rem;
1583
+ box-shadow: var(--shadow);
1584
+ display: flex;
1585
+ align-items: flex-start;
1586
+ justify-content: space-between;
1587
+ gap: 1rem;
1588
+ flex-shrink: 0;
1589
+ }
1590
+
1591
+ .module-header-content {
1592
+ flex: 1;
1593
+ text-align: left;
1594
+ }
1595
+
1596
+ .module-title {
1597
+ font-size: 1.25rem;
1598
+ font-weight: 700;
1599
+ background: var(--accent-gradient);
1600
+ -webkit-background-clip: text;
1601
+ -webkit-text-fill-color: transparent;
1602
+ background-clip: text;
1603
+ margin-bottom: 0.375rem;
1604
+ line-height: 1.2;
1605
+ }
1606
+
1607
+ .module-description {
1608
+ font-size: 0.8rem;
1609
+ color: var(--text-secondary);
1610
+ margin-bottom: 0;
1611
+ line-height: 1.3;
1612
+ }
1613
+
1614
+ .module-actions {
1615
+ display: flex;
1616
+ align-items: flex-start;
1617
+ gap: 0.75rem;
1618
+ flex-wrap: wrap;
1619
+ flex-shrink: 0;
1620
+ }
1621
+
1622
+ .module-content {
1623
+ padding: 0 1.25rem 2rem 1.25rem;
1624
+ max-width: 100%;
1625
+ overflow: hidden;
1626
+ }
1627
+
1628
+ .layout-body.footer-hidden .module-content {
1629
+ padding-bottom: 0;
1630
+ }
1631
+
1632
+ .btn {
1633
+ display: inline-flex;
1634
+ align-items: center;
1635
+ justify-content: center;
1636
+ gap: 0.375rem;
1637
+ padding: 0.5rem 1rem;
1638
+ font-size: 0.8rem;
1639
+ font-weight: 500;
1640
+ border: none;
1641
+ border-radius: 0.75rem;
1642
+ cursor: pointer;
1643
+ text-decoration: none;
1644
+ transition: all 0.3s ease;
1645
+ white-space: nowrap;
1646
+ }
1647
+
1648
+ .btn-primary {
1649
+ background: var(--accent-gradient);
1650
+ color: white;
1651
+ box-shadow: 0 4px 15px rgba(37, 99, 235, 0.3);
1652
+ }
1653
+
1654
+ .btn-primary:hover {
1655
+ transform: translateY(-2px);
1656
+ box-shadow: 0 8px 25px rgba(37, 99, 235, 0.4);
1657
+ }
1658
+
1659
+ .btn-secondary {
1660
+ background: var(--glass-bg);
1661
+ color: var(--text-primary);
1662
+ border: 1px solid var(--border-color);
1663
+ }
1664
+
1665
+ .btn-secondary:hover {
1666
+ background: var(--bg-secondary);
1667
+ transform: translateY(-2px);
1668
+ }
1669
+
1670
+ .nav-section {
1671
+ padding: 0.75rem 0 0 0;
1672
+ border-bottom: 1px solid var(--border-color);
1673
+ }
1674
+
1675
+ .nav-section:last-child {
1676
+ border-bottom: none;
1677
+ }
1678
+
1679
+ .nav-section-header {
1680
+ display: flex;
1681
+ align-items: center;
1682
+ justify-content: space-between;
1683
+ padding: 0.5rem 1rem;
1684
+ cursor: pointer;
1685
+ transition: all 0.3s ease;
1686
+ border-radius: 0.75rem;
1687
+ margin: 0 0.75rem 0.375rem 0.75rem;
1688
+ position: relative;
1689
+ }
1690
+
1691
+ .nav-section-header:hover {
1692
+ background: var(--bg-secondary);
1693
+ }
1694
+
1695
+ .nav-section-title {
1696
+ font-size: 0.6875rem;
1697
+ font-weight: 600;
1698
+ color: var(--text-secondary);
1699
+ text-transform: uppercase;
1700
+ letter-spacing: 0.075em;
1701
+ margin: 0;
1702
+ flex: 1;
1703
+ }
1704
+
1705
+ .nav-chevron {
1706
+ color: var(--text-muted);
1707
+ transition: all 0.2s ease;
1708
+ display: flex;
1709
+ align-items: center;
1710
+ justify-content: center;
1711
+ }
1712
+
1713
+ .nav-section-header.expanded .nav-chevron {
1714
+ transform: rotate(0deg);
1715
+ }
1716
+
1717
+ .nav-section-header.collapsed .nav-chevron {
1718
+ transform: rotate(-90deg);
1719
+ }
1720
+
1721
+ .nav-section-content {
1722
+ overflow: hidden;
1723
+ transition: all 0.3s ease;
1724
+ padding-bottom: 0.75rem;
1725
+ }
1726
+
1727
+ .nav-section-content.expanded {
1728
+ max-height: 500px;
1729
+ opacity: 1;
1730
+ }
1731
+
1732
+ .nav-section-content.collapsed {
1733
+ max-height: 0;
1734
+ opacity: 0;
1735
+ padding-bottom: 0;
1736
+ }
1737
+
1738
+ .nav-menu {
1739
+ list-style: none;
1740
+ }
1741
+
1742
+ .nav-item {
1743
+ margin: 0 0.75rem;
1744
+ }
1745
+
1746
+ .nav-link {
1747
+ display: flex;
1748
+ align-items: center;
1749
+ gap: 0.75rem;
1750
+ padding: 0.5rem 0.75rem;
1751
+ color: var(--text-secondary);
1752
+ text-decoration: none;
1753
+ border-radius: 0.75rem;
1754
+ transition: all 0.3s ease;
1755
+ font-size: 0.8rem;
1756
+ position: relative;
1757
+ }
1758
+
1759
+ .nav-link:hover,
1760
+ .nav-link.active {
1761
+ background: var(--accent-gradient);
1762
+ color: white;
1763
+ transform: translateX(4px);
1764
+ box-shadow: var(--shadow);
1765
+ }
1766
+
1767
+ /* NEW: Multi-level menu styles */
1768
+ .nav-sub-menu {
1769
+ margin-left: 1.5rem;
1770
+ border-left: 1px solid var(--border-color);
1771
+ padding-left: 0.5rem;
1772
+ margin-top: 0.25rem;
1773
+ list-style: none;
1774
+ }
1775
+
1776
+ .nav-sub-item {
1777
+ margin: 0 0.5rem;
1778
+ }
1779
+
1780
+ .nav-sub-link {
1781
+ display: flex;
1782
+ align-items: center;
1783
+ gap: 0.5rem;
1784
+ padding: 0.375rem 0.5rem;
1785
+ color: var(--text-muted);
1786
+ text-decoration: none;
1787
+ border-radius: 0.5rem;
1788
+ transition: all 0.3s ease;
1789
+ font-size: 0.75rem;
1790
+ position: relative;
1791
+ }
1792
+
1793
+ .nav-sub-link:hover,
1794
+ .nav-sub-link.active {
1795
+ background: var(--accent-gradient);
1796
+ color: white;
1797
+ transform: translateX(2px);
1798
+ box-shadow: var(--shadow);
1799
+ }
1800
+
1801
+ .nav-item-with-children > .nav-link {
1802
+ position: relative;
1803
+ }
1804
+
1805
+ .nav-item-expand {
1806
+ position: absolute;
1807
+ right: 0.5rem;
1808
+ color: var(--text-muted);
1809
+ transition: all 0.2s ease;
1810
+ display: flex;
1811
+ align-items: center;
1812
+ justify-content: center;
1813
+ }
1814
+
1815
+ .nav-item-expand.expanded {
1816
+ transform: rotate(90deg);
1817
+ }
1818
+
1819
+ .nav-badge {
1820
+ background: var(--danger);
1821
+ color: white;
1822
+ font-size: 0.625rem;
1823
+ padding: 0.125rem 0.3rem;
1824
+ border-radius: 0.5rem;
1825
+ margin-left: auto;
1826
+ font-weight: 600;
1827
+ }
1828
+
1829
+ .menu-toggle {
1830
+ background: none;
1831
+ border: none;
1832
+ padding: 0.5rem;
1833
+ border-radius: 0.75rem;
1834
+ cursor: pointer;
1835
+ color: var(--text-secondary);
1836
+ transition: all 0.3s ease;
1837
+ }
1838
+
1839
+ .menu-toggle:hover {
1840
+ background: var(--bg-secondary);
1841
+ color: var(--text-primary);
1842
+ }
1843
+
1844
+ .persona-control {
1845
+ position: relative;
1846
+ display: flex;
1847
+ align-items: center;
1848
+ gap: 0.375rem;
1849
+ padding: 0.375rem;
1850
+ border-radius: 0.75rem;
1851
+ cursor: pointer;
1852
+ transition: all 0.3s ease;
1853
+ flex-shrink: 0;
1854
+ z-index: 10002;
1855
+ }
1856
+
1857
+ .persona-control:hover {
1858
+ background: var(--bg-secondary);
1859
+ }
1860
+
1861
+ .persona-avatar {
1862
+ width: 2rem;
1863
+ height: 2rem;
1864
+ border-radius: 50%;
1865
+ background: var(--accent-gradient);
1866
+ display: flex;
1867
+ align-items: center;
1868
+ justify-content: center;
1869
+ color: white;
1870
+ font-weight: 600;
1871
+ font-size: 0.75rem;
1872
+ flex-shrink: 0;
1873
+ }
1874
+
1875
+ .persona-role {
1876
+ font-size: 0.75rem;
1877
+ color: var(--text-secondary);
1878
+ white-space: nowrap;
1879
+ overflow: hidden;
1880
+ text-overflow: ellipsis;
1881
+ max-width: 100px;
1882
+ }
1883
+
1884
+ .persona-panel {
1885
+ position: absolute;
1886
+ top: 100%;
1887
+ right: 0;
1888
+ width: 300px;
1889
+ background: var(--glass-bg);
1890
+ backdrop-filter: blur(15px);
1891
+ border: 2px solid rgba(203, 213, 225, 0.8);
1892
+ border-radius: 1rem;
1893
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
1894
+ padding: 1rem;
1895
+ z-index: 10001;
1896
+ margin-top: 0.5rem;
1897
+ opacity: 0;
1898
+ visibility: hidden;
1899
+ transform: translateY(-10px);
1900
+ transition: all 0.3s ease;
1901
+ }
1902
+
1903
+ .persona-panel.open {
1904
+ opacity: 1;
1905
+ visibility: visible;
1906
+ transform: translateY(0);
1907
+ }
1908
+
1909
+ .persona-panel-header {
1910
+ display: flex;
1911
+ align-items: center;
1912
+ gap: 0.75rem;
1913
+ padding-bottom: 0.75rem;
1914
+ margin-bottom: 0.75rem;
1915
+ border-bottom: 2px solid rgba(226, 232, 240, 0.9);
1916
+ }
1917
+
1918
+ .persona-panel-avatar {
1919
+ width: 2.5rem;
1920
+ height: 2.5rem;
1921
+ border-radius: 50%;
1922
+ background: var(--accent-gradient);
1923
+ display: flex;
1924
+ align-items: center;
1925
+ justify-content: center;
1926
+ color: white;
1927
+ font-weight: 600;
1928
+ font-size: 0.875rem;
1929
+ }
1930
+
1931
+ .persona-panel-info h3 {
1932
+ font-size: 0.875rem;
1933
+ font-weight: 600;
1934
+ color: var(--text-primary);
1935
+ margin-bottom: 0.125rem;
1936
+ }
1937
+
1938
+ .persona-panel-info p {
1939
+ font-size: 0.75rem;
1940
+ color: var(--text-secondary);
1941
+ }
1942
+
1943
+ .persona-panel-section {
1944
+ padding-bottom: 0.75rem;
1945
+ margin-bottom: 0.75rem;
1946
+ border-bottom: 1px solid rgba(226, 232, 240, 0.8);
1947
+ }
1948
+
1949
+ .persona-panel-section:last-child {
1950
+ margin-bottom: 0;
1951
+ padding-bottom: 0;
1952
+ border-bottom: none;
1953
+ }
1954
+
1955
+ .persona-panel-section h4 {
1956
+ font-size: 0.75rem;
1957
+ font-weight: 600;
1958
+ color: var(--text-secondary);
1959
+ text-transform: uppercase;
1960
+ letter-spacing: 0.05em;
1961
+ margin-bottom: 0.5rem;
1962
+ }
1963
+
1964
+ .persona-panel-controls {
1965
+ display: flex;
1966
+ flex-direction: column;
1967
+ gap: 0.375rem;
1968
+ }
1969
+
1970
+ .persona-control-item {
1971
+ display: flex;
1972
+ align-items: center;
1973
+ justify-content: space-between;
1974
+ padding: 0.5rem 0.75rem;
1975
+ border-radius: 0.5rem;
1976
+ border: 1px solid transparent;
1977
+ transition: all 0.3s ease;
1978
+ cursor: pointer;
1979
+ font-size: 0.8rem;
1980
+ }
1981
+
1982
+ .persona-control-item:hover {
1983
+ background: var(--bg-secondary);
1984
+ border-color: rgba(203, 213, 225, 0.7);
1985
+ }
1986
+
1987
+ .persona-control-item.action {
1988
+ color: var(--text-primary);
1989
+ }
1990
+
1991
+ .persona-control-item.action:hover {
1992
+ color: var(--accent-primary);
1993
+ }
1994
+
1995
+ .persona-control-item.danger:hover {
1996
+ background: rgba(220, 38, 38, 0.1);
1997
+ color: var(--danger);
1998
+ }
1999
+
2000
+ .persona-control-label {
2001
+ display: flex;
2002
+ align-items: center;
2003
+ gap: 0.5rem;
2004
+ }
2005
+
2006
+ /* Notification Panel */
2007
+ .notification-bell {
2008
+ position: relative;
2009
+ display: flex;
2010
+ align-items: center;
2011
+ justify-content: center;
2012
+ width: 2.5rem;
2013
+ height: 2.5rem;
2014
+ border-radius: 50%;
2015
+ cursor: pointer;
2016
+ transition: all 0.3s ease;
2017
+ background: transparent;
2018
+ }
2019
+
2020
+ .notification-bell:hover {
2021
+ background: var(--bg-secondary);
2022
+ }
2023
+
2024
+ .notification-badge {
2025
+ position: absolute;
2026
+ top: 0.25rem;
2027
+ right: 0.25rem;
2028
+ background: var(--danger);
2029
+ color: white;
2030
+ font-size: 0.625rem;
2031
+ font-weight: 600;
2032
+ min-width: 1.125rem;
2033
+ height: 1.125rem;
2034
+ border-radius: 0.5625rem;
2035
+ display: flex;
2036
+ align-items: center;
2037
+ justify-content: center;
2038
+ padding: 0 0.25rem;
2039
+ border: 2px solid var(--glass-bg);
2040
+ }
2041
+
2042
+ .notification-panel {
2043
+ position: absolute;
2044
+ top: 100%;
2045
+ right: 0;
2046
+ width: 380px;
2047
+ max-height: 500px;
2048
+ background: var(--glass-bg);
2049
+ backdrop-filter: blur(15px);
2050
+ border: 2px solid rgba(203, 213, 225, 0.8);
2051
+ border-radius: 1rem;
2052
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
2053
+ z-index: 1001;
2054
+ margin-top: 0.5rem;
2055
+ opacity: 0;
2056
+ visibility: hidden;
2057
+ transform: translateY(-10px);
2058
+ transition: all 0.3s ease;
2059
+ display: flex;
2060
+ flex-direction: column;
2061
+ }
2062
+
2063
+ .notification-panel.open {
2064
+ opacity: 1;
2065
+ visibility: visible;
2066
+ transform: translateY(0);
2067
+ }
2068
+
2069
+ .notification-panel-header {
2070
+ display: flex;
2071
+ align-items: center;
2072
+ justify-content: space-between;
2073
+ padding: 1rem 1.25rem;
2074
+ border-bottom: 2px solid rgba(226, 232, 240, 0.9);
2075
+ }
2076
+
2077
+ .notification-panel-title {
2078
+ font-size: 1rem;
2079
+ font-weight: 600;
2080
+ color: var(--text-primary);
2081
+ }
2082
+
2083
+ .notification-panel-actions {
2084
+ display: flex;
2085
+ gap: 0.5rem;
2086
+ }
2087
+
2088
+ .notification-action-btn {
2089
+ font-size: 0.75rem;
2090
+ color: var(--primary);
2091
+ background: transparent;
2092
+ border: none;
2093
+ cursor: pointer;
2094
+ padding: 0.25rem 0.5rem;
2095
+ border-radius: 0.375rem;
2096
+ transition: all 0.2s ease;
2097
+ }
2098
+
2099
+ .notification-action-btn:hover {
2100
+ background: rgba(37, 99, 235, 0.1);
2101
+ }
2102
+
2103
+ .notification-panel-content {
2104
+ flex: 1;
2105
+ overflow-y: auto;
2106
+ padding: 0.5rem;
2107
+ }
2108
+
2109
+ .notification-item {
2110
+ display: flex;
2111
+ gap: 0.75rem;
2112
+ padding: 0.875rem;
2113
+ margin-bottom: 0.5rem;
2114
+ border-radius: 0.75rem;
2115
+ cursor: pointer;
2116
+ transition: all 0.2s ease;
2117
+ border: 1px solid transparent;
2118
+ }
2119
+
2120
+ .notification-item:hover {
2121
+ background: var(--bg-secondary);
2122
+ border-color: rgba(203, 213, 225, 0.7);
2123
+ }
2124
+
2125
+ .notification-item.unread {
2126
+ background: rgba(37, 99, 235, 0.05);
2127
+ }
2128
+
2129
+ .notification-icon {
2130
+ flex-shrink: 0;
2131
+ width: 2.5rem;
2132
+ height: 2.5rem;
2133
+ border-radius: 50%;
2134
+ display: flex;
2135
+ align-items: center;
2136
+ justify-content: center;
2137
+ font-size: 1.125rem;
2138
+ }
2139
+
2140
+ .notification-icon.success {
2141
+ background: rgba(22, 163, 74, 0.1);
2142
+ color: var(--success);
2143
+ }
2144
+
2145
+ .notification-icon.info {
2146
+ background: rgba(37, 99, 235, 0.1);
2147
+ color: var(--primary);
2148
+ }
2149
+
2150
+ .notification-icon.warning {
2151
+ background: rgba(217, 119, 6, 0.1);
2152
+ color: var(--warning);
2153
+ }
2154
+
2155
+ .notification-icon.error {
2156
+ background: rgba(220, 38, 38, 0.1);
2157
+ color: var(--danger);
2158
+ }
2159
+
2160
+ .notification-content {
2161
+ flex: 1;
2162
+ min-width: 0;
2163
+ }
2164
+
2165
+ .notification-title {
2166
+ font-size: 0.875rem;
2167
+ font-weight: 600;
2168
+ color: var(--text-primary);
2169
+ margin-bottom: 0.25rem;
2170
+ }
2171
+
2172
+ .notification-message {
2173
+ font-size: 0.8rem;
2174
+ color: var(--text-secondary);
2175
+ margin-bottom: 0.375rem;
2176
+ line-height: 1.4;
2177
+ }
2178
+
2179
+ .notification-time {
2180
+ font-size: 0.7rem;
2181
+ color: var(--text-muted);
2182
+ }
2183
+
2184
+ .notification-empty {
2185
+ text-align: center;
2186
+ padding: 3rem 2rem;
2187
+ color: var(--text-muted);
2188
+ }
2189
+
2190
+ .notification-empty-icon {
2191
+ font-size: 3rem;
2192
+ margin-bottom: 1rem;
2193
+ opacity: 0.3;
2194
+ }
2195
+
2196
+ .notification-filters {
2197
+ display: flex;
2198
+ align-items: center;
2199
+ justify-content: space-between;
2200
+ padding: 0.75rem 1rem;
2201
+ border-bottom: 1px solid rgba(226, 232, 240, 0.8);
2202
+ background: var(--bg-secondary);
2203
+ }
2204
+
2205
+ .notification-filter-tabs {
2206
+ display: flex;
2207
+ gap: 0.25rem;
2208
+ background: white;
2209
+ border-radius: 0.5rem;
2210
+ padding: 0.25rem;
2211
+ }
2212
+
2213
+ .notification-filter-tab {
2214
+ padding: 0.375rem 0.75rem;
2215
+ font-size: 0.75rem;
2216
+ font-weight: 500;
2217
+ color: var(--text-secondary);
2218
+ background: transparent;
2219
+ border: none;
2220
+ border-radius: 0.375rem;
2221
+ cursor: pointer;
2222
+ transition: all 0.2s ease;
2223
+ }
2224
+
2225
+ .notification-filter-tab:hover {
2226
+ background: var(--bg-secondary);
2227
+ }
2228
+
2229
+ .notification-filter-tab.active {
2230
+ background: var(--primary);
2231
+ color: white;
2232
+ }
2233
+
2234
+ .notification-grouping-dropdown {
2235
+ position: relative;
2236
+ }
2237
+
2238
+ .notification-grouping-btn {
2239
+ display: flex;
2240
+ align-items: center;
2241
+ gap: 0.375rem;
2242
+ padding: 0.375rem 0.625rem;
2243
+ font-size: 0.75rem;
2244
+ font-weight: 500;
2245
+ color: var(--text-secondary);
2246
+ background: white;
2247
+ border: 1px solid var(--border-color);
2248
+ border-radius: 0.375rem;
2249
+ cursor: pointer;
2250
+ transition: all 0.2s ease;
2251
+ }
2252
+
2253
+ .notification-grouping-btn:hover {
2254
+ border-color: var(--primary);
2255
+ color: var(--primary);
2256
+ }
2257
+
2258
+ .notification-group-header {
2259
+ padding: 0.5rem 0.875rem;
2260
+ font-size: 0.75rem;
2261
+ font-weight: 600;
2262
+ color: var(--text-secondary);
2263
+ text-transform: uppercase;
2264
+ letter-spacing: 0.05em;
2265
+ background: var(--bg-secondary);
2266
+ border-bottom: 1px solid rgba(226, 232, 240, 0.8);
2267
+ margin-bottom: 0.25rem;
2268
+ }
2269
+
2270
+ .view-mode-toggle {
2271
+ display: flex;
2272
+ align-items: center;
2273
+ background: var(--bg-secondary);
2274
+ border: 1px solid var(--border-color);
2275
+ border-radius: 0.5rem;
2276
+ padding: 0.125rem;
2277
+ gap: 0.125rem;
2278
+ }
2279
+
2280
+ .view-mode-btn {
2281
+ display: flex;
2282
+ align-items: center;
2283
+ justify-content: center;
2284
+ padding: 0.25rem 0.5rem;
2285
+ border-radius: 0.375rem;
2286
+ cursor: pointer;
2287
+ transition: all 0.3s ease;
2288
+ color: var(--text-secondary);
2289
+ background: transparent;
2290
+ border: none;
2291
+ font-size: 0.75rem;
2292
+ }
2293
+
2294
+ .view-mode-btn.active {
2295
+ background: var(--accent-gradient);
2296
+ color: white;
2297
+ }
2298
+
2299
+ .theme-toggle {
2300
+ display: flex;
2301
+ align-items: center;
2302
+ gap: 0.5rem;
2303
+ }
2304
+
2305
+ .theme-switch {
2306
+ position: relative;
2307
+ width: 2.5rem;
2308
+ height: 1.25rem;
2309
+ background: var(--bg-secondary);
2310
+ border: 1px solid var(--border-color);
2311
+ border-radius: 0.625rem;
2312
+ cursor: pointer;
2313
+ transition: all 0.3s ease;
2314
+ }
2315
+
2316
+ .theme-switch.active {
2317
+ background: var(--accent-primary);
2318
+ border-color: var(--accent-primary);
2319
+ }
2320
+
2321
+ .theme-switch-handle {
2322
+ position: absolute;
2323
+ top: 0.125rem;
2324
+ left: 0.125rem;
2325
+ width: 0.875rem;
2326
+ height: 0.875rem;
2327
+ background: white;
2328
+ border-radius: 50%;
2329
+ transition: all 0.3s ease;
2330
+ }
2331
+
2332
+ .theme-switch.active .theme-switch-handle {
2333
+ transform: translateX(1.125rem);
2334
+ }
2335
+
2336
+ /* Compact Mode Styles */
2337
+ .sidebar-collapsed .nav-section-title,
2338
+ .sidebar-collapsed .nav-badge,
2339
+ .sidebar-collapsed .persona-role {
2340
+ display: none;
2341
+ }
2342
+
2343
+ .sidebar-collapsed .nav-link {
2344
+ justify-content: center;
2345
+ padding: 0.75rem;
2346
+ margin: 0.25rem 0.5rem;
2347
+ }
2348
+
2349
+ .sidebar-collapsed .nav-section-header {
2350
+ justify-content: center;
2351
+ padding: 0.75rem;
2352
+ margin: 0.25rem 0.5rem;
2353
+ }
2354
+
2355
+ .sidebar-collapsed .nav-chevron {
2356
+ display: none;
2357
+ }
2358
+
2359
+ .sidebar-collapsed .nav-section-content {
2360
+ display: none;
2361
+ }
2362
+
2363
+ /* Compact navigation specific styles */
2364
+ .compact-navigation {
2365
+ padding: 0.5rem 0;
2366
+ width: 100%;
2367
+ overflow: visible;
2368
+ }
2369
+
2370
+ .compact-nav-item {
2371
+ margin: 0.25rem 0.5rem;
2372
+ position: relative;
2373
+ }
2374
+
2375
+ .compact-nav-link {
2376
+ display: flex;
2377
+ align-items: center;
2378
+ justify-content: center;
2379
+ padding: 0.75rem;
2380
+ color: var(--text-secondary);
2381
+ text-decoration: none;
2382
+ border-radius: 0.75rem;
2383
+ transition: all 0.2s ease;
2384
+ font-size: 0.8rem;
2385
+ position: relative;
2386
+ min-height: 3rem;
2387
+ width: 100%;
2388
+ box-sizing: border-box;
2389
+ }
2390
+
2391
+ .compact-nav-link:hover,
2392
+ .compact-nav-link.active {
2393
+ background: var(--accent-gradient);
2394
+ color: white;
2395
+ box-shadow: var(--shadow);
2396
+ }
2397
+
2398
+ .compact-nav-badge {
2399
+ position: absolute;
2400
+ top: 0.25rem;
2401
+ right: 0.25rem;
2402
+ background: var(--danger);
2403
+ color: white;
2404
+ font-size: 0.5rem;
2405
+ padding: 0.125rem 0.25rem;
2406
+ border-radius: 0.375rem;
2407
+ font-weight: 600;
2408
+ min-width: 1rem;
2409
+ text-align: center;
2410
+ line-height: 1;
2411
+ }
2412
+
2413
+ .section-tooltip {
2414
+ position: relative;
2415
+ }
2416
+
2417
+ .section-tooltip::after {
2418
+ content: attr(data-tooltip);
2419
+ position: absolute;
2420
+ left: calc(100% + 0.75rem);
2421
+ top: 50%;
2422
+ transform: translateY(-50%);
2423
+ background: var(--glass-bg);
2424
+ color: var(--text-primary);
2425
+ padding: 0.5rem 0.75rem;
2426
+ border-radius: 0.5rem;
2427
+ font-size: 0.75rem;
2428
+ white-space: nowrap;
2429
+ z-index: 1000;
2430
+ box-shadow: var(--shadow);
2431
+ backdrop-filter: blur(15px);
2432
+ border: 1px solid var(--border-color);
2433
+ opacity: 0;
2434
+ visibility: hidden;
2435
+ transition: all 0.2s ease;
2436
+ pointer-events: none;
2437
+ }
2438
+
2439
+ /* FIX: Only show tooltips after initialization */
2440
+ .enterprise-layout.initialized .section-tooltip:hover::after {
2441
+ opacity: 1;
2442
+ visibility: visible;
2443
+ }
2444
+
2445
+ /* Tablet responsive styles */
2446
+ @media (max-width: 1024px) and (min-width: 769px) {
2447
+ /* Reduce header info card padding on tablets */
2448
+ .header-info-card {
2449
+ padding: 0.3rem 0.6rem;
2450
+ gap: 0.4rem;
2451
+ }
2452
+
2453
+ .header-info-label {
2454
+ font-size: 0.5rem;
2455
+ }
2456
+
2457
+ .header-info-value {
2458
+ font-size: 0.75rem;
2459
+ }
2460
+
2461
+ .header-info-icon {
2462
+ width: 1rem;
2463
+ height: 1rem;
2464
+ }
2465
+
2466
+ .notification-bell {
2467
+ width: 2.25rem;
2468
+ height: 2.25rem;
2469
+ }
2470
+
2471
+ .logo-company {
2472
+ font-size: 1.1rem;
2473
+ }
2474
+
2475
+ .logo-tagline {
2476
+ font-size: 0.55rem;
2477
+ }
2478
+ }
2479
+
2480
+ /* Mobile responsive styles */
2481
+ @media (max-width: 768px) {
2482
+ .mobile-overlay {
2483
+ position: fixed;
2484
+ top: var(--header-height);
2485
+ left: 0;
2486
+ right: 0;
2487
+ bottom: 0;
2488
+ background: rgba(0, 0, 0, 0.5);
2489
+ z-index: 1000;
2490
+ opacity: 0;
2491
+ visibility: hidden;
2492
+ transition: opacity 0.3s ease, visibility 0.3s ease;
2493
+ }
2494
+
2495
+ .mobile-overlay.visible {
2496
+ opacity: 1;
2497
+ visibility: visible;
2498
+ }
2499
+
2500
+ .layout-sidebar {
2501
+ position: absolute;
2502
+ top: var(--header-height);
2503
+ left: 0;
2504
+ height: calc(100% - var(--header-height));
2505
+ width: 85vw;
2506
+ max-width: var(--sidebar-width);
2507
+ transform: translateX(-100%);
2508
+ z-index: 1001;
2509
+ }
2510
+
2511
+ .layout-sidebar.mobile-open {
2512
+ transform: translateX(0);
2513
+ }
2514
+
2515
+ .tab-bar {
2516
+ padding: 0;
2517
+ margin: 0;
2518
+ width: 100%;
2519
+ }
2520
+
2521
+ /* Mobile tab overflow - show only 2 tabs + more button */
2522
+ .tab-bar-visible .tab {
2523
+ display: none;
2524
+ }
2525
+
2526
+ .tab-bar-visible .tab:nth-child(-n+2) {
2527
+ display: flex;
2528
+ }
2529
+
2530
+ .tab-more-btn {
2531
+ display: flex;
2532
+ }
2533
+
2534
+ .tab {
2535
+ min-width: 80px;
2536
+ max-width: 120px;
2537
+ padding: 0.25rem 0.5rem;
2538
+ margin: 0;
2539
+ font-size: 0.7rem;
2540
+ }
2541
+
2542
+ .single-page-header {
2543
+ padding: 0.375rem 0.75rem;
2544
+ }
2545
+
2546
+ .page-title {
2547
+ font-size: 1rem;
2548
+ }
2549
+
2550
+ .module-header {
2551
+ margin: 1rem;
2552
+ padding: 1rem;
2553
+ flex-direction: column;
2554
+ align-items: flex-start;
2555
+ gap: 0.75rem;
2556
+ }
2557
+
2558
+ .module-content {
2559
+ padding: 1rem;
2560
+ }
2561
+
2562
+ .footer-left,
2563
+ .footer-right {
2564
+ gap: 0.5rem;
2565
+ }
2566
+
2567
+ .footer-environment {
2568
+ font-size: 0.5rem;
2569
+ }
2570
+
2571
+ .breadcrumb-container {
2572
+ display: none;
2573
+ }
2574
+
2575
+ .header-center {
2576
+ display: none;
2577
+ }
2578
+
2579
+ /* Hide Entity & FY info cards on mobile to make room for persona */
2580
+ .header-info-section {
2581
+ display: none;
2582
+ }
2583
+
2584
+ /* Ensure header-right elements are properly spaced and visible */
2585
+ .header-right {
2586
+ gap: 0.5rem;
2587
+ flex-wrap: nowrap;
2588
+ flex-shrink: 0;
2589
+ }
2590
+
2591
+ /* Adjust notification bell size for mobile */
2592
+ .notification-bell {
2593
+ width: 2.25rem;
2594
+ height: 2.25rem;
2595
+ flex-shrink: 0;
2596
+ }
2597
+
2598
+ /* Ensure persona-control is always visible on mobile */
2599
+ .persona-control {
2600
+ display: flex !important;
2601
+ flex-shrink: 0;
2602
+ z-index: 1001;
2603
+ }
2604
+
2605
+ /* Make notification panel full width on mobile */
2606
+ .notification-panel {
2607
+ position: fixed;
2608
+ left: 50%;
2609
+ right: auto;
2610
+ transform: translateX(-50%);
2611
+ width: calc(100vw - 2rem);
2612
+ max-width: 380px;
2613
+ }
2614
+
2615
+ .notification-panel.open {
2616
+ transform: translateX(-50%) translateY(0);
2617
+ }
2618
+
2619
+ /* Adjust persona panel for mobile - fixed position for better visibility */
2620
+ .persona-panel {
2621
+ position: fixed;
2622
+ top: var(--header-height);
2623
+ right: 0.5rem;
2624
+ left: auto;
2625
+ width: calc(100vw - 1rem);
2626
+ max-width: 300px;
2627
+ margin-top: 0.25rem;
2628
+ }
2629
+
2630
+ /* Make logo smaller on mobile */
2631
+ .logo-company {
2632
+ font-size: 1rem;
2633
+ }
2634
+
2635
+ .logo-tagline {
2636
+ font-size: 0.5rem;
2637
+ }
2638
+
2639
+ .persona-role {
2640
+ display: none;
2641
+ }
2642
+ }
2643
+
2644
+ /* Extra small mobile (phones) */
2645
+ @media (max-width: 480px) {
2646
+ .layout-header {
2647
+ padding: 0 0.5rem;
2648
+ height: 3rem;
2649
+ }
2650
+
2651
+ .header-left {
2652
+ gap: 0.5rem;
2653
+ }
2654
+
2655
+ .menu-toggle {
2656
+ width: 2rem;
2657
+ height: 2rem;
2658
+ padding: 0.25rem;
2659
+ }
2660
+
2661
+ .logo-company {
2662
+ font-size: 0.875rem;
2663
+ }
2664
+
2665
+ .logo-tagline {
2666
+ display: none;
2667
+ }
2668
+
2669
+ .notification-bell {
2670
+ width: 2rem;
2671
+ height: 2rem;
2672
+ }
2673
+
2674
+ .notification-panel {
2675
+ width: calc(100vw - 1rem);
2676
+ max-width: 100%;
2677
+ right: -0.25rem;
2678
+ }
2679
+
2680
+ .notification-filter-tab {
2681
+ padding: 0.25rem 0.5rem;
2682
+ font-size: 0.65rem;
2683
+ }
2684
+
2685
+ .notification-grouping-btn {
2686
+ font-size: 0.65rem;
2687
+ padding: 0.25rem 0.5rem;
2688
+ }
2689
+
2690
+ .tab {
2691
+ min-width: 60px;
2692
+ max-width: 100px;
2693
+ padding: 0.25rem 0.375rem;
2694
+ font-size: 0.65rem;
2695
+ }
2696
+
2697
+ .module-header {
2698
+ margin: 0.75rem;
2699
+ padding: 0.75rem;
2700
+ }
2701
+
2702
+ .module-content {
2703
+ padding: 0.75rem;
2704
+ }
2705
+
2706
+ .header-right {
2707
+ gap: 0.375rem;
2708
+ }
2709
+
2710
+ /* Smaller persona avatar on extra small screens */
2711
+ .persona-avatar {
2712
+ width: 1.75rem;
2713
+ height: 1.75rem;
2714
+ font-size: 0.65rem;
2715
+ }
2716
+ }
2717
+ `;
2718
+ }, [config.branding.colors, config.layout, footerVisible]);
2719
+
2720
+ // Apply theme and styles
2721
+ useEffect(() => {
2722
+ const styleId = "enterprise-layout-styles";
2723
+ const existingStyle = document.getElementById(styleId);
2724
+ if (existingStyle) {
2725
+ existingStyle.remove();
2726
+ }
2727
+
2728
+ const style = document.createElement("style");
2729
+ style.id = styleId;
2730
+ style.textContent = dynamicStyles;
2731
+ document.head.appendChild(style);
2732
+
2733
+ return () => {
2734
+ const style = document.getElementById(styleId);
2735
+ if (style) {
2736
+ style.remove();
2737
+ }
2738
+ };
2739
+ }, [dynamicStyles]);
2740
+
2741
+ // Helper function to find which section contains the active tab (moved before useEffect)
2742
+ const findActiveSectionName = useCallback(
2743
+ (activeTabId: string): string | null => {
2744
+ for (const section of dynamicNavigation) {
2745
+ if (section.items.some((item) => item.id === activeTabId)) {
2746
+ return section.section;
2747
+ }
2748
+ }
2749
+ return null;
2750
+ },
2751
+ [dynamicNavigation]
2752
+ );
2753
+
2754
+ useEffect(() => {
2755
+ document.documentElement.setAttribute(
2756
+ "data-theme",
2757
+ darkMode ? "dark" : "light"
2758
+ );
2759
+ if (onThemeChange) {
2760
+ onThemeChange(darkMode ? "dark" : "light");
2761
+ }
2762
+ }, [darkMode, onThemeChange]);
2763
+
2764
+ // Responsive handling
2765
+ useEffect(() => {
2766
+ const checkMobile = () => {
2767
+ setIsMobile(window.innerWidth <= 768);
2768
+ };
2769
+
2770
+ checkMobile();
2771
+ window.addEventListener("resize", checkMobile);
2772
+ return () => window.removeEventListener("resize", checkMobile);
2773
+ }, []);
2774
+
2775
+ // Update breadcrumbs when active tab changes
2776
+ useEffect(() => {
2777
+ if (activeTab === "overview") {
2778
+ setBreadcrumbs(["Dashboard", "Overview"]);
2779
+ } else {
2780
+ const moduleConfig = dynamicModules[activeTab];
2781
+ if (moduleConfig && moduleConfig.breadcrumb) {
2782
+ setBreadcrumbs(moduleConfig.breadcrumb);
2783
+ } else {
2784
+ setBreadcrumbs([
2785
+ "Dashboard",
2786
+ activeTab
2787
+ .replace("-", " ")
2788
+ .replace(/\b\w/g, (l: string) => l.toUpperCase()),
2789
+ ]);
2790
+ }
2791
+ }
2792
+ }, [activeTab, dynamicModules]);
2793
+
2794
+ useEffect(() => {
2795
+ if (autoCollapseEnabled && !isMobile && activeTab !== "overview") {
2796
+ setSidebarCollapsed(true);
2797
+
2798
+ const activeSectionName = findActiveSectionName(activeTab);
2799
+ if (activeSectionName) {
2800
+ setExpandedSections(new Set([activeSectionName]));
2801
+ }
2802
+ }
2803
+ }, [activeTab, autoCollapseEnabled, isMobile, findActiveSectionName]);
2804
+
2805
+ // NEW: Helper function to recursively find if an item has active children
2806
+ const hasActiveChild = useCallback(
2807
+ (item: NavigationItem, activeTabId: string): boolean => {
2808
+ if (item.id === activeTabId) return true;
2809
+ if (item.children) {
2810
+ return item.children.some((child) =>
2811
+ hasActiveChild(child, activeTabId)
2812
+ );
2813
+ }
2814
+ return false;
2815
+ },
2816
+ []
2817
+ );
2818
+
2819
+ // Helper function to get items to display in compact mode
2820
+ const getCompactModeItems = useCallback(
2821
+ (
2822
+ strategy: "sections-only" | "all-items" | "smart-grouping"
2823
+ ): CompactModeItem[] => {
2824
+ switch (strategy) {
2825
+ case "sections-only":
2826
+ return dynamicNavigation.map(
2827
+ (section) =>
2828
+ ({
2829
+ id: `section-${section.section}`,
2830
+ title: section.section,
2831
+ icon: section.icon || "folder",
2832
+ isSection: true,
2833
+ section: section.section,
2834
+ items: section.items,
2835
+ sectionName: section.section,
2836
+ badge:
2837
+ section.items
2838
+ .reduce((sum, item) => {
2839
+ const badgeNum = item.badge
2840
+ ? parseInt(item.badge) || 0
2841
+ : 0;
2842
+ return sum + badgeNum;
2843
+ }, 0)
2844
+ .toString() || undefined,
2845
+ } as CompactModeItem)
2846
+ );
2847
+
2848
+ case "all-items":
2849
+ return dynamicNavigation.flatMap((section) =>
2850
+ section.items.map((item) => {
2851
+ const compactItem: CompactModeItem = {
2852
+ ...item,
2853
+ sectionName: section.section || "default-section",
2854
+ children: item.children
2855
+ ? item.children.map(
2856
+ (child) =>
2857
+ ({
2858
+ ...child,
2859
+ sectionName: section.section || "default-section",
2860
+ } as CompactModeItem)
2861
+ )
2862
+ : undefined,
2863
+ };
2864
+ return compactItem;
2865
+ })
2866
+ );
2867
+
2868
+ case "smart-grouping":
2869
+ default:
2870
+ const maxItemsBeforeGrouping = 3;
2871
+ return dynamicNavigation.flatMap((section) => {
2872
+ if (section.items.length > maxItemsBeforeGrouping) {
2873
+ return [
2874
+ {
2875
+ id: `section-${section.section}`,
2876
+ title: section.section,
2877
+ icon: section.icon || "folder",
2878
+ isSection: true,
2879
+ section: section.section,
2880
+ items: section.items,
2881
+ sectionName: section.section || "default-section",
2882
+ badge:
2883
+ section.items
2884
+ .reduce((sum, item) => {
2885
+ const badgeNum = item.badge
2886
+ ? parseInt(item.badge) || 0
2887
+ : 0;
2888
+ return sum + badgeNum;
2889
+ }, 0)
2890
+ .toString() || undefined,
2891
+ },
2892
+ ];
2893
+ } else {
2894
+ return section.items.map(
2895
+ (item) =>
2896
+ ({
2897
+ ...item,
2898
+ sectionName: section.section || "default-section",
2899
+ children: item.children
2900
+ ? item.children.map(
2901
+ (child) =>
2902
+ ({
2903
+ ...child,
2904
+ sectionName: section.section || "default-section",
2905
+ } as CompactModeItem)
2906
+ )
2907
+ : undefined,
2908
+ } as CompactModeItem)
2909
+ );
2910
+ }
2911
+ });
2912
+ }
2913
+ },
2914
+ [dynamicNavigation, config.layout.compactModeStrategy]
2915
+ );
2916
+
2917
+ // Permission checking helper
2918
+ const hasPermission = useCallback(
2919
+ (permission: string): boolean => {
2920
+ return (
2921
+ dynamicUser.permissions.includes(permission) ||
2922
+ dynamicUser.permissions.includes("admin") ||
2923
+ dynamicUser.permissions.includes("write")
2924
+ );
2925
+ },
2926
+ [dynamicUser.permissions]
2927
+ );
2928
+
2929
+ // NEW: Helper function to toggle sub-item expansion
2930
+ const toggleSubItem = useCallback((itemId: string) => {
2931
+ setExpandedSubItems((prev) => {
2932
+ const newSet = new Set(prev);
2933
+ if (newSet.has(itemId)) {
2934
+ newSet.delete(itemId);
2935
+ } else {
2936
+ newSet.add(itemId);
2937
+ }
2938
+ return newSet;
2939
+ });
2940
+ }, []);
2941
+
2942
+ // Calculate how many compact items can fit in screen height
2943
+ const [visibleCompactItems, setVisibleCompactItems] = useState<number>(10);
2944
+
2945
+ useEffect(() => {
2946
+ const calculateVisibleItems = () => {
2947
+ const headerHeight = 48;
2948
+ const footerHeight = footerVisible ? 32 : 0;
2949
+ const availableHeight = window.innerHeight - headerHeight - footerHeight;
2950
+ const sidebarPadding = 16;
2951
+ const itemHeight = 56;
2952
+
2953
+ const maxItems = Math.floor(
2954
+ (availableHeight - sidebarPadding) / itemHeight
2955
+ );
2956
+ setVisibleCompactItems(Math.max(3, maxItems));
2957
+ };
2958
+
2959
+ calculateVisibleItems();
2960
+ window.addEventListener("resize", calculateVisibleItems);
2961
+ return () => window.removeEventListener("resize", calculateVisibleItems);
2962
+ }, [footerVisible]);
2963
+
2964
+ // Module navigation
2965
+ const handleModuleSelect = useCallback(
2966
+ (moduleId: string) => {
2967
+ if (autoCollapseEnabled && !isMobile && moduleId !== "overview") {
2968
+ setSidebarCollapsed(true);
2969
+ const activeSectionName = findActiveSectionName(moduleId);
2970
+ if (activeSectionName) {
2971
+ setExpandedSections(new Set([activeSectionName]));
2972
+ }
2973
+ }
2974
+
2975
+ startTransition(() => {
2976
+ if (tabModeEnabled) {
2977
+ if (!openTabs.has(moduleId)) {
2978
+ const moduleConfig = dynamicModules[moduleId] || {
2979
+ title: moduleId
2980
+ .replace("-", " ")
2981
+ .replace(/\b\w/g, (l: string) => l.toUpperCase()),
2982
+ description: `${moduleId.replace("-", " ")} module`,
2983
+ component: null,
2984
+ actions: ["View Details", "Configure"],
2985
+ permissions: [],
2986
+ breadcrumb: [],
2987
+ };
2988
+ const title =
2989
+ moduleId === "overview" ? "Overview" : moduleConfig.title;
2990
+
2991
+ setOpenTabs((prev) =>
2992
+ new Map(prev).set(moduleId, { title, config: moduleConfig })
2993
+ );
2994
+ }
2995
+ } else {
2996
+ if (!openTabs.has(moduleId)) {
2997
+ const moduleConfig = dynamicModules[moduleId] || {
2998
+ title: moduleId
2999
+ .replace("-", " ")
3000
+ .replace(/\b\w/g, (l: string) => l.toUpperCase()),
3001
+ description: `${moduleId.replace("-", " ")} module`,
3002
+ component: null,
3003
+ actions: ["View Details", "Configure"],
3004
+ permissions: [],
3005
+ breadcrumb: [],
3006
+ };
3007
+ const title =
3008
+ moduleId === "overview" ? "Overview" : moduleConfig.title;
3009
+
3010
+ setOpenTabs((prev) =>
3011
+ new Map(prev).set(moduleId, { title, config: moduleConfig })
3012
+ );
3013
+ }
3014
+ }
3015
+
3016
+ setActiveTab(moduleId);
3017
+ });
3018
+
3019
+ if (isMobile) {
3020
+ setMobileMenuOpen(false);
3021
+ }
3022
+
3023
+ if (onModuleChange) {
3024
+ (onModuleChange as (moduleId: string, config?: ModuleConfig) => void)(
3025
+ moduleId,
3026
+ dynamicModules[moduleId]
3027
+ );
3028
+ }
3029
+ },
3030
+ [
3031
+ openTabs,
3032
+ isMobile,
3033
+ tabModeEnabled,
3034
+ startTransition,
3035
+ onModuleChange,
3036
+ dynamicModules,
3037
+ autoCollapseEnabled,
3038
+ findActiveSectionName,
3039
+ ]
3040
+ );
3041
+
3042
+ // Handle tab close
3043
+ const handleTabClose = useCallback(
3044
+ (moduleId: string) => {
3045
+ if (moduleId === "overview") return;
3046
+
3047
+ setOpenTabs((prev) => {
3048
+ const newTabs = new Map(prev);
3049
+ newTabs.delete(moduleId);
3050
+ return newTabs;
3051
+ });
3052
+
3053
+ if (activeTab === moduleId) {
3054
+ const availableTabs = Array.from(openTabs.keys()).filter(
3055
+ (id) => id !== moduleId
3056
+ );
3057
+ const newActiveTab = availableTabs.includes("overview")
3058
+ ? "overview"
3059
+ : availableTabs[0];
3060
+ if (newActiveTab) {
3061
+ setActiveTab(newActiveTab);
3062
+ }
3063
+ }
3064
+ },
3065
+ [activeTab, openTabs]
3066
+ );
3067
+
3068
+ // Handle back to overview in single page mode
3069
+ const handleBackToOverview = () => {
3070
+ setActiveTab("overview");
3071
+ };
3072
+
3073
+ const toggleSection = (sectionName: string) => {
3074
+ setExpandedSections((prev) => {
3075
+ const newSet = new Set(prev);
3076
+ if (newSet.has(sectionName)) {
3077
+ newSet.delete(sectionName);
3078
+ } else {
3079
+ newSet.add(sectionName);
3080
+ }
3081
+ return newSet;
3082
+ });
3083
+ };
3084
+
3085
+ // Handle section click in compact mode
3086
+ const handleSectionClick = useCallback(
3087
+ (section: any) => {
3088
+ if (section.items.length === 1) {
3089
+ handleModuleSelect(section.items[0].id);
3090
+ } else {
3091
+ handleModuleSelect(section.items[0].id);
3092
+ }
3093
+ },
3094
+ [handleModuleSelect]
3095
+ );
3096
+
3097
+ const toggleTheme = () => {
3098
+ startTransition(() => {
3099
+ setDarkMode((prev) => !prev);
3100
+ });
3101
+ };
3102
+
3103
+ const toggleFooter = () => {
3104
+ setFooterVisible((prev) => !prev);
3105
+ };
3106
+
3107
+ const toggleAutoCollapse = () => {
3108
+ setAutoCollapseEnabled((prev) => !prev);
3109
+ };
3110
+
3111
+ // Persona panel handlers
3112
+ const handleChangePassword = () => {
3113
+ setPersonaPanelOpen(false);
3114
+ if (config.hooks.onChangePassword) {
3115
+ config.hooks.onChangePassword();
3116
+ } else {
3117
+ // Open the change password modal
3118
+ setChangePasswordModalOpen(true);
3119
+ }
3120
+ };
3121
+
3122
+ // Handle change password submission
3123
+ const handleChangePasswordSubmit = async (data: {
3124
+ currentPassword: string;
3125
+ newPassword: string;
3126
+ confirmPassword: string;
3127
+ }) => {
3128
+ setIsChangingPassword(true);
3129
+ try {
3130
+ // TODO: Replace with actual API call
3131
+ // await authService.changePassword(data);
3132
+
3133
+ // Simulate API call
3134
+ await new Promise((resolve) => setTimeout(resolve, 1500));
3135
+
3136
+ // Show success notification
3137
+ notificationService.success(
3138
+ 'Password Changed',
3139
+ 'Your password has been changed successfully.'
3140
+ );
3141
+
3142
+ setChangePasswordModalOpen(false);
3143
+ } catch (error: any) {
3144
+ // Show error notification
3145
+ notificationService.error(
3146
+ 'Password Change Failed',
3147
+ error?.message || 'Failed to change password. Please try again.'
3148
+ );
3149
+ throw error; // Re-throw to let the modal handle it
3150
+ } finally {
3151
+ setIsChangingPassword(false);
3152
+ }
3153
+ };
3154
+
3155
+ const handleLogout = () => {
3156
+ console.log("Logout button clicked");
3157
+ try {
3158
+ setPersonaPanelOpen(false);
3159
+
3160
+ if (config.hooks.onLogout) {
3161
+ console.log("Calling custom logout hook");
3162
+ config.hooks.onLogout();
3163
+ } else {
3164
+ console.log("No custom logout hook, using default");
3165
+ const confirmLogout = window.confirm("Are you sure you want to logout?");
3166
+ if (confirmLogout) {
3167
+ console.log("User confirmed logout");
3168
+ // Clear any stored session data
3169
+ localStorage.removeItem('authToken');
3170
+ localStorage.removeItem('userData');
3171
+ sessionStorage.clear();
3172
+
3173
+ // Show logout confirmation
3174
+ alert("You have been logged out successfully!");
3175
+
3176
+ // Redirect to login page after alert is dismissed
3177
+ console.log("Redirecting to login page...");
3178
+ setTimeout(() => {
3179
+ // Redirect to root path (login page)
3180
+ window.location.href = '/';
3181
+ }, 100); // Small delay to ensure alert is dismissed
3182
+ } else {
3183
+ console.log("User cancelled logout");
3184
+ }
3185
+ }
3186
+ } catch (error) {
3187
+ console.error("Error during logout:", error);
3188
+ alert("An error occurred during logout. Please try again.");
3189
+ }
3190
+ };
3191
+
3192
+ // Environment class helper
3193
+ const getEnvironmentClass = (env: string) => {
3194
+ const envLower = env.toLowerCase();
3195
+ switch (envLower) {
3196
+ case "development":
3197
+ return "env-development";
3198
+ case "staging":
3199
+ return "env-staging";
3200
+ case "production":
3201
+ return "env-production";
3202
+ case "uat":
3203
+ return "env-uat";
3204
+ default:
3205
+ return "env-development";
3206
+ }
3207
+ };
3208
+
3209
+ // NEW: Recursive function to render navigation items with multi-level support
3210
+ const renderNavigationItem = useCallback(
3211
+ (item: NavigationItem, level: number = 0) => {
3212
+ const hasChildren = item.children && item.children.length > 0;
3213
+ const isExpanded = expandedSubItems.has(item.id);
3214
+ const isActive = hasActiveChild(item, activeTab);
3215
+ const isDirectlyActive = activeTab === item.id;
3216
+
3217
+ if (hasChildren) {
3218
+ return (
3219
+ <li key={item.id} className={`nav-item nav-item-with-children`}>
3220
+ <a
3221
+ href="#"
3222
+ className={`nav-link ${isDirectlyActive ? "active" : ""}`}
3223
+ onClick={(e) => {
3224
+ e.preventDefault();
3225
+ toggleSubItem(item.id);
3226
+ }}
3227
+ title={sidebarCollapsed ? item.title : undefined}
3228
+ >
3229
+ {IconSystem[item.icon] && IconSystem[item.icon]()}
3230
+ {!sidebarCollapsed && (
3231
+ <>
3232
+ {item.title}
3233
+ {item.badge && (
3234
+ <span className="nav-badge">{item.badge}</span>
3235
+ )}
3236
+ <div
3237
+ className={`nav-item-expand ${
3238
+ isExpanded ? "expanded" : ""
3239
+ }`}
3240
+ >
3241
+ <IconSystem.chevronRight />
3242
+ </div>
3243
+ </>
3244
+ )}
3245
+ </a>
3246
+ {!sidebarCollapsed && item.children && (
3247
+ <ul
3248
+ className={`nav-sub-menu ${
3249
+ isExpanded ? "expanded" : "collapsed"
3250
+ }`}
3251
+ style={{
3252
+ maxHeight: isExpanded ? "300px" : "0",
3253
+ overflow: "hidden",
3254
+ transition: "all 0.3s ease",
3255
+ opacity: isExpanded ? 1 : 0,
3256
+ }}
3257
+ >
3258
+ {item.children.map((child) =>
3259
+ renderNavigationItem(child, level + 1)
3260
+ )}
3261
+ </ul>
3262
+ )}
3263
+ </li>
3264
+ );
3265
+ } else {
3266
+ return (
3267
+ <li key={item.id} className={level > 0 ? "nav-sub-item" : "nav-item"}>
3268
+ <a
3269
+ href="#"
3270
+ className={
3271
+ level > 0
3272
+ ? `nav-sub-link ${isDirectlyActive ? "active" : ""}`
3273
+ : `nav-link ${isDirectlyActive ? "active" : ""}`
3274
+ }
3275
+ onClick={(e) => {
3276
+ e.preventDefault();
3277
+ if (item.onClick) {
3278
+ item.onClick();
3279
+ } else {
3280
+ handleModuleSelect(item.id);
3281
+ }
3282
+ }}
3283
+ title={sidebarCollapsed ? item.title : undefined}
3284
+ >
3285
+ {IconSystem[item.icon] && IconSystem[item.icon]()}
3286
+ {!sidebarCollapsed && (
3287
+ <>
3288
+ {item.title}
3289
+ {item.badge && (
3290
+ <span className="nav-badge">{item.badge}</span>
3291
+ )}
3292
+ </>
3293
+ )}
3294
+ </a>
3295
+ </li>
3296
+ );
3297
+ }
3298
+ },
3299
+ [
3300
+ activeTab,
3301
+ sidebarCollapsed,
3302
+ expandedSubItems,
3303
+ toggleSubItem,
3304
+ handleModuleSelect,
3305
+ hasActiveChild,
3306
+ IconSystem,
3307
+ ]
3308
+ );
3309
+
3310
+ // Breadcrumb component
3311
+ const BreadcrumbComponent = () => {
3312
+ if (!config.layout.showBreadcrumbs || isMobile) return null;
3313
+
3314
+ return (
3315
+ <div className="breadcrumb-container">
3316
+ <nav className="breadcrumb" aria-label="Breadcrumb">
3317
+ {breadcrumbs.map((crumb, index) => (
3318
+ <div key={index} className="breadcrumb-item">
3319
+ {index < breadcrumbs.length - 1 ? (
3320
+ <>
3321
+ <a
3322
+ href="#"
3323
+ className="breadcrumb-link"
3324
+ onClick={(e) => e.preventDefault()}
3325
+ >
3326
+ {crumb}
3327
+ </a>
3328
+ <span className="breadcrumb-separator">
3329
+ <IconSystem.breadcrumbSeparator />
3330
+ </span>
3331
+ </>
3332
+ ) : (
3333
+ <span className="breadcrumb-current">{crumb}</span>
3334
+ )}
3335
+ </div>
3336
+ ))}
3337
+ </nav>
3338
+ </div>
3339
+ );
3340
+ };
3341
+
3342
+ // Logo component
3343
+ const LogoComponent = () => {
3344
+ const { logo } = config.branding;
3345
+
3346
+ return (
3347
+ <div className="app-logo">
3348
+ {logo?.src && (
3349
+ <img
3350
+ src={logo.src}
3351
+ alt={logo.alt || `${dynamicAppTitle} Logo`}
3352
+ className="logo-image"
3353
+ style={{
3354
+ width: logo.width || "28px",
3355
+ height: logo.height || "28px",
3356
+ }}
3357
+ onError={(e) => {
3358
+ (e.target as HTMLImageElement).style.display = "none";
3359
+ }}
3360
+ />
3361
+ )}
3362
+ {logo?.showText !== false && <span>{dynamicAppTitle}</span>}
3363
+ </div>
3364
+ );
3365
+ };
3366
+
3367
+ // Header Info Card Component
3368
+ interface HeaderInfoCardProps {
3369
+ label: string;
3370
+ value: string;
3371
+ icon?: React.ReactNode;
3372
+ color: 'blue' | 'green';
3373
+ options?: string[];
3374
+ isOpen?: boolean;
3375
+ onToggle?: () => void;
3376
+ onSelect?: (value: string) => void;
3377
+ }
3378
+
3379
+ const HeaderInfoCard: React.FC<HeaderInfoCardProps> = ({
3380
+ label,
3381
+ value,
3382
+ icon,
3383
+ color,
3384
+ options,
3385
+ isOpen,
3386
+ onToggle,
3387
+ onSelect
3388
+ }) => {
3389
+ const isSelectable = options && options.length > 0;
3390
+
3391
+ return (
3392
+ <div
3393
+ className={`header-info-card header-info-card-${color} ${isSelectable ? 'selectable' : ''}`}
3394
+ onClick={isSelectable ? onToggle : undefined}
3395
+ >
3396
+ {icon && <div className="header-info-icon">{icon}</div>}
3397
+ <div className="header-info-content">
3398
+ <span className="header-info-label">{label}</span>
3399
+ <span className="header-info-value">{value}</span>
3400
+ </div>
3401
+ {isSelectable && (
3402
+ <>
3403
+ <div className="header-info-chevron">
3404
+ <IconSystem.chevronDown />
3405
+ </div>
3406
+ {isOpen && (
3407
+ <div className={`header-info-dropdown header-info-dropdown-${color}`}>
3408
+ {options.map((option) => (
3409
+ <div
3410
+
3411
+ key={option}
3412
+ className={`header-info-dropdown-item ${option === value ? 'active' : ''}`}
3413
+ onClick={(e) => {
3414
+ e.stopPropagation();
3415
+ onSelect?.(option);
3416
+ onToggle?.();
3417
+ }}
3418
+ >
3419
+ {option}
3420
+ {option === value && (
3421
+ <svg
3422
+ width="14"
3423
+ height="14"
3424
+ viewBox="0 0 24 24"
3425
+ fill="none"
3426
+ stroke="currentColor"
3427
+ strokeWidth="3"
3428
+ >
3429
+ <polyline points="20 6 9 17 4 12"></polyline>
3430
+ </svg>
3431
+ )}
3432
+ </div>
3433
+ ))}
3434
+ </div>
3435
+ )}
3436
+ </>
3437
+ )}
3438
+ </div>
3439
+ );
3440
+ };
3441
+
3442
+ // Persona Control Component
3443
+ const PersonaControlComponent = () => {
3444
+ return (
3445
+ <div
3446
+ className="persona-control"
3447
+ onClick={() => setPersonaPanelOpen(!personaPanelOpen)}
3448
+ >
3449
+ <div className="persona-avatar">{dynamicUser.avatar}</div>
3450
+ {!isMobile && <div className="persona-role">{dynamicUser.role}</div>}
3451
+ <IconSystem.chevronDown />
3452
+
3453
+ {/* FIX: Only render panel content after initialization */}
3454
+ {isInitialized && (
3455
+ <div className={`persona-panel ${personaPanelOpen ? "open" : ""}`}>
3456
+ <div className="persona-panel-header">
3457
+ <div className="persona-panel-avatar">{dynamicUser.avatar}</div>
3458
+ <div className="persona-panel-info">
3459
+ <h3>{dynamicUser.name}</h3>
3460
+ <p>{dynamicUser.role}</p>
3461
+ </div>
3462
+ </div>
3463
+
3464
+ <div className="persona-panel-section">
3465
+ <h4>Display Options</h4>
3466
+ <div className="persona-panel-controls">
3467
+ <div className="persona-control-item">
3468
+ <div className="persona-control-label">
3469
+ <IconSystem.tabs />
3470
+ View Mode
3471
+ </div>
3472
+ <div className="view-mode-toggle">
3473
+ <button
3474
+ className={`view-mode-btn ${
3475
+ tabModeEnabled ? "active" : ""
3476
+ }`}
3477
+ onClick={(e) => {
3478
+ e.stopPropagation();
3479
+ startTransition(() => setTabModeEnabled(true));
3480
+ }}
3481
+ >
3482
+ Tabs
3483
+ </button>
3484
+ <button
3485
+ className={`view-mode-btn ${
3486
+ !tabModeEnabled ? "active" : ""
3487
+ }`}
3488
+ onClick={(e) => {
3489
+ e.stopPropagation();
3490
+ startTransition(() => setTabModeEnabled(false));
3491
+ }}
3492
+ >
3493
+ Single
3494
+ </button>
3495
+ </div>
3496
+ </div>
3497
+
3498
+ <div className="persona-control-item">
3499
+ <div className="persona-control-label">
3500
+ <IconSystem.settings />
3501
+ Dark Mode-7
3502
+ </div>
3503
+ <div
3504
+ className={`theme-switch ${darkMode ? "active" : ""}`}
3505
+ onClick={(e) => {
3506
+ e.stopPropagation();
3507
+ toggleTheme();
3508
+ }}
3509
+ >
3510
+ <div className="theme-switch-handle"></div>
3511
+ </div>
3512
+ </div>
3513
+
3514
+ <div className="persona-control-item">
3515
+ <div className="persona-control-label">
3516
+ <IconSystem.footer />
3517
+ Show Footer
3518
+ </div>
3519
+ <div
3520
+ className={`theme-switch ${footerVisible ? "active" : ""}`}
3521
+ onClick={(e) => {
3522
+ e.stopPropagation();
3523
+ toggleFooter();
3524
+ }}
3525
+ >
3526
+ <div className="theme-switch-handle"></div>
3527
+ </div>
3528
+ </div>
3529
+
3530
+ <div className="persona-control-item">
3531
+ <div className="persona-control-label">
3532
+ <IconSystem.collapse />
3533
+ Auto Collapse Sidebar
3534
+ </div>
3535
+ <div
3536
+ className={`theme-switch ${
3537
+ autoCollapseEnabled ? "active" : ""
3538
+ }`}
3539
+ onClick={(e) => {
3540
+ e.stopPropagation();
3541
+ toggleAutoCollapse();
3542
+ }}
3543
+ >
3544
+ <div className="theme-switch-handle"></div>
3545
+ </div>
3546
+ </div>
3547
+ </div>
3548
+ </div>
3549
+
3550
+ <div className="persona-panel-section">
3551
+ <h4>Account</h4>
3552
+ <div className="persona-panel-controls">
3553
+ <div
3554
+ className="persona-control-item action"
3555
+ onClick={(e) => {
3556
+ e.stopPropagation();
3557
+ handleChangePassword();
3558
+ }}
3559
+ >
3560
+ <div className="persona-control-label">
3561
+ <IconSystem.lock />
3562
+ Change Password
3563
+ </div>
3564
+ </div>
3565
+
3566
+ <div
3567
+ className="persona-control-item action danger"
3568
+ onClick={(e) => {
3569
+ e.stopPropagation();
3570
+ handleLogout();
3571
+ }}
3572
+ >
3573
+ <div className="persona-control-label">
3574
+ <IconSystem.logout />
3575
+ Logout
3576
+ </div>
3577
+ </div>
3578
+ </div>
3579
+ </div>
3580
+ </div>
3581
+ )}
3582
+ </div>
3583
+ );
3584
+ };
3585
+
3586
+ // Click outside handler for persona panel
3587
+ useEffect(() => {
3588
+ const handleClickOutside = (event: MouseEvent) => {
3589
+ const target = event.target as Element;
3590
+ if (personaPanelOpen && !target.closest(".persona-control")) {
3591
+ setPersonaPanelOpen(false);
3592
+ }
3593
+ };
3594
+
3595
+ document.addEventListener("mousedown", handleClickOutside);
3596
+ return () => {
3597
+ document.removeEventListener("mousedown", handleClickOutside);
3598
+ };
3599
+ }, [personaPanelOpen]);
3600
+
3601
+ // Click outside handler for notification panel
3602
+ useEffect(() => {
3603
+ const handleClickOutside = (event: MouseEvent) => {
3604
+ const target = event.target as Element;
3605
+ if (notificationPanelOpen && !target.closest(".notification-bell") && !target.closest(".notification-panel")) {
3606
+ setNotificationPanelOpen(false);
3607
+ }
3608
+ };
3609
+
3610
+ document.addEventListener("mousedown", handleClickOutside);
3611
+ return () => {
3612
+ document.removeEventListener("mousedown", handleClickOutside);
3613
+ };
3614
+ }, [notificationPanelOpen]);
3615
+
3616
+ // Click outside handler for tab overflow menu
3617
+ useEffect(() => {
3618
+ const handleClickOutside = (event: MouseEvent) => {
3619
+ const target = event.target as Element;
3620
+ if (tabOverflowMenuOpen && !target.closest(".tab-more-container")) {
3621
+ setTabOverflowMenuOpen(false);
3622
+ }
3623
+ };
3624
+
3625
+ document.addEventListener("mousedown", handleClickOutside);
3626
+ return () => {
3627
+ document.removeEventListener("mousedown", handleClickOutside);
3628
+ };
3629
+ }, [tabOverflowMenuOpen]);
3630
+
3631
+ // Click outside handler for header info dropdowns
3632
+ useEffect(() => {
3633
+ const handleClickOutside = (event: MouseEvent) => {
3634
+ const target = event.target as Element;
3635
+ if (!target.closest(".header-info-card")) {
3636
+ setEntityDropdownOpen(false);
3637
+ setFyDropdownOpen(false);
3638
+ }
3639
+ };
3640
+
3641
+ document.addEventListener("mousedown", handleClickOutside);
3642
+ return () => {
3643
+ document.removeEventListener("mousedown", handleClickOutside);
3644
+ };
3645
+ }, []);
3646
+
3647
+ return (
3648
+ <div
3649
+ className={`enterprise-layout ${isInitialized ? "initialized" : ""}`}
3650
+ id={`enterprise-app-${componentId}`}
3651
+ >
3652
+ {/* Header */}
3653
+ <header className="layout-header">
3654
+ <div className="header-left">
3655
+ <button
3656
+ className="menu-toggle"
3657
+ onClick={() =>
3658
+ isMobile
3659
+ ? setMobileMenuOpen(!mobileMenuOpen)
3660
+ : setSidebarCollapsed(!sidebarCollapsed)
3661
+ }
3662
+ >
3663
+ <IconSystem.menu />
3664
+ </button>
3665
+ <LogoComponent />
3666
+ <BreadcrumbComponent />
3667
+ </div>
3668
+
3669
+ <div className="header-center">{/* Reserved for future use */}</div>
3670
+
3671
+ <div className="header-right">
3672
+ {/* Entity and Financial Year Info */}
3673
+ <div className="header-info-section">
3674
+ <HeaderInfoCard
3675
+ label="Entity & BU"
3676
+ value={`${currentEntity} - ${currentBusinessUnit}`}
3677
+ color="blue"
3678
+ options={availableEntities}
3679
+ isOpen={entityDropdownOpen}
3680
+ onToggle={() => {
3681
+ setEntityDropdownOpen(!entityDropdownOpen);
3682
+ setFyDropdownOpen(false);
3683
+ }}
3684
+ onSelect={(value) => setCurrentEntity(value)}
3685
+ icon={
3686
+ <svg
3687
+ width="18"
3688
+ height="18"
3689
+ viewBox="0 0 24 24"
3690
+ fill="none"
3691
+ stroke="currentColor"
3692
+ strokeWidth="2"
3693
+ >
3694
+ <rect x="4" y="2" width="16" height="20" rx="2" />
3695
+ <line x1="9" y1="6" x2="9" y2="6" />
3696
+ <line x1="15" y1="6" x2="15" y2="6" />
3697
+ <line x1="9" y1="10" x2="9" y2="10" />
3698
+ <line x1="15" y1="10" x2="15" y2="10" />
3699
+ <line x1="9" y1="14" x2="9" y2="14" />
3700
+ <line x1="15" y1="14" x2="15" y2="14" />
3701
+ <line x1="9" y1="18" x2="9" y2="18" />
3702
+ <line x1="15" y1="18" x2="15" y2="18" />
3703
+ </svg>
3704
+ }
3705
+ />
3706
+ <HeaderInfoCard
3707
+ label="Financial Year"
3708
+ value={financialYear}
3709
+ color="green"
3710
+ options={availableFinancialYears}
3711
+ isOpen={fyDropdownOpen}
3712
+ onToggle={() => {
3713
+ setFyDropdownOpen(!fyDropdownOpen);
3714
+ setEntityDropdownOpen(false);
3715
+ }}
3716
+ onSelect={(value) => setFinancialYear(value)}
3717
+ icon={
3718
+ <svg
3719
+ width="18"
3720
+ height="18"
3721
+ viewBox="0 0 24 24"
3722
+ fill="none"
3723
+ stroke="currentColor"
3724
+ strokeWidth="2"
3725
+ >
3726
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
3727
+ <line x1="16" y1="2" x2="16" y2="6" />
3728
+ <line x1="8" y1="2" x2="8" y2="6" />
3729
+ <line x1="3" y1="10" x2="21" y2="10" />
3730
+ </svg>
3731
+ }
3732
+ />
3733
+ </div>
3734
+
3735
+ {/* Notification Bell */}
3736
+ <div style={{ position: "relative" }}>
3737
+ <div
3738
+ className="notification-bell"
3739
+ onClick={() => {
3740
+ setNotificationPanelOpen(!notificationPanelOpen);
3741
+ setPersonaPanelOpen(false);
3742
+ setEntityDropdownOpen(false);
3743
+ setFyDropdownOpen(false);
3744
+ }}
3745
+ >
3746
+ <IconSystem.notification />
3747
+ {notifications.filter(n => !n.read).length > 0 && (
3748
+ <div className="notification-badge">
3749
+ {notifications.filter(n => !n.read).length}
3750
+ </div>
3751
+ )}
3752
+ </div>
3753
+
3754
+ {/* Notification Panel */}
3755
+ {isInitialized && (
3756
+ <div className={`notification-panel ${notificationPanelOpen ? "open" : ""}`}>
3757
+ <div className="notification-panel-header">
3758
+ <div className="notification-panel-title">Notifications</div>
3759
+ <div className="notification-panel-actions">
3760
+ <button
3761
+ className="notification-action-btn"
3762
+ onClick={(e) => {
3763
+ e.stopPropagation();
3764
+ setNotifications(notifications.map(n => ({ ...n, read: true })));
3765
+ }}
3766
+ >
3767
+ Mark all read
3768
+ </button>
3769
+ </div>
3770
+ </div>
3771
+
3772
+ {/* Filter and Grouping Bar */}
3773
+ <div className="notification-filters">
3774
+ <div className="notification-filter-tabs">
3775
+ <button
3776
+ className={`notification-filter-tab ${notificationFilter === 'all' ? 'active' : ''}`}
3777
+ onClick={() => setNotificationFilter('all')}
3778
+ >
3779
+ All
3780
+ </button>
3781
+ <button
3782
+ className={`notification-filter-tab ${notificationFilter === 'unread' ? 'active' : ''}`}
3783
+ onClick={() => setNotificationFilter('unread')}
3784
+ >
3785
+ Unread
3786
+ </button>
3787
+ <button
3788
+ className={`notification-filter-tab ${notificationFilter === 'read' ? 'active' : ''}`}
3789
+ onClick={() => setNotificationFilter('read')}
3790
+ >
3791
+ Read
3792
+ </button>
3793
+ </div>
3794
+ <select
3795
+ className="notification-grouping-btn"
3796
+ value={notificationGrouping}
3797
+ onChange={(e) => setNotificationGrouping(e.target.value as 'none' | 'type' | 'time')}
3798
+ >
3799
+ <option value="none">No Grouping</option>
3800
+ <option value="type">Group by Type</option>
3801
+ <option value="time">Group by Time</option>
3802
+ </select>
3803
+ </div>
3804
+
3805
+ <div className="notification-panel-content">
3806
+ {(() => {
3807
+ // Filter notifications based on selected filter
3808
+ const filteredNotifications = notifications.filter(n => {
3809
+ if (notificationFilter === 'unread') return !n.read;
3810
+ if (notificationFilter === 'read') return n.read;
3811
+ return true;
3812
+ });
3813
+
3814
+ if (filteredNotifications.length === 0) {
3815
+ return (
3816
+ <div className="notification-empty">
3817
+ <div className="notification-empty-icon">🔔</div>
3818
+ <div>No notifications found</div>
3819
+ </div>
3820
+ );
3821
+ }
3822
+
3823
+ // Group notifications if grouping is enabled
3824
+ if (notificationGrouping === 'type') {
3825
+ const groupedByType: { [key: string]: typeof filteredNotifications } = {};
3826
+ filteredNotifications.forEach(n => {
3827
+ if (!groupedByType[n.type]) groupedByType[n.type] = [];
3828
+ groupedByType[n.type].push(n);
3829
+ });
3830
+
3831
+ return Object.entries(groupedByType).map(([type, items]) => (
3832
+ <React.Fragment key={type}>
3833
+ <div className="notification-group-header">
3834
+ {type.charAt(0).toUpperCase() + type.slice(1)} ({items.length})
3835
+ </div>
3836
+ {items.map((notification) => (
3837
+ <div
3838
+ key={notification.id}
3839
+ className={`notification-item ${!notification.read ? 'unread' : ''}`}
3840
+ onClick={(e) => {
3841
+ e.stopPropagation();
3842
+ setNotifications(notifications.map(n =>
3843
+ n.id === notification.id ? { ...n, read: true } : n
3844
+ ));
3845
+ }}
3846
+ >
3847
+ <div className={`notification-icon ${notification.type}`}>
3848
+ {notification.type === 'success' && '✓'}
3849
+ {notification.type === 'info' && 'ℹ'}
3850
+ {notification.type === 'warning' && '⚠'}
3851
+ {notification.type === 'error' && '✕'}
3852
+ </div>
3853
+ <div className="notification-content">
3854
+ <div className="notification-title">{notification.title}</div>
3855
+ <div className="notification-message">{notification.message}</div>
3856
+ <div className="notification-time">{notification.time}</div>
3857
+ </div>
3858
+ </div>
3859
+ ))}
3860
+ </React.Fragment>
3861
+ ));
3862
+ } else if (notificationGrouping === 'time') {
3863
+ const groupedByTime: { [key: string]: typeof filteredNotifications } = {
3864
+ 'Recent': [],
3865
+ 'Earlier Today': [],
3866
+ 'Older': []
3867
+ };
3868
+
3869
+ filteredNotifications.forEach(n => {
3870
+ if (n.time.includes('min') || n.time.includes('hour')) {
3871
+ groupedByTime['Recent'].push(n);
3872
+ } else if (n.time.includes('today')) {
3873
+ groupedByTime['Earlier Today'].push(n);
3874
+ } else {
3875
+ groupedByTime['Older'].push(n);
3876
+ }
3877
+ });
3878
+
3879
+ return Object.entries(groupedByTime)
3880
+ .filter(([_, items]) => items.length > 0)
3881
+ .map(([time, items]) => (
3882
+ <React.Fragment key={time}>
3883
+ <div className="notification-group-header">
3884
+ {time} ({items.length})
3885
+ </div>
3886
+ {items.map((notification) => (
3887
+ <div
3888
+ key={notification.id}
3889
+ className={`notification-item ${!notification.read ? 'unread' : ''}`}
3890
+ onClick={(e) => {
3891
+ e.stopPropagation();
3892
+ setNotifications(notifications.map(n =>
3893
+ n.id === notification.id ? { ...n, read: true } : n
3894
+ ));
3895
+ }}
3896
+ >
3897
+ <div className={`notification-icon ${notification.type}`}>
3898
+ {notification.type === 'success' && '✓'}
3899
+ {notification.type === 'info' && 'ℹ'}
3900
+ {notification.type === 'warning' && '⚠'}
3901
+ {notification.type === 'error' && '✕'}
3902
+ </div>
3903
+ <div className="notification-content">
3904
+ <div className="notification-title">{notification.title}</div>
3905
+ <div className="notification-message">{notification.message}</div>
3906
+ <div className="notification-time">{notification.time}</div>
3907
+ </div>
3908
+ </div>
3909
+ ))}
3910
+ </React.Fragment>
3911
+ ));
3912
+ } else {
3913
+ // No grouping - show flat list
3914
+ return filteredNotifications.map((notification) => (
3915
+ <div
3916
+ key={notification.id}
3917
+ className={`notification-item ${!notification.read ? 'unread' : ''}`}
3918
+ onClick={(e) => {
3919
+ e.stopPropagation();
3920
+ setNotifications(notifications.map(n =>
3921
+ n.id === notification.id ? { ...n, read: true } : n
3922
+ ));
3923
+ }}
3924
+ >
3925
+ <div className={`notification-icon ${notification.type}`}>
3926
+ {notification.type === 'success' && '✓'}
3927
+ {notification.type === 'info' && 'ℹ'}
3928
+ {notification.type === 'warning' && '⚠'}
3929
+ {notification.type === 'error' && '✕'}
3930
+ </div>
3931
+ <div className="notification-content">
3932
+ <div className="notification-title">{notification.title}</div>
3933
+ <div className="notification-message">{notification.message}</div>
3934
+ <div className="notification-time">{notification.time}</div>
3935
+ </div>
3936
+ </div>
3937
+ ));
3938
+ }
3939
+ })()}
3940
+ </div>
3941
+ </div>
3942
+ )}
3943
+ </div>
3944
+
3945
+ <PersonaControlComponent />
3946
+ </div>
3947
+ </header>
3948
+
3949
+
3950
+
3951
+
3952
+
3953
+
3954
+ {/* Mobile Overlay - shown when menu is open on mobile */}
3955
+ {isMobile && mobileMenuOpen && (
3956
+ <div
3957
+ className="mobile-overlay visible"
3958
+ onClick={() => setMobileMenuOpen(false)}
3959
+ />
3960
+ )}
3961
+
3962
+ {/* Body */}
3963
+ <div className={`layout-body ${!footerVisible ? "footer-hidden" : ""}`}>
3964
+ {/* Sidebar */}
3965
+ <aside
3966
+ className={`layout-sidebar ${sidebarCollapsed ? "collapsed" : ""} ${
3967
+ isMobile && !mobileMenuOpen ? "mobile-hidden" : ""
3968
+ } ${mobileMenuOpen ? "mobile-open" : ""}`}
3969
+ >
3970
+ {sidebarCollapsed ? (
3971
+ // Compact mode with smart grouping
3972
+ <div className="compact-navigation">
3973
+ {getCompactModeItems(config.layout.compactModeStrategy)
3974
+ .slice(0, visibleCompactItems)
3975
+ .map((item, index) => (
3976
+ <div key={item.id || index} className="compact-nav-item">
3977
+ <a
3978
+ href="#"
3979
+ className={`compact-nav-link section-tooltip ${
3980
+ item.isSection
3981
+ ? item.items?.some(
3982
+ (subItem: any) => subItem.id === activeTab
3983
+ )
3984
+ ? "active"
3985
+ : ""
3986
+ : activeTab === item.id
3987
+ ? "active"
3988
+ : ""
3989
+ }`}
3990
+ onClick={(e) => {
3991
+ e.preventDefault();
3992
+ if (item.isSection) {
3993
+ handleSectionClick(item);
3994
+ } else {
3995
+ handleModuleSelect(item.id);
3996
+ }
3997
+ }}
3998
+ data-tooltip={item.title}
3999
+ >
4000
+ {IconSystem[item.icon] && IconSystem[item.icon]()}
4001
+ {item.badge && (
4002
+ <span className="compact-nav-badge">{item.badge}</span>
4003
+ )}
4004
+ </a>
4005
+ </div>
4006
+ ))}
4007
+ {getCompactModeItems(config.layout.compactModeStrategy).length >
4008
+ visibleCompactItems && (
4009
+ <div className="compact-nav-item">
4010
+ <div
4011
+ className="compact-nav-link section-tooltip"
4012
+ data-tooltip={`+${
4013
+ getCompactModeItems(config.layout.compactModeStrategy)
4014
+ .length - visibleCompactItems
4015
+ } more items`}
4016
+ style={{
4017
+ opacity: 0.6,
4018
+ cursor: "default",
4019
+ fontSize: "0.75rem",
4020
+ fontWeight: 600,
4021
+ }}
4022
+ >
4023
+ +
4024
+ {getCompactModeItems(config.layout.compactModeStrategy)
4025
+ .length - visibleCompactItems}
4026
+ </div>
4027
+ </div>
4028
+ )}
4029
+ </div>
4030
+ ) : (
4031
+ // Regular expanded navigation with multi-level support
4032
+ dynamicNavigation.map((section, sectionIndex) => {
4033
+ const isExpanded = expandedSections.has(section.section);
4034
+
4035
+ return (
4036
+ <div key={sectionIndex} className="nav-section">
4037
+ <div
4038
+ className={`nav-section-header ${
4039
+ isExpanded ? "expanded" : "collapsed"
4040
+ }`}
4041
+ onClick={() =>
4042
+ !sidebarCollapsed && toggleSection(section.section)
4043
+ }
4044
+ style={{ cursor: sidebarCollapsed ? "default" : "pointer" }}
4045
+ >
4046
+ {!sidebarCollapsed && (
4047
+ <>
4048
+ <div className="nav-section-title" style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
4049
+ {section.icon && IconSystem[section.icon] && (
4050
+ <span style={{ display: "flex", alignItems: "center", color: "var(--text-secondary)", fontSize: "0.875rem" }}>
4051
+ {IconSystem[section.icon]()}
4052
+ </span>
4053
+ )}
4054
+ <span>
4055
+ {section.section}
4056
+ <span
4057
+ style={{
4058
+ marginLeft: "0.5rem",
4059
+ fontSize: "0.6rem",
4060
+ color: "var(--text-muted)",
4061
+ fontWeight: "400",
4062
+ }}
4063
+ >
4064
+ ({section.items.length})
4065
+ </span>
4066
+ </span>
4067
+ </div>
4068
+ <div className="nav-chevron">
4069
+ {isExpanded ? (
4070
+ <IconSystem.chevronDown />
4071
+ ) : (
4072
+ <IconSystem.chevronRight />
4073
+ )}
4074
+ </div>
4075
+ </>
4076
+ )}
4077
+ </div>
4078
+
4079
+ <div
4080
+ className={`nav-section-content ${
4081
+ isExpanded || sidebarCollapsed ? "expanded" : "collapsed"
4082
+ }`}
4083
+ >
4084
+ <ul className="nav-menu">
4085
+ {section.items.map((item) => renderNavigationItem(item))}
4086
+ </ul>
4087
+ </div>
4088
+ </div>
4089
+ );
4090
+ })
4091
+ )}
4092
+ </aside>
4093
+
4094
+ {/* Main Content */}
4095
+ <main className="layout-main">
4096
+ <div className="content-wrapper">
4097
+ {tabModeEnabled ? (
4098
+ <div className="tab-bar-wrapper">
4099
+ <div className="tab-bar-visible">
4100
+ {Array.from(openTabs.entries()).map(([moduleId, tabData]) => (
4101
+ <div
4102
+ key={moduleId}
4103
+ className={`tab ${activeTab === moduleId ? "active" : ""}`}
4104
+ onClick={() => setActiveTab(moduleId)}
4105
+ >
4106
+ <span className="tab-title">{tabData.title}</span>
4107
+ {moduleId !== "overview" && (
4108
+ <button
4109
+ className="tab-close"
4110
+ onClick={(e) => {
4111
+ e.stopPropagation();
4112
+ handleTabClose(moduleId);
4113
+ }}
4114
+ >
4115
+ ×
4116
+ </button>
4117
+ )}
4118
+ </div>
4119
+ ))}
4120
+ </div>
4121
+ {openTabs.size > 2 && (
4122
+ <div className="tab-more-container">
4123
+ <button
4124
+ className={`tab-more-btn ${tabOverflowMenuOpen ? "active" : ""}`}
4125
+ onClick={() => setTabOverflowMenuOpen(!tabOverflowMenuOpen)}
4126
+ >
4127
+ <span>More</span>
4128
+ <span>({openTabs.size - 2})</span>
4129
+ <IconSystem.chevronDown />
4130
+ </button>
4131
+ <div className={`tab-overflow-menu ${tabOverflowMenuOpen ? "open" : ""}`}>
4132
+ {Array.from(openTabs.entries())
4133
+ .slice(2)
4134
+ .map(([moduleId, tabData]) => (
4135
+ <div
4136
+ key={moduleId}
4137
+ className={`tab-overflow-item ${activeTab === moduleId ? "active" : ""}`}
4138
+ onClick={() => {
4139
+ setActiveTab(moduleId);
4140
+ setTabOverflowMenuOpen(false);
4141
+ }}
4142
+ >
4143
+ <span className="tab-overflow-item-title">{tabData.title}</span>
4144
+ {moduleId !== "overview" && (
4145
+ <button
4146
+ className="tab-overflow-item-close"
4147
+ onClick={(e) => {
4148
+ e.stopPropagation();
4149
+ handleTabClose(moduleId);
4150
+ }}
4151
+ >
4152
+ ×
4153
+ </button>
4154
+ )}
4155
+ </div>
4156
+ ))}
4157
+ </div>
4158
+ </div>
4159
+ )}
4160
+ </div>
4161
+ ) : (
4162
+ activeTab !== "overview" && (
4163
+ <div className="single-page-header">
4164
+ <div className="page-title">
4165
+ {openTabs.get(activeTab)?.title || "Unknown Module"}
4166
+ </div>
4167
+ <button
4168
+ className="back-button"
4169
+ onClick={handleBackToOverview}
4170
+ >
4171
+ <IconSystem.arrowLeft />
4172
+ Back to Overview
4173
+ </button>
4174
+ </div>
4175
+ )
4176
+ )}
4177
+
4178
+ <div className="tab-content-area">
4179
+ {Array.from(openTabs.entries()).map(([moduleId, tabData]) => (
4180
+ <div
4181
+ key={moduleId}
4182
+ className={`enterprise-module ${
4183
+ activeTab === moduleId ? "active" : ""
4184
+ }`}
4185
+ >
4186
+ {activeTab === moduleId && (
4187
+ <>
4188
+ {moduleId === "overview" ? (
4189
+ <>
4190
+ <div className="module-header">
4191
+ <div className="module-header-content">
4192
+ <h1 className="module-title">
4193
+ Welcome to {dynamicAppTitle}
4194
+ </h1>
4195
+ <p className="module-description">
4196
+ Enhanced Enterprise Management System with
4197
+ Multi-level Navigation Support
4198
+ </p>
4199
+ </div>
4200
+ <div className="module-actions">
4201
+ <button className="btn btn-primary">
4202
+ <IconSystem.plus />
4203
+ Get Started
4204
+ </button>
4205
+ <button className="btn btn-secondary">
4206
+ <IconSystem.file />
4207
+ Documentation
4208
+ </button>
4209
+ </div>
4210
+ </div>
4211
+
4212
+ <div className="module-content">
4213
+ {/* Grouped Sections with Accordion - Full Width Horizontal Layout */}
4214
+ <div style={{ padding: "0.75rem 0 0 0", width: "100%" }}>
4215
+ {dynamicNavigation
4216
+ .filter(section => section.section !== "Dashboard")
4217
+ .map((section) => {
4218
+ const filteredItems = section.items.filter(item => item.id !== "overview");
4219
+
4220
+ if (filteredItems.length === 0) return null;
4221
+
4222
+ const isExpanded = expandedOverviewSections.has(section.section);
4223
+
4224
+ return (
4225
+ <div
4226
+ key={section.section}
4227
+ style={{
4228
+ marginBottom: "1rem",
4229
+ background: "var(--glass-bg)",
4230
+ borderRadius: "1rem",
4231
+ border: "1px solid var(--border-color)",
4232
+ overflow: "hidden",
4233
+ width: "100%",
4234
+ boxSizing: "border-box"
4235
+ }}
4236
+ >
4237
+ {/* Accordion Header */}
4238
+ <div
4239
+ onClick={() => {
4240
+ setExpandedOverviewSections(prev => {
4241
+ const newSet = new Set(prev);
4242
+ if (newSet.has(section.section)) {
4243
+ newSet.delete(section.section);
4244
+ } else {
4245
+ newSet.add(section.section);
4246
+ }
4247
+ return newSet;
4248
+ });
4249
+ }}
4250
+ style={{
4251
+ padding: "1rem 1.25rem",
4252
+ cursor: "pointer",
4253
+ display: "flex",
4254
+ alignItems: "center",
4255
+ justifyContent: "space-between",
4256
+ background: "linear-gradient(135deg, rgba(248, 250, 252, 0.8) 0%, rgba(241, 245, 249, 0.6) 100%)",
4257
+ borderBottom: isExpanded ? "1px solid var(--border-color)" : "none",
4258
+ transition: "all 0.2s ease"
4259
+ }}
4260
+ onMouseEnter={(e) => {
4261
+ e.currentTarget.style.background = "linear-gradient(135deg, rgba(224, 235, 247, 1) 0%, rgba(203, 213, 225, 0.9) 100%)";
4262
+ }}
4263
+ onMouseLeave={(e) => {
4264
+ e.currentTarget.style.background = "linear-gradient(135deg, rgba(248, 250, 252, 0.8) 0%, rgba(241, 245, 249, 0.6) 100%)";
4265
+ }}
4266
+ >
4267
+ <div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
4268
+ <h2 style={{
4269
+ fontSize: "1.1rem",
4270
+ fontWeight: 600,
4271
+ color: "var(--text-primary)",
4272
+ margin: 0
4273
+ }}>
4274
+ {section.section}
4275
+ </h2>
4276
+ <span style={{
4277
+ fontSize: "0.8rem",
4278
+ color: "var(--text-secondary)",
4279
+ background: "var(--accent-color-10, rgba(59, 130, 246, 0.1))",
4280
+ padding: "0.2rem 0.6rem",
4281
+ borderRadius: "0.5rem",
4282
+ fontWeight: 500
4283
+ }}>
4284
+ {filteredItems.length}
4285
+ </span>
4286
+ </div>
4287
+ <div style={{
4288
+ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
4289
+ transition: "transform 0.3s ease",
4290
+ display: "flex",
4291
+ alignItems: "center",
4292
+ color: "var(--text-secondary)"
4293
+ }}>
4294
+ <IconSystem.chevronDown />
4295
+ </div>
4296
+ </div>
4297
+
4298
+ {/* Accordion Content - Responsive Grid Layout */}
4299
+ {isExpanded && (
4300
+ <div style={{
4301
+ padding: "1.25rem",
4302
+ display: "grid",
4303
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
4304
+ gap: "1rem",
4305
+ animation: "slideDown 0.3s ease",
4306
+ width: "100%",
4307
+ boxSizing: "border-box"
4308
+ }}>
4309
+ {filteredItems.map((item) => (
4310
+ <div
4311
+ key={item.id}
4312
+ onClick={() => handleModuleSelect(item.id)}
4313
+ style={{
4314
+ background: "var(--bg-secondary, rgba(255,255,255,0.02))",
4315
+ backdropFilter: "blur(15px)",
4316
+ border: "1px solid var(--border-color)",
4317
+ borderRadius: "1rem",
4318
+ padding: "1.25rem",
4319
+ transition: "all 0.3s ease",
4320
+ cursor: "pointer"
4321
+ }}
4322
+ onMouseEnter={(e) => {
4323
+ (e.currentTarget as HTMLElement).style.transform = "translateY(-4px)";
4324
+ (e.currentTarget as HTMLElement).style.boxShadow = "0 8px 16px rgba(0,0,0,0.1)";
4325
+ (e.currentTarget as HTMLElement).style.borderColor = "var(--accent-color)";
4326
+ }}
4327
+ onMouseLeave={(e) => {
4328
+ (e.currentTarget as HTMLElement).style.transform = "translateY(0)";
4329
+ (e.currentTarget as HTMLElement).style.boxShadow = "none";
4330
+ (e.currentTarget as HTMLElement).style.borderColor = "var(--border-color)";
4331
+ }}
4332
+ >
4333
+ <div
4334
+ style={{
4335
+ width: "2.5rem",
4336
+ height: "2.5rem",
4337
+ background: "var(--accent-gradient)",
4338
+ borderRadius: "0.75rem",
4339
+ display: "flex",
4340
+ alignItems: "center",
4341
+ justifyContent: "center",
4342
+ color: "white",
4343
+ marginBottom: "0.75rem",
4344
+ }}
4345
+ >
4346
+ {IconSystem[item.icon] && IconSystem[item.icon]()}
4347
+ </div>
4348
+ <h3
4349
+ style={{
4350
+ fontSize: "1rem",
4351
+ fontWeight: 600,
4352
+ marginBottom: "0.375rem",
4353
+ color: "var(--text-primary)",
4354
+ }}
4355
+ >
4356
+ {item.title}
4357
+ </h3>
4358
+ <p
4359
+ style={{
4360
+ fontSize: "0.8rem",
4361
+ color: "var(--text-secondary)",
4362
+ lineHeight: 1.5,
4363
+ margin: 0
4364
+ }}
4365
+ >
4366
+ Click to open {item.title.toLowerCase()} module
4367
+ </p>
4368
+ </div>
4369
+ ))}
4370
+ </div>
4371
+ )}
4372
+ </div>
4373
+ );
4374
+ })}
4375
+ </div>
4376
+ </div>
4377
+ </>
4378
+ ) : (
4379
+ <>
4380
+ {tabData.config?.showHeader && (
4381
+ <div className="module-header">
4382
+ <div className="module-header-content">
4383
+ <h1 className="module-title">
4384
+ {tabData.config?.title || tabData.title}
4385
+ </h1>
4386
+ <p className="module-description">
4387
+ {tabData.config?.description ||
4388
+ `${tabData.title} module for managing and monitoring operations`}
4389
+ </p>
4390
+ </div>
4391
+ </div>
4392
+ )}
4393
+
4394
+ {tabData.config?.component ? (
4395
+ React.createElement(tabData.config?.component)
4396
+ ) : (
4397
+ <div className="module-content" style={{ padding: "2rem", textAlign: "center" }}>
4398
+ <div style={{
4399
+ width: "4rem",
4400
+ height: "4rem",
4401
+ background: "var(--accent-gradient)",
4402
+ borderRadius: "1rem",
4403
+ display: "flex",
4404
+ alignItems: "center",
4405
+ justifyContent: "center",
4406
+ color: "white",
4407
+ margin: "0 auto 1.5rem",
4408
+ fontSize: "1.5rem"
4409
+ }}>
4410
+ {IconSystem.folder && IconSystem.folder()}
4411
+ </div>
4412
+ <h2 style={{
4413
+ fontSize: "1.5rem",
4414
+ fontWeight: 600,
4415
+ color: "var(--text-primary)",
4416
+ marginBottom: "0.5rem"
4417
+ }}>
4418
+ {tabData.title}
4419
+ </h2>
4420
+ <p style={{
4421
+ color: "var(--text-secondary)",
4422
+ maxWidth: "400px",
4423
+ margin: "0 auto"
4424
+ }}>
4425
+ This module is available. Configure the component in your module settings.
4426
+ </p>
4427
+ </div>
4428
+ )}
4429
+ </>
4430
+ )}
4431
+ </>
4432
+ )}
4433
+ </div>
4434
+ ))}
4435
+ </div>
4436
+ </div>
4437
+ </main>
4438
+ </div>
4439
+
4440
+ {/* Footer */}
4441
+ {config.layout.enableFooter && (
4442
+ <footer className={`layout-footer ${!footerVisible ? "hidden" : ""}`}>
4443
+ <div className="footer-left">
4444
+ <div
4445
+ className={`footer-environment ${getEnvironmentClass(
4446
+ config.footer.environment
4447
+ )}`}
4448
+ >
4449
+ {config.footer.environment}
4450
+ </div>
4451
+ <span>{config.footer.appVersion}</span>
4452
+ <span>{config.footer.copyright}</span>
4453
+ </div>
4454
+ <div className="footer-right">
4455
+ <a href={config.footer.supportLink} className="footer-support">
4456
+ {config.footer.supportText}
4457
+ </a>
4458
+ </div>
4459
+ </footer>
4460
+ )}
4461
+
4462
+ {/* Change Password Modal */}
4463
+ <ChangePasswordModal
4464
+ isOpen={changePasswordModalOpen}
4465
+ onClose={() => setChangePasswordModalOpen(false)}
4466
+ onSubmit={handleChangePasswordSubmit}
4467
+ username={config.user.name}
4468
+ isLoading={isChangingPassword}
4469
+ />
4470
+ </div>
4471
+ );
4472
+ };
4473
+
4474
+
4475
+ export default EnterpriseLayout;