@syuttechnologies/layout 1.0.0

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