cloud-ide-layout 1.0.109 → 1.0.112
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.
- package/fesm2022/{cloud-ide-layout-cloud-ide-layout-DKLoBtjT.mjs → cloud-ide-layout-cloud-ide-layout-B69VSPoc.mjs} +315 -57
- package/fesm2022/cloud-ide-layout-cloud-ide-layout-B69VSPoc.mjs.map +1 -0
- package/fesm2022/{cloud-ide-layout-drawer-theme.component-BnbifwOg.mjs → cloud-ide-layout-drawer-theme.component-DlUZDcaF.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-drawer-theme.component-BnbifwOg.mjs.map → cloud-ide-layout-drawer-theme.component-DlUZDcaF.mjs.map} +1 -1
- package/fesm2022/{cloud-ide-layout-floating-entity-selection.component-BWt2jNzs.mjs → cloud-ide-layout-floating-entity-selection.component-Bmxe5h-w.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-floating-entity-selection.component-BWt2jNzs.mjs.map → cloud-ide-layout-floating-entity-selection.component-Bmxe5h-w.mjs.map} +1 -1
- package/fesm2022/{cloud-ide-layout-home-wrapper.component-DBEz2uEN.mjs → cloud-ide-layout-home-wrapper.component-DVX-kibW.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-home-wrapper.component-DBEz2uEN.mjs.map → cloud-ide-layout-home-wrapper.component-DVX-kibW.mjs.map} +1 -1
- package/fesm2022/{cloud-ide-layout-sidedrawer-notes.component-CLgErxyC.mjs → cloud-ide-layout-sidedrawer-notes.component-C0wGoiPy.mjs} +2 -2
- package/fesm2022/{cloud-ide-layout-sidedrawer-notes.component-CLgErxyC.mjs.map → cloud-ide-layout-sidedrawer-notes.component-C0wGoiPy.mjs.map} +1 -1
- package/fesm2022/cloud-ide-layout.mjs +1 -1
- package/index.d.ts +55 -2
- package/package.json +1 -1
- package/fesm2022/cloud-ide-layout-cloud-ide-layout-DKLoBtjT.mjs.map +0 -1
|
@@ -5,9 +5,9 @@ import { cidePath, hostManagerRoutesUrl, coreRoutesUrl, commonRoutesUrl, designC
|
|
|
5
5
|
import { Observable, throwError, of, BehaviorSubject, interval, take as take$1, firstValueFrom } from 'rxjs';
|
|
6
6
|
import { map, filter, tap, catchError, shareReplay, take, distinctUntilChanged } from 'rxjs/operators';
|
|
7
7
|
import * as i2$1 from '@angular/router';
|
|
8
|
-
import { Router, NavigationEnd, RouteReuseStrategy, RouterModule } from '@angular/router';
|
|
8
|
+
import { Router, NavigationEnd, RouteReuseStrategy, RouterModule, ActivatedRoute } from '@angular/router';
|
|
9
9
|
import { Title, DomSanitizer } from '@angular/platform-browser';
|
|
10
|
-
import { CideEleFileManagerService, CideElementsService, CideEleFloatingContainerService, NotificationService, CideIconComponent, CideEleButtonComponent, CideInputComponent, CideSelectComponent, CideThemeService, WebSocketNotificationService, NotificationApiService, CideEleDropdownComponent, CideEleFileImageDirective, CideEleResizerDirective, TooltipDirective, CideSpinnerComponent, CideEleSkeletonLoaderComponent, KeyboardShortcutService, CideEleFloatingContainerManagerComponent, CideEleGlobalNotificationsComponent, CideEleBreadcrumbComponent } from 'cloud-ide-element';
|
|
10
|
+
import { CideEleFileManagerService, CideElementsService, CideEleFloatingContainerService, NotificationService, CideIconComponent, CideEleButtonComponent, CideInputComponent, CideSelectComponent, CideThemeService, WebSocketNotificationService, NotificationApiService, CideEleDropdownComponent, CideEleFileImageDirective, CideEleResizerDirective, TooltipDirective, CideSpinnerComponent, CideEleSkeletonLoaderComponent, KeyboardShortcutService, FloatingContainerShortcutsService, CideEleFloatingContainerManagerComponent, CideEleGlobalNotificationsComponent, CideEleBreadcrumbComponent } from 'cloud-ide-element';
|
|
11
11
|
import * as i1 from '@angular/common';
|
|
12
12
|
import { CommonModule, NgClass, NgFor, NgIf, isPlatformBrowser } from '@angular/common';
|
|
13
13
|
import { FINANCIAL_YEAR_SERVICE_TOKEN, ACADEMIC_YEAR_SERVICE_TOKEN, AUTH_SERVICE_TOKEN, authGuard, ENTITY_SERVICE_TOKEN } from 'cloud-ide-shared';
|
|
@@ -1268,7 +1268,7 @@ class CideLytFloatingEntitySelectionService {
|
|
|
1268
1268
|
}
|
|
1269
1269
|
try {
|
|
1270
1270
|
// Use relative import to avoid circular dependency
|
|
1271
|
-
const module = await import('./cloud-ide-layout-floating-entity-selection.component-
|
|
1271
|
+
const module = await import('./cloud-ide-layout-floating-entity-selection.component-Bmxe5h-w.mjs');
|
|
1272
1272
|
if (module.CideLytFloatingEntitySelectionComponent) {
|
|
1273
1273
|
this.containerService.registerComponent('entity-selection-header', module.CideLytFloatingEntitySelectionComponent);
|
|
1274
1274
|
console.log('✅ Entity selection component registered successfully');
|
|
@@ -1442,36 +1442,65 @@ class NotificationSettingsService {
|
|
|
1442
1442
|
// Create audio context for sound generation
|
|
1443
1443
|
// Handle browser autoplay policy - resume if suspended
|
|
1444
1444
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
1445
|
-
|
|
1445
|
+
let audioContext;
|
|
1446
|
+
// Try to get existing audio context or create new one
|
|
1447
|
+
if (!this._audioContext) {
|
|
1448
|
+
audioContext = new AudioContextClass();
|
|
1449
|
+
this._audioContext = audioContext;
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
audioContext = this._audioContext;
|
|
1453
|
+
}
|
|
1454
|
+
// Resume audio context if suspended (required for browser autoplay policy)
|
|
1446
1455
|
if (audioContext.state === 'suspended') {
|
|
1447
|
-
audioContext.resume()
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
break;
|
|
1465
|
-
case 'default':
|
|
1466
|
-
default:
|
|
1467
|
-
this.playTone(audioContext, 800, 600, 0.15, volume, 'sine');
|
|
1468
|
-
break;
|
|
1456
|
+
audioContext.resume().then(() => {
|
|
1457
|
+
this.playSoundWithContext(audioContext, settings);
|
|
1458
|
+
}).catch((error) => {
|
|
1459
|
+
console.warn('Failed to resume audio context:', error);
|
|
1460
|
+
// Try creating a new context as fallback
|
|
1461
|
+
try {
|
|
1462
|
+
const newContext = new AudioContextClass();
|
|
1463
|
+
this.playSoundWithContext(newContext, settings);
|
|
1464
|
+
}
|
|
1465
|
+
catch (fallbackError) {
|
|
1466
|
+
console.warn('Failed to create fallback audio context:', fallbackError);
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
// Context is already running, play sound immediately
|
|
1472
|
+
this.playSoundWithContext(audioContext, settings);
|
|
1469
1473
|
}
|
|
1470
1474
|
}
|
|
1471
1475
|
catch (error) {
|
|
1472
1476
|
console.warn('Failed to play notification sound:', error);
|
|
1473
1477
|
}
|
|
1474
1478
|
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Play sound with the given audio context
|
|
1481
|
+
*/
|
|
1482
|
+
playSoundWithContext(audioContext, settings) {
|
|
1483
|
+
const volume = settings.soundVolume / 100;
|
|
1484
|
+
// Configure based on sound type
|
|
1485
|
+
switch (settings.soundType) {
|
|
1486
|
+
case 'gentle':
|
|
1487
|
+
this.playTone(audioContext, 800, 600, 0.15, volume, 'sine');
|
|
1488
|
+
break;
|
|
1489
|
+
case 'alert':
|
|
1490
|
+
this.playTone(audioContext, 1000, 800, 0.2, volume, 'square');
|
|
1491
|
+
break;
|
|
1492
|
+
case 'chime':
|
|
1493
|
+
// Play a chord (C-E-G)
|
|
1494
|
+
this.playTone(audioContext, 523.25, 523.25, 0.3, volume * 0.7, 'sine', 0); // C5
|
|
1495
|
+
this.playTone(audioContext, 659.25, 659.25, 0.3, volume * 0.7, 'sine', 0.05); // E5
|
|
1496
|
+
this.playTone(audioContext, 783.99, 783.99, 0.3, volume * 0.7, 'sine', 0.1); // G5
|
|
1497
|
+
break;
|
|
1498
|
+
case 'default':
|
|
1499
|
+
default:
|
|
1500
|
+
this.playTone(audioContext, 800, 600, 0.15, volume, 'sine');
|
|
1501
|
+
break;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1475
1504
|
/**
|
|
1476
1505
|
* Play a single tone
|
|
1477
1506
|
*/
|
|
@@ -1567,6 +1596,17 @@ class NotificationSettingsComponent {
|
|
|
1567
1596
|
showPreview: [settings.showPreview],
|
|
1568
1597
|
groupNotifications: [settings.groupNotifications]
|
|
1569
1598
|
});
|
|
1599
|
+
// Update slider background on volume change
|
|
1600
|
+
this.settingsForm.get('soundVolume')?.valueChanges
|
|
1601
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1602
|
+
.subscribe((value) => {
|
|
1603
|
+
this.updateSliderBackground(value);
|
|
1604
|
+
});
|
|
1605
|
+
// Initialize slider background
|
|
1606
|
+
setTimeout(() => {
|
|
1607
|
+
const initialValue = this.settingsForm.get('soundVolume')?.value || 70;
|
|
1608
|
+
this.updateSliderBackground(initialValue);
|
|
1609
|
+
}, 0);
|
|
1570
1610
|
// Auto-save on form changes
|
|
1571
1611
|
this.settingsForm.valueChanges
|
|
1572
1612
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
@@ -1574,6 +1614,26 @@ class NotificationSettingsComponent {
|
|
|
1574
1614
|
this.save();
|
|
1575
1615
|
});
|
|
1576
1616
|
}
|
|
1617
|
+
updateSliderBackground(value) {
|
|
1618
|
+
setTimeout(() => {
|
|
1619
|
+
const slider = document.querySelector('.volume-slider');
|
|
1620
|
+
if (slider) {
|
|
1621
|
+
const percentage = value;
|
|
1622
|
+
const isDark = document.documentElement.classList.contains('dark-mode') ||
|
|
1623
|
+
document.documentElement.getAttribute('data-theme') === 'dark';
|
|
1624
|
+
if (isDark) {
|
|
1625
|
+
slider.style.background = `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${percentage}%, #374151 ${percentage}%, #374151 100%)`;
|
|
1626
|
+
}
|
|
1627
|
+
else {
|
|
1628
|
+
slider.style.background = `linear-gradient(to right, #2563eb 0%, #2563eb ${percentage}%, #e5e7eb ${percentage}%, #e5e7eb 100%)`;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}, 0);
|
|
1632
|
+
}
|
|
1633
|
+
onVolumeChange(event) {
|
|
1634
|
+
const value = parseInt(event.target.value, 10);
|
|
1635
|
+
this.updateSliderBackground(value);
|
|
1636
|
+
}
|
|
1577
1637
|
testSound() {
|
|
1578
1638
|
this.settingsService.playNotificationSound();
|
|
1579
1639
|
}
|
|
@@ -1591,7 +1651,7 @@ class NotificationSettingsComponent {
|
|
|
1591
1651
|
}
|
|
1592
1652
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1593
1653
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: NotificationSettingsComponent, isStandalone: true, selector: "cide-lyt-notification-settings", ngImport: i0, template: `
|
|
1594
|
-
<div class="tw-p-6 tw-space-y-6 tw-bg-white dark:tw-bg-gray-900">
|
|
1654
|
+
<div class="notification-settings-container tw-p-6 tw-space-y-6 tw-bg-white dark:tw-bg-gray-900 tw-text-gray-900 dark:tw-text-gray-100">
|
|
1595
1655
|
<!-- Header -->
|
|
1596
1656
|
<div class="tw-flex tw-items-center tw-gap-2 tw-pb-3 tw-border-b tw-border-gray-200 dark:tw-border-gray-700">
|
|
1597
1657
|
<div class="tw-w-8 tw-h-8 tw-bg-blue-500 tw-rounded-lg tw-flex tw-items-center tw-justify-center">
|
|
@@ -1599,7 +1659,7 @@ class NotificationSettingsComponent {
|
|
|
1599
1659
|
</div>
|
|
1600
1660
|
<div>
|
|
1601
1661
|
<h3 class="tw-m-0 tw-text-sm tw-font-semibold tw-text-gray-900 dark:tw-text-gray-100">Notification Settings</h3>
|
|
1602
|
-
<p class="tw-m-0 tw-text-xs tw-text-gray-
|
|
1662
|
+
<p class="tw-m-0 tw-text-xs tw-text-gray-600 dark:tw-text-gray-400">Customize your notification preferences</p>
|
|
1603
1663
|
</div>
|
|
1604
1664
|
</div>
|
|
1605
1665
|
|
|
@@ -1644,14 +1704,17 @@ class NotificationSettingsComponent {
|
|
|
1644
1704
|
<label class="tw-text-xs tw-font-medium tw-text-gray-700 dark:tw-text-gray-300">Volume</label>
|
|
1645
1705
|
<span class="tw-text-xs tw-text-gray-600 dark:tw-text-gray-400">{{ settingsForm.get('soundVolume')?.value }}%</span>
|
|
1646
1706
|
</div>
|
|
1647
|
-
<
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1707
|
+
<div class="volume-slider-container">
|
|
1708
|
+
<input
|
|
1709
|
+
type="range"
|
|
1710
|
+
formControlName="soundVolume"
|
|
1711
|
+
min="0"
|
|
1712
|
+
max="100"
|
|
1713
|
+
step="1"
|
|
1714
|
+
(input)="onVolumeChange($event)"
|
|
1715
|
+
class="volume-slider tw-w-full">
|
|
1716
|
+
</div>
|
|
1717
|
+
<div class="tw-flex tw-justify-between tw-text-[10px] tw-text-gray-500 dark:tw-text-gray-400 tw-mt-1">
|
|
1655
1718
|
<span>0%</span>
|
|
1656
1719
|
<span>100%</span>
|
|
1657
1720
|
</div>
|
|
@@ -1800,7 +1863,7 @@ class NotificationSettingsComponent {
|
|
|
1800
1863
|
</div>
|
|
1801
1864
|
</form>
|
|
1802
1865
|
</div>
|
|
1803
|
-
`, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton], cide-ele-button", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }, { kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "step", "size"], outputs: ["ngModelChange"] }, { kind: "component", type: CideSelectComponent, selector: "cide-ele-select", inputs: ["label", "labelHide", "placeholder", "helperText", "errorText", "required", "disabled", "id", "size", "fill", "labelPlacement", "labelDir", "leadingIcon", "trailingIcon", "clearInput", "options", "multiple", "searchable", "showSearchInput", "loading", "valueKey", "labelKey", "treeView"], outputs: ["ngModelChange", "change", "searchChange"] }] });
|
|
1866
|
+
`, isInline: true, styles: [":host{display:block}.notification-settings-container{color:#111827}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container{background-color:#fff!important;color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container *{color:inherit}.volume-slider-container{position:relative;width:100%}.volume-slider{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:3px;background:linear-gradient(to right,#e5e7eb 0% 100%);outline:none;cursor:pointer;transition:background .3s ease}:root:not([data-theme=dark]):not(.dark-mode) .volume-slider{background:linear-gradient(to right,#e5e7eb 0% 100%)}:root[data-theme=dark] .volume-slider,:root.dark-mode .volume-slider{background:linear-gradient(to right,#374151 0% 100%)}.volume-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:#2563eb;cursor:pointer;border:2px solid #ffffff;box-shadow:0 2px 4px #0003;transition:all .2s ease}.volume-slider::-webkit-slider-thumb:hover{background:#1d4ed8;transform:scale(1.1);box-shadow:0 3px 6px #0000004d}.volume-slider::-webkit-slider-thumb:active{transform:scale(1.2)}:root[data-theme=dark] .volume-slider::-webkit-slider-thumb,:root.dark-mode .volume-slider::-webkit-slider-thumb{background:#3b82f6;border-color:#1e293b}.volume-slider::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:#2563eb;cursor:pointer;border:2px solid #ffffff;box-shadow:0 2px 4px #0003;transition:all .2s ease}.volume-slider::-moz-range-thumb:hover{background:#1d4ed8;transform:scale(1.1);box-shadow:0 3px 6px #0000004d}.volume-slider::-moz-range-thumb:active{transform:scale(1.2)}:root[data-theme=dark] .volume-slider::-moz-range-thumb,:root.dark-mode .volume-slider::-moz-range-thumb{background:#3b82f6;border-color:#1e293b}.volume-slider{background-size:var(--slider-value, 0%) 100%;background-repeat:no-repeat}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-700{color:#374151!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-600{color:#4b5563!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-500{color:#6b7280!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-900{color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-bg-gray-50{background-color:#f9fafb!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton], cide-ele-button", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }, { kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "step", "size"], outputs: ["ngModelChange"] }, { kind: "component", type: CideSelectComponent, selector: "cide-ele-select", inputs: ["label", "labelHide", "placeholder", "helperText", "errorText", "required", "disabled", "id", "size", "fill", "labelPlacement", "labelDir", "leadingIcon", "trailingIcon", "clearInput", "options", "multiple", "searchable", "showSearchInput", "loading", "valueKey", "labelKey", "treeView"], outputs: ["ngModelChange", "change", "searchChange"] }] });
|
|
1804
1867
|
}
|
|
1805
1868
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NotificationSettingsComponent, decorators: [{
|
|
1806
1869
|
type: Component,
|
|
@@ -1812,7 +1875,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
1812
1875
|
CideInputComponent,
|
|
1813
1876
|
CideSelectComponent
|
|
1814
1877
|
], template: `
|
|
1815
|
-
<div class="tw-p-6 tw-space-y-6 tw-bg-white dark:tw-bg-gray-900">
|
|
1878
|
+
<div class="notification-settings-container tw-p-6 tw-space-y-6 tw-bg-white dark:tw-bg-gray-900 tw-text-gray-900 dark:tw-text-gray-100">
|
|
1816
1879
|
<!-- Header -->
|
|
1817
1880
|
<div class="tw-flex tw-items-center tw-gap-2 tw-pb-3 tw-border-b tw-border-gray-200 dark:tw-border-gray-700">
|
|
1818
1881
|
<div class="tw-w-8 tw-h-8 tw-bg-blue-500 tw-rounded-lg tw-flex tw-items-center tw-justify-center">
|
|
@@ -1820,7 +1883,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
1820
1883
|
</div>
|
|
1821
1884
|
<div>
|
|
1822
1885
|
<h3 class="tw-m-0 tw-text-sm tw-font-semibold tw-text-gray-900 dark:tw-text-gray-100">Notification Settings</h3>
|
|
1823
|
-
<p class="tw-m-0 tw-text-xs tw-text-gray-
|
|
1886
|
+
<p class="tw-m-0 tw-text-xs tw-text-gray-600 dark:tw-text-gray-400">Customize your notification preferences</p>
|
|
1824
1887
|
</div>
|
|
1825
1888
|
</div>
|
|
1826
1889
|
|
|
@@ -1865,14 +1928,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
1865
1928
|
<label class="tw-text-xs tw-font-medium tw-text-gray-700 dark:tw-text-gray-300">Volume</label>
|
|
1866
1929
|
<span class="tw-text-xs tw-text-gray-600 dark:tw-text-gray-400">{{ settingsForm.get('soundVolume')?.value }}%</span>
|
|
1867
1930
|
</div>
|
|
1868
|
-
<
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1931
|
+
<div class="volume-slider-container">
|
|
1932
|
+
<input
|
|
1933
|
+
type="range"
|
|
1934
|
+
formControlName="soundVolume"
|
|
1935
|
+
min="0"
|
|
1936
|
+
max="100"
|
|
1937
|
+
step="1"
|
|
1938
|
+
(input)="onVolumeChange($event)"
|
|
1939
|
+
class="volume-slider tw-w-full">
|
|
1940
|
+
</div>
|
|
1941
|
+
<div class="tw-flex tw-justify-between tw-text-[10px] tw-text-gray-500 dark:tw-text-gray-400 tw-mt-1">
|
|
1876
1942
|
<span>0%</span>
|
|
1877
1943
|
<span>100%</span>
|
|
1878
1944
|
</div>
|
|
@@ -2021,7 +2087,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
2021
2087
|
</div>
|
|
2022
2088
|
</form>
|
|
2023
2089
|
</div>
|
|
2024
|
-
`, styles: [":host{display:block}\n"] }]
|
|
2090
|
+
`, styles: [":host{display:block}.notification-settings-container{color:#111827}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container{background-color:#fff!important;color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container *{color:inherit}.volume-slider-container{position:relative;width:100%}.volume-slider{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:3px;background:linear-gradient(to right,#e5e7eb 0% 100%);outline:none;cursor:pointer;transition:background .3s ease}:root:not([data-theme=dark]):not(.dark-mode) .volume-slider{background:linear-gradient(to right,#e5e7eb 0% 100%)}:root[data-theme=dark] .volume-slider,:root.dark-mode .volume-slider{background:linear-gradient(to right,#374151 0% 100%)}.volume-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:#2563eb;cursor:pointer;border:2px solid #ffffff;box-shadow:0 2px 4px #0003;transition:all .2s ease}.volume-slider::-webkit-slider-thumb:hover{background:#1d4ed8;transform:scale(1.1);box-shadow:0 3px 6px #0000004d}.volume-slider::-webkit-slider-thumb:active{transform:scale(1.2)}:root[data-theme=dark] .volume-slider::-webkit-slider-thumb,:root.dark-mode .volume-slider::-webkit-slider-thumb{background:#3b82f6;border-color:#1e293b}.volume-slider::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:#2563eb;cursor:pointer;border:2px solid #ffffff;box-shadow:0 2px 4px #0003;transition:all .2s ease}.volume-slider::-moz-range-thumb:hover{background:#1d4ed8;transform:scale(1.1);box-shadow:0 3px 6px #0000004d}.volume-slider::-moz-range-thumb:active{transform:scale(1.2)}:root[data-theme=dark] .volume-slider::-moz-range-thumb,:root.dark-mode .volume-slider::-moz-range-thumb{background:#3b82f6;border-color:#1e293b}.volume-slider{background-size:var(--slider-value, 0%) 100%;background-repeat:no-repeat}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-700{color:#374151!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-600{color:#4b5563!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-500{color:#6b7280!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-text-gray-900{color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-settings-container .tw-bg-gray-50{background-color:#f9fafb!important}\n"] }]
|
|
2025
2091
|
}] });
|
|
2026
2092
|
|
|
2027
2093
|
class CideLytHeaderWrapperComponent {
|
|
@@ -2052,6 +2118,9 @@ class CideLytHeaderWrapperComponent {
|
|
|
2052
2118
|
unreadCount = computed(() => this.wsNotificationService?.unreadNotificationsCount() || 0, ...(ngDevMode ? [{ debugName: "unreadCount" }] : []));
|
|
2053
2119
|
isNotificationDropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "isNotificationDropdownOpen" }] : []));
|
|
2054
2120
|
notificationItems = signal([], ...(ngDevMode ? [{ debugName: "notificationItems" }] : []));
|
|
2121
|
+
// Track previously seen notification IDs to detect new ones
|
|
2122
|
+
previousNotificationIds = new Set();
|
|
2123
|
+
isInitialLoad = true; // Track if this is the first load
|
|
2055
2124
|
// Animation and undo state
|
|
2056
2125
|
animatingNotifications = signal(new Set(), ...(ngDevMode ? [{ debugName: "animatingNotifications" }] : []));
|
|
2057
2126
|
recentlyRemovedNotifications = signal(new Map(), ...(ngDevMode ? [{ debugName: "recentlyRemovedNotifications" }] : []));
|
|
@@ -2187,6 +2256,9 @@ class CideLytHeaderWrapperComponent {
|
|
|
2187
2256
|
});
|
|
2188
2257
|
}
|
|
2189
2258
|
ngOnInit() {
|
|
2259
|
+
// Initialize audio context early to avoid browser autoplay restrictions
|
|
2260
|
+
// This must be done on user interaction, so we'll do it on first user click
|
|
2261
|
+
this.initializeAudioContext();
|
|
2190
2262
|
// Subscribe to theme service for reactive theme updates
|
|
2191
2263
|
this.themeService.getEffectiveTheme$()
|
|
2192
2264
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
@@ -2320,6 +2392,23 @@ class CideLytHeaderWrapperComponent {
|
|
|
2320
2392
|
}
|
|
2321
2393
|
// Sort by timestamp (newest first)
|
|
2322
2394
|
filteredNotifications.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
2395
|
+
// Detect new notifications and play sound
|
|
2396
|
+
if (!this.isInitialLoad) {
|
|
2397
|
+
const currentNotificationIds = new Set(filteredNotifications.map(n => n.id));
|
|
2398
|
+
const newNotifications = filteredNotifications.filter(n => !this.previousNotificationIds.has(n.id));
|
|
2399
|
+
if (newNotifications.length > 0) {
|
|
2400
|
+
console.log(`[Notifications] ${newNotifications.length} new notification(s) detected, playing sound...`);
|
|
2401
|
+
// Play sound for new notifications
|
|
2402
|
+
this.playNotificationSound();
|
|
2403
|
+
}
|
|
2404
|
+
// Update previous notification IDs
|
|
2405
|
+
this.previousNotificationIds = currentNotificationIds;
|
|
2406
|
+
}
|
|
2407
|
+
else {
|
|
2408
|
+
// First load - just track IDs, don't play sound
|
|
2409
|
+
this.previousNotificationIds = new Set(filteredNotifications.map(n => n.id));
|
|
2410
|
+
this.isInitialLoad = false;
|
|
2411
|
+
}
|
|
2323
2412
|
console.log('[Notifications] Total notifications to display:', filteredNotifications.length);
|
|
2324
2413
|
this.notifications.set(filteredNotifications);
|
|
2325
2414
|
this.updateNotificationDropdown();
|
|
@@ -3245,6 +3334,44 @@ class CideLytHeaderWrapperComponent {
|
|
|
3245
3334
|
}
|
|
3246
3335
|
}
|
|
3247
3336
|
}
|
|
3337
|
+
/**
|
|
3338
|
+
* Initialize audio context early to avoid browser autoplay restrictions
|
|
3339
|
+
*/
|
|
3340
|
+
initializeAudioContext() {
|
|
3341
|
+
// Initialize audio context on first user interaction
|
|
3342
|
+
const initAudio = () => {
|
|
3343
|
+
try {
|
|
3344
|
+
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
3345
|
+
if (AudioContextClass && !window._notificationAudioContext) {
|
|
3346
|
+
const audioContext = new AudioContextClass();
|
|
3347
|
+
// Resume to unlock audio (required for autoplay policy)
|
|
3348
|
+
if (audioContext.state === 'suspended') {
|
|
3349
|
+
audioContext.resume().then(() => {
|
|
3350
|
+
console.log('[Notifications] Audio context initialized and resumed');
|
|
3351
|
+
}).catch((error) => {
|
|
3352
|
+
console.warn('[Notifications] Failed to initialize audio context:', error);
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
window._notificationAudioContext = audioContext;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
catch (error) {
|
|
3359
|
+
console.warn('[Notifications] Failed to initialize audio context:', error);
|
|
3360
|
+
}
|
|
3361
|
+
};
|
|
3362
|
+
// Try to initialize on any user interaction
|
|
3363
|
+
const events = ['click', 'touchstart', 'keydown'];
|
|
3364
|
+
const initOnce = () => {
|
|
3365
|
+
initAudio();
|
|
3366
|
+
// Remove listeners after first initialization
|
|
3367
|
+
events.forEach(event => {
|
|
3368
|
+
document.removeEventListener(event, initOnce);
|
|
3369
|
+
});
|
|
3370
|
+
};
|
|
3371
|
+
events.forEach(event => {
|
|
3372
|
+
document.addEventListener(event, initOnce, { once: true, passive: true });
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3248
3375
|
/**
|
|
3249
3376
|
* Play notification sound based on user settings
|
|
3250
3377
|
*/
|
|
@@ -3286,12 +3413,12 @@ class CideLytHeaderWrapperComponent {
|
|
|
3286
3413
|
this.floatingContainerService.bringToFront(containerId);
|
|
3287
3414
|
}
|
|
3288
3415
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytHeaderWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3289
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideLytHeaderWrapperComponent, isStandalone: true, selector: "cide-lyt-header-wrapper", viewQueries: [{ propertyName: "triggerTemplate", first: true, predicate: ["triggerTemplate"], descendants: true }, { propertyName: "financialYearTriggerTemplate", first: true, predicate: ["financialYearTriggerTemplate"], descendants: true }, { propertyName: "academicYearTriggerTemplate", first: true, predicate: ["academicYearTriggerTemplate"], descendants: true }, { propertyName: "notificationTriggerTemplate", first: true, predicate: ["notificationTriggerTemplate"], descendants: true }, { propertyName: "notificationDropdown", first: true, predicate: ["notificationDropdown"], descendants: true }], ngImport: i0, template: "<header id=\"cide-lyt-header-wrapper\" class=\"cide-lyt-header tw-w-full tw-select-none cide-lyt-header-wrapper-hide\">\n <!-- Logo Section -->\n <div class=\"tw-flex tw-items-center tw-gap-3\">\n <div class=\"header-logo-container tw-flex tw-items-center tw-gap-3 tw-cursor-pointer\" (click)=\"onLogoClick()\"\n (keydown.enter)=\"onLogoClick()\" (keydown.space)=\"onLogoClick()\" tabindex=\"0\" role=\"button\"\n aria-label=\"Navigate to home\" title=\"Click to go to control panel home\">\n @if (appStateService.activeEntity()?.syen_photo_id_cyfm) {\n <img cideEleFileImage [fileId]=\"(appStateService.activeEntity()?.syen_photo_id_cyfm || '')\"\n [altText]=\"'Entity Logo'\" class=\"tw-w-8 tw-h-8 tw-object-contain\">\n } @else {\n <cide-ele-icon name=\"business\" class=\"tw-w-8 tw-h-8 tw-text-blue-600\"></cide-ele-icon>\n }\n\n </div>\n @if (appStateService.activeEntity()?.syen_name) {\n <span\n class=\"tw-text-md tw-font-semibold tw-text-blue-600 hover:tw-text-blue-800 tw-cursor-pointer sm:block tw-transition-colors tw-duration-200 hover:tw-underline\"\n (click)=\"onEntityNameClick()\" title=\"Click to switch entity\">\n {{ appStateService.activeEntity()?.syen_name }}\n </span>\n }\n </div>\n <!-- Search Section -->\n <div class=\"header-search-container\">\n <cide-ele-input id=\"cide_lyt_header_search\" placeholder=\"Search...\" leadingIcon=\"search\"\n size=\"md\"></cide-ele-input>\n </div>\n\n <!-- Icons Section -->\n <div class=\"header-icons-container\">\n <!-- Financial Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"financialYearItems()\" [config]=\"financialYearConfig\"\n [triggerTemplate]=\"financialYearTriggerTemplate\"\n (itemClick)=\"onFinancialYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Financial Year</div>\n </div>\n \n <ng-template #financialYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">calendar_today</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentFinancialYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Academic Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"academicYearItems()\" [config]=\"academicYearConfig\"\n [triggerTemplate]=\"academicYearTriggerTemplate\"\n (itemClick)=\"onAcademicYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Academic Year</div>\n </div>\n \n <ng-template #academicYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">school</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentAcademicYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Notifications Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown \n #notificationDropdown\n [items]=\"notificationItems()\" \n [config]=\"notificationConfig\"\n [triggerTemplate]=\"notificationTriggerTemplate\"\n [menuTemplate]=\"notificationMenuTemplate\"\n (itemClick)=\"onNotificationClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Notifications</div>\n </div>\n \n <ng-template #notificationTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-icon notification-icon\" [class.active]=\"isOpen\">\n <cide-ele-icon>notifications</cide-ele-icon>\n @if (unreadCount() > 0) {\n <div class=\"header-badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</div>\n }\n </div>\n </ng-template>\n\n <!-- Custom Notification Menu Template -->\n <ng-template #notificationMenuTemplate let-items=\"items\">\n <div class=\"tw-min-w-[380px] tw-bg-white dark:tw-bg-gray-800 tw-rounded-xl tw-shadow-2xl tw-overflow-hidden\">\n <!-- Header - Fixed/Sticky -->\n <div class=\"tw-sticky tw-top-0 tw-z-10 tw-px-2.5 tw-py-1 tw-bg-gradient-to-r tw-from-white dark:tw-from-gray-800 tw-to-gray-50 dark:tw-to-gray-700 tw-flex tw-justify-between tw-items-center tw-border-b tw-border-gray-200 dark:tw-border-gray-700 tw-backdrop-blur-sm\">\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <div class=\"tw-w-5 tw-h-5 tw-bg-blue-500 tw-rounded tw-flex tw-items-center tw-justify-center tw-shadow-sm\">\n <cide-ele-icon class=\"!tw-text-[10px] !tw-text-white\">notifications</cide-ele-icon>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <h3 class=\"tw-m-0 tw-text-[10px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-none\">Notifications</h3>\n @if (unreadCount() > 0) {\n <span class=\"tw-bg-red-500 tw-text-white tw-px-1 tw-py-0 tw-rounded-full tw-text-[7px] tw-font-bold tw-leading-none tw-animate-pulse\">{{ unreadCount() }}</span>\n }\n </div>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-0.5\">\n <button \n type=\"button\"\n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-400 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 hover:tw-text-gray-700 dark:hover:tw-text-gray-200\"\n (click)=\"openNotificationSettings(); $event.stopPropagation()\"\n title=\"Notification Settings\">\n <cide-ele-icon class=\"!tw-text-xs\">settings</cide-ele-icon>\n </button>\n @if (unreadCount() > 0) {\n <button \n type=\"button\"\n class=\"tw-bg-blue-500 tw-text-white tw-px-1 tw-py-0.5 tw-rounded tw-text-[8px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex tw-items-center tw-gap-0.5 tw-shadow-sm hover:tw-bg-blue-600 hover:tw-scale-105\"\n (click)=\"markAllAsRead(); $event.stopPropagation()\"\n title=\"Mark all as read\">\n <cide-ele-icon class=\"!tw-text-[9px]\">done_all</cide-ele-icon>\n </button>\n }\n <button \n type=\"button\" \n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-400 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 hover:tw-text-gray-700 dark:hover:tw-text-gray-200 hover:tw-rotate-90\"\n (click)=\"closeNotificationDropdown($event)\"\n title=\"Close\">\n <cide-ele-icon class=\"!tw-text-xs\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n\n <!-- Notifications List -->\n <div class=\"tw-overflow-x-hidden notification-scroll-container tw-bg-white dark:tw-bg-gray-800\">\n @if (notifications().length === 0) {\n <div class=\"tw-py-16 tw-px-4 tw-text-center tw-bg-white dark:tw-bg-gray-800\">\n <cide-ele-icon class=\"!tw-text-6xl !tw-text-gray-200 dark:!tw-text-gray-600 tw-mb-3\">notifications_off</cide-ele-icon>\n <p class=\"tw-m-0 tw-text-sm tw-text-gray-500 dark:tw-text-gray-400 tw-font-medium\">No notifications yet</p>\n <p class=\"tw-m-0 tw-mt-1 tw-text-xs tw-text-gray-400 dark:tw-text-gray-500\">You're all caught up!</p>\n </div>\n } @else {\n @for (notif of notifications().slice(0, 15); track notif.id) {\n <div \n class=\"tw-flex tw-items-start tw-px-3 tw-py-2 tw-cursor-pointer tw-transition-all tw-duration-300 tw-relative tw-gap-2 tw-border-b tw-border-gray-200 dark:tw-border-gray-700 last:tw-border-b-0 hover:tw-bg-gray-50 dark:hover:tw-bg-gray-700 tw-transform tw-ease-in-out tw-bg-white dark:tw-bg-gray-800\"\n [class.tw-border-l-2]=\"!isNotificationRead(notif)\"\n [class.tw-border-l-blue-500]=\"!isNotificationRead(notif)\"\n [class.dark:tw-border-l-blue-400]=\"!isNotificationRead(notif)\"\n [class.tw-bg-blue-50]=\"!isNotificationRead(notif)\"\n [class.dark:tw-bg-gray-700]=\"!isNotificationRead(notif)\"\n [class.-tw-translate-x-full]=\"isNotificationAnimating(notif)\"\n [class.tw-opacity-0]=\"isNotificationAnimating(notif)\"\n [class.tw-max-h-0]=\"isNotificationAnimating(notif)\"\n [class.tw-overflow-hidden]=\"isNotificationAnimating(notif)\"\n [class.tw-mb-0]=\"isNotificationAnimating(notif)\"\n [class.tw-p-0]=\"isNotificationAnimating(notif)\"\n (click)=\"onNotificationItemClick(notif)\">\n <!-- Icon/Avatar -->\n <div class=\"tw-flex-shrink-0 tw-relative\">\n <div class=\"tw-w-9 tw-h-9 tw-rounded-full tw-overflow-hidden tw-bg-gradient-to-br tw-from-blue-500 tw-to-blue-600 tw-flex tw-items-center tw-justify-center tw-shadow-sm tw-ring-1 tw-ring-white\">\n @if (getNotificationAvatar(notif)) {\n <img \n cideEleFileImage \n [fileId]=\"getNotificationAvatar(notif) || ''\"\n [altText]=\"getNotificationName(notif)\"\n class=\"tw-w-full tw-h-full tw-object-cover\">\n } @else {\n <cide-ele-icon class=\"!tw-text-base !tw-text-white\">{{ getNotificationIcon(notif.type) }}</cide-ele-icon>\n }\n </div>\n <!-- Green online indicator -->\n <div class=\"tw-absolute tw-bottom-0 tw-right-0 tw-w-2 tw-h-2 tw-bg-green-500 tw-rounded-full tw-border tw-border-white\"></div>\n </div>\n\n <!-- Content -->\n <div class=\"tw-flex-1 tw-min-w-0\">\n <!-- Title and relative time -->\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-1.5 tw-mb-0.5\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <h4 class=\"tw-m-0 tw-text-[11px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-tight\">\n <span class=\"tw-text-blue-600 dark:tw-text-blue-400\">{{ getNotificationName(notif) }}</span><span class=\"tw-font-normal tw-text-gray-700 dark:tw-text-gray-300\"> {{ getNotificationAction(notif) }}</span>\n </h4>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-400 dark:tw-text-gray-500 tw-mt-0.5\">{{ getTimeAgo(notif.timestamp) }}</p>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1 tw-flex-shrink-0\">\n @if (!isNotificationRead(notif)) {\n <div class=\"tw-w-1.5 tw-h-1.5 tw-rounded-full tw-bg-blue-500 dark:tw-bg-blue-400 tw-animate-pulse\"></div>\n }\n @if (isNotificationAnimating(notif) && isNotificationUndoable(notif)) {\n <button \n type=\"button\"\n (click)=\"undoNotificationRemoval(getNotificationId(notif)); $event.stopPropagation()\"\n class=\"tw-px-2 tw-py-0.5 tw-text-[9px] tw-font-semibold tw-text-blue-600 dark:tw-text-blue-400 tw-bg-blue-50 dark:tw-bg-blue-900 tw-border tw-border-blue-200 dark:tw-border-blue-700 tw-rounded-md tw-cursor-pointer tw-transition-all tw-duration-200 hover:tw-bg-blue-100 dark:hover:tw-bg-blue-800 hover:tw-border-blue-300 dark:hover:tw-border-blue-600 hover:tw-scale-105 tw-whitespace-nowrap tw-animate-pulse\">\n Undo\n </button>\n }\n </div>\n </div>\n \n <!-- Message -->\n @if (notif.message && notif.message !== notif.title) {\n <p class=\"tw-m-0 tw-mt-1 tw-text-[10px] tw-text-gray-600 dark:tw-text-gray-400 tw-leading-snug tw-line-clamp-2\">\n {{ notif.message }}\n </p>\n }\n \n <!-- Comment -->\n @if (notif.data?.comment) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-50 dark:tw-bg-gray-700 tw-rounded-md tw-border-l-2 tw-border-l-blue-400 dark:tw-border-l-blue-500\">\n <p class=\"tw-m-0 tw-text-[10px] tw-text-gray-700 dark:tw-text-gray-300 tw-leading-snug tw-italic tw-line-clamp-2\">{{ notif.data.comment }}</p>\n </div>\n }\n \n <!-- File -->\n @if (notif.data?.file) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-50 dark:tw-bg-gray-700 tw-border tw-border-gray-200 dark:tw-border-gray-600 tw-rounded-md tw-flex tw-items-center tw-gap-2\">\n <div class=\"tw-w-7 tw-h-7 tw-bg-blue-100 dark:tw-bg-blue-900 tw-rounded-md tw-flex tw-items-center tw-justify-center tw-flex-shrink-0\">\n <span class=\"tw-text-[9px] tw-font-bold tw-text-blue-600 dark:tw-text-blue-300\">{{ getFileIcon(notif.data.file.type) }}</span>\n </div>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <p class=\"tw-m-0 tw-text-[10px] tw-font-semibold tw-text-gray-900 dark:tw-text-gray-100 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap\">{{ notif.data.file.name }}</p>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-500 dark:tw-text-gray-400\">{{ notif.data.file.size }}</p>\n </div>\n <cide-ele-icon class=\"!tw-text-sm tw-text-blue-500 dark:tw-text-blue-400 tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex-shrink-0 hover:tw-text-blue-600 dark:hover:tw-text-blue-300 hover:tw-scale-110\">download</cide-ele-icon>\n </div>\n }\n \n <!-- Action Buttons -->\n @if (notif.action_label) {\n <div class=\"tw-mt-1.5 tw-flex tw-gap-1.5\">\n @if (notif.action_label.toLowerCase().includes('decline')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-gray-300 dark:tw-border-gray-600 tw-bg-white dark:tw-bg-gray-800 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-gray-700 dark:tw-text-gray-300 hover:tw-bg-gray-50 dark:hover:tw-bg-gray-700 hover:tw-border-gray-400 dark:hover:tw-border-gray-500 hover:tw-scale-105\">Decline</button>\n }\n @if (notif.action_label.toLowerCase().includes('accept')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-blue-600 dark:tw-border-blue-500 tw-bg-blue-600 dark:tw-bg-blue-500 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-white hover:tw-bg-blue-700 dark:hover:tw-bg-blue-600 hover:tw-border-blue-700 dark:hover:tw-border-blue-600 hover:tw-scale-105\">Accept</button>\n }\n </div>\n }\n </div>\n </div>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n <div class=\"header-divider\"></div>\n\n <!-- Profile with Dropdown -->\n <div class=\"header-icon user-profile\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"profileItems\" [config]=\"profileConfig\"\n [triggerTemplate]=\"triggerTemplate\"\n (itemClick)=\"onProfileClick($event)\">\n <ng-template #triggerTemplate>\n @if (appStateService.currentUser()?.user_photo_id_cyfm) {\n <div class=\"profile-avatar\">\n <img cideEleFileImage [fileId]=\"(appStateService.currentUser()?.user_photo_id_cyfm || '')\"\n [altText]=\"'User Profile Photo'\" class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-full\">\n </div>\n } @else {\n <div class=\"profile-avatar\">\n <cide-ele-icon name=\"person\" class=\"tw-w-6 tw-h-6 tw-text-white\"></cide-ele-icon>\n </div>\n }\n </ng-template>\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">My Account</div>\n </div>\n </div>\n</header>", styles: [".cide-lyt-header{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(to right,rgb(var(--tw-white-rgb) / .95),rgb(var(--tw-gray-50-rgb) / .95));box-shadow:0 2px 8px #00000008;padding:0 1rem;position:relative;z-index:20;transition:all .3s cubic-bezier(.4,0,.2,1);will-change:transform;border-bottom:1px solid rgb(var(--tw-gray-200-rgb) / .8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-logo-container{height:100%;display:flex;align-items:center;padding:.5rem 0;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);border-radius:8px;outline:none}.header-logo-container img{height:30px;max-height:100%;transition:all .3s ease;border-radius:5px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.header-logo-container:hover img{transform:scale(1.03);filter:brightness(1.05);box-shadow:0 2px 6px #00000014}.header-logo-container:after{content:\"\";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(to bottom right,rgb(var(--tw-white-rgb) / 0),rgb(var(--tw-white-rgb) / .3),rgb(var(--tw-white-rgb) / 0));transform:rotate(30deg);opacity:0;transition:transform .6s ease,opacity .6s ease;pointer-events:none}.header-logo-container:hover:after,.header-logo-container:focus:after{opacity:1;transform:rotate(30deg) translate(50%,50%)}.header-search-container{flex-grow:1;max-width:600px;margin:0 2rem;position:relative;transition:all .3s ease}::ng-deep .header-search-container #cide_lyt_header_search{width:100%;background-color:rgb(var(--tw-gray-50-rgb) / .8);border-radius:20px!important;transition:all .3s ease;overflow:visible;transform:translateZ(0)}::ng-deep .header-search-container #cide_lyt_header_search:hover{box-shadow:0 3px 12px #00000014;background-color:rgb(var(--tw-white-rgb) / 1);transform:translateY(-1px)}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-input{background-color:transparent;font-size:.85rem!important;letter-spacing:.01em}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:#6b7280b3!important;font-size:1.1rem!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within{transform:translateY(-1px) scale(1.01)}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-input{border-color:#3b82f6!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-leading-icon{color:#3b82f6!important}.header-icons-container{display:flex;align-items:center;gap:1rem}.header-icon{position:relative;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-dropdown-container{position:relative;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-icon:before{content:\"\";position:absolute;inset:0;background-color:#3b82f61a;border-radius:.5rem;opacity:0;transform:scale(.8);transition:all .2s cubic-bezier(.4,0,.2,1)}.header-icon:hover:before{opacity:1;transform:scale(1)}.header-icon:hover{color:#3b82f6}.header-icon:active{transform:scale(.95)}.header-tooltip{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);background-color:rgb(var(--tw-gray-700-rgb) / .9);color:rgb(var(--tw-white-rgb) / 1);padding:.25rem .6rem;border-radius:.25rem;font-size:.7rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 5px #0003;letter-spacing:.01em;will-change:transform,opacity}.header-tooltip:before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:transparent transparent rgba(55,65,81,.9) transparent}.header-icon:hover .header-tooltip{opacity:1;transform:translate(-50%) translateY(0)}.header-badge{position:absolute;top:0;right:0;min-width:16px;height:16px;border-radius:8px;background-color:#ef4444;color:rgb(var(--tw-white-rgb) / 1);font-size:9px;display:flex;align-items:center;justify-content:center;padding:0 4px;box-shadow:0 1px 3px #ef44444d;font-weight:600;z-index:2;transition:all .2s ease}.header-icon:hover .header-badge{transform:scale(1.1)}.header-divider{height:20px;width:1px;background-color:#e5e7ebcc;margin:0 6px}.header-year-dropdown-wrapper{position:relative;display:flex;align-items:center;justify-content:center}.header-year-pill{position:relative;display:flex;align-items:center;padding:.25rem .625rem;background:linear-gradient(135deg,#3b82f61a,#2563eb26);border:1px solid rgba(59,130,246,.3);border-radius:9999px;color:#2563eb;font-size:.6875rem;font-weight:600;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;white-space:nowrap;box-shadow:0 1px 2px #3b82f61a;min-height:22px;height:22px;line-height:1}.header-year-pill:hover{background:linear-gradient(135deg,#3b82f626,#2563eb33);border-color:#3b82f666;transform:translateY(-1px);box-shadow:0 2px 6px #3b82f626}.header-year-pill-text{max-width:180px;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em;line-height:1;display:inline-block}::ng-deep .header-dropdown-container .dropdown-trigger{background:transparent!important;border:none!important;border-radius:0!important;padding:0!important;width:100%!important;height:100%!important;min-width:auto!important;box-shadow:none!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:none!important;cursor:pointer!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover{background:transparent!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover .header-year-pill{background:linear-gradient(135deg,#3b82f626,#2563eb33)!important;border-color:#3b82f666!important;transform:translateY(-1px)!important;box-shadow:0 2px 6px #3b82f626!important}::ng-deep .header-dropdown-container .dropdown-trigger:focus,::ng-deep .header-dropdown-container .dropdown-trigger:focus-visible,::ng-deep .header-dropdown-container .dropdown-trigger:active{outline:none!important;box-shadow:0 2px 6px #3b82f626!important}.profile-avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);color:rgb(var(--tw-white-rgb) / 1);font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #2563eb33;transition:all .2s cubic-bezier(.4,0,.2,1);letter-spacing:-.5px;cursor:pointer;border:2px solid transparent}.profile-avatar:hover,.header-icon:hover .profile-avatar{transform:scale(1.08);box-shadow:0 3px 8px #2563eb4d;border-color:#3b82f64d}::ng-deep .user-profile .dropdown-trigger{background:transparent!important;border:none!important;padding:0!important;width:auto!important;height:auto!important;border-radius:0!important}::ng-deep .user-profile .dropdown-trigger:hover{background:transparent!important}::ng-deep .user-profile .dropdown-trigger:focus,::ng-deep .user-profile .dropdown-trigger:focus-visible,::ng-deep .user-profile .dropdown-trigger:active{outline:none!important;box-shadow:none!important}:root[data-theme=dark] .cide-lyt-header,:root.dark-mode .cide-lyt-header{background:linear-gradient(to right,var(--cide-theme-light-color),var(--cide-theme-hover-bg-color));border-bottom-color:var(--cide-theme-border-color);box-shadow:0 2px 8px var(--cide-theme-shadow-color)}:root[data-theme=dark] .header-icon,:root.dark-mode .header-icon{color:var(--cide-theme-text-color)}:root[data-theme=dark] .header-icon.notification-icon,:root.dark-mode .header-icon.notification-icon{color:#d1d5db}:root[data-theme=dark] .header-icon.notification-icon:hover,:root.dark-mode .header-icon.notification-icon:hover{color:#60a5fa}:root[data-theme=dark] .header-divider,:root.dark-mode .header-divider{background-color:var(--cide-theme-border-color)}:root[data-theme=dark] .header-year-pill,:root.dark-mode .header-year-pill{background:linear-gradient(135deg,#60a5fa33,#3b82f640);border-color:#60a5fa80;color:#60a5fa}:root[data-theme=dark] .header-year-pill .header-year-pill-text,:root.dark-mode .header-year-pill .header-year-pill-text{color:#60a5fa}:root[data-theme=dark] .header-year-pill cide-ele-icon,:root.dark-mode .header-year-pill cide-ele-icon{color:#60a5fa!important}:root[data-theme=dark] .header-year-pill:hover,:root.dark-mode .header-year-pill:hover{background:linear-gradient(135deg,#60a5fa4d,#3b82f659);border-color:#60a5fa99;color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover .header-year-pill-text,:root.dark-mode .header-year-pill:hover .header-year-pill-text{color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover cide-ele-icon,:root.dark-mode .header-year-pill:hover cide-ele-icon{color:#93c5fd!important}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search{background-color:var(--cide-theme-hover-bg-color);border-color:var(--cide-theme-border-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search:hover,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search:hover{background-color:var(--cide-theme-light-color);box-shadow:0 3px 12px var(--cide-theme-shadow-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:var(--cide-theme-label-color)!important}.header-avatar{width:36px;height:36px;border-radius:50%;overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1);border:2px solid transparent;box-shadow:0 2px 4px #0000001a}.header-avatar:hover{border-color:#3b82f6;transform:scale(1.05);box-shadow:0 3px 6px #3b82f64d}@media (max-width: 768px){.header-search-container{margin:0 1rem}.header-icons-container{gap:.5rem}}@media (max-width: 640px){.header-search-container{max-width:200px;margin:0 .5rem}}.notification-scroll-container{scrollbar-width:thin;scrollbar-color:var(--cide-ele-scrollbar-thumb, #d1d5db) var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar{width:6px;height:6px}.notification-scroll-container::-webkit-scrollbar-track{background:var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar-thumb{background-color:var(--cide-ele-scrollbar-thumb, #d1d5db);border-radius:3px}.notification-scroll-container::-webkit-scrollbar-thumb:hover{background-color:var(--cide-ele-scrollbar-thumb-hover, #9ca3af)}\n"], dependencies: [{ kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "step", "size"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleDropdownComponent, selector: "cide-ele-dropdown", inputs: ["items", "config", "triggerTemplate", "menuTemplate"], outputs: ["itemClick", "dropdownToggle"] }, { kind: "directive", type: CideEleFileImageDirective, selector: "[cideEleFileImage]", inputs: ["fileId", "altText"] }] });
|
|
3416
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideLytHeaderWrapperComponent, isStandalone: true, selector: "cide-lyt-header-wrapper", viewQueries: [{ propertyName: "triggerTemplate", first: true, predicate: ["triggerTemplate"], descendants: true }, { propertyName: "financialYearTriggerTemplate", first: true, predicate: ["financialYearTriggerTemplate"], descendants: true }, { propertyName: "academicYearTriggerTemplate", first: true, predicate: ["academicYearTriggerTemplate"], descendants: true }, { propertyName: "notificationTriggerTemplate", first: true, predicate: ["notificationTriggerTemplate"], descendants: true }, { propertyName: "notificationDropdown", first: true, predicate: ["notificationDropdown"], descendants: true }], ngImport: i0, template: "<header id=\"cide-lyt-header-wrapper\" class=\"cide-lyt-header tw-w-full tw-select-none cide-lyt-header-wrapper-hide\">\n <!-- Logo Section -->\n <div class=\"tw-flex tw-items-center tw-gap-3\">\n <div class=\"header-logo-container tw-flex tw-items-center tw-gap-3 tw-cursor-pointer\" (click)=\"onLogoClick()\"\n (keydown.enter)=\"onLogoClick()\" (keydown.space)=\"onLogoClick()\" tabindex=\"0\" role=\"button\"\n aria-label=\"Navigate to home\" title=\"Click to go to control panel home\">\n @if (appStateService.activeEntity()?.syen_photo_id_cyfm) {\n <img cideEleFileImage [fileId]=\"(appStateService.activeEntity()?.syen_photo_id_cyfm || '')\"\n [altText]=\"'Entity Logo'\" class=\"tw-w-8 tw-h-8 tw-object-contain\">\n } @else {\n <cide-ele-icon name=\"business\" class=\"tw-w-8 tw-h-8 tw-text-blue-600\"></cide-ele-icon>\n }\n\n </div>\n @if (appStateService.activeEntity()?.syen_name) {\n <span\n class=\"tw-text-md tw-font-semibold tw-text-blue-600 hover:tw-text-blue-800 tw-cursor-pointer sm:block tw-transition-colors tw-duration-200 hover:tw-underline\"\n (click)=\"onEntityNameClick()\" title=\"Click to switch entity\">\n {{ appStateService.activeEntity()?.syen_name }}\n </span>\n }\n </div>\n <!-- Search Section -->\n <div class=\"header-search-container\">\n <cide-ele-input id=\"cide_lyt_header_search\" placeholder=\"Search...\" leadingIcon=\"search\"\n size=\"md\"></cide-ele-input>\n </div>\n\n <!-- Icons Section -->\n <div class=\"header-icons-container\">\n <!-- Financial Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"financialYearItems()\" [config]=\"financialYearConfig\"\n [triggerTemplate]=\"financialYearTriggerTemplate\"\n (itemClick)=\"onFinancialYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Financial Year</div>\n </div>\n \n <ng-template #financialYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">calendar_today</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentFinancialYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Academic Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"academicYearItems()\" [config]=\"academicYearConfig\"\n [triggerTemplate]=\"academicYearTriggerTemplate\"\n (itemClick)=\"onAcademicYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Academic Year</div>\n </div>\n \n <ng-template #academicYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">school</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentAcademicYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Notifications Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown \n #notificationDropdown\n [items]=\"notificationItems()\" \n [config]=\"notificationConfig\"\n [triggerTemplate]=\"notificationTriggerTemplate\"\n [menuTemplate]=\"notificationMenuTemplate\"\n (itemClick)=\"onNotificationClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Notifications</div>\n </div>\n \n <ng-template #notificationTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-icon notification-icon\" [class.active]=\"isOpen\">\n <cide-ele-icon>notifications</cide-ele-icon>\n @if (unreadCount() > 0) {\n <div class=\"header-badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</div>\n }\n </div>\n </ng-template>\n\n <!-- Custom Notification Menu Template -->\n <ng-template #notificationMenuTemplate let-items=\"items\">\n <div class=\"tw-min-w-[380px] tw-bg-white dark:tw-bg-gray-800 tw-rounded-xl tw-shadow-2xl tw-overflow-hidden tw-text-gray-900 dark:tw-text-gray-100\">\n <!-- Header - Fixed/Sticky -->\n <div class=\"tw-sticky tw-top-0 tw-z-10 tw-px-2.5 tw-py-1 tw-bg-white dark:tw-bg-gray-800 tw-flex tw-justify-between tw-items-center tw-border-b tw-border-gray-200 dark:tw-border-gray-700 tw-backdrop-blur-sm\">\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <div class=\"tw-w-5 tw-h-5 tw-bg-blue-500 tw-rounded tw-flex tw-items-center tw-justify-center tw-shadow-sm\">\n <cide-ele-icon class=\"!tw-text-[10px] !tw-text-white\">notifications</cide-ele-icon>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <h3 class=\"tw-m-0 tw-text-[10px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-none\">Notifications</h3>\n @if (unreadCount() > 0) {\n <span class=\"tw-bg-red-500 tw-text-white tw-px-1 tw-py-0 tw-rounded-full tw-text-[7px] tw-font-bold tw-leading-none tw-animate-pulse\">{{ unreadCount() }}</span>\n }\n </div>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-0.5\">\n <button \n type=\"button\"\n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-600 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-100 dark:hover:tw-bg-gray-600 hover:tw-text-gray-900 dark:hover:tw-text-gray-200\"\n (click)=\"openNotificationSettings(); $event.stopPropagation()\"\n title=\"Notification Settings\">\n <cide-ele-icon class=\"!tw-text-xs\">settings</cide-ele-icon>\n </button>\n @if (unreadCount() > 0) {\n <button \n type=\"button\"\n class=\"tw-bg-blue-500 tw-text-white tw-px-1 tw-py-0.5 tw-rounded tw-text-[8px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex tw-items-center tw-gap-0.5 tw-shadow-sm hover:tw-bg-blue-600 hover:tw-scale-105\"\n (click)=\"markAllAsRead(); $event.stopPropagation()\"\n title=\"Mark all as read\">\n <cide-ele-icon class=\"!tw-text-[9px]\">done_all</cide-ele-icon>\n </button>\n }\n <button \n type=\"button\" \n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-600 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-100 dark:hover:tw-bg-gray-600 hover:tw-text-gray-900 dark:hover:tw-text-gray-200 hover:tw-rotate-90\"\n (click)=\"closeNotificationDropdown($event)\"\n title=\"Close\">\n <cide-ele-icon class=\"!tw-text-xs\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n\n <!-- Notifications List -->\n <div class=\"tw-overflow-x-hidden notification-scroll-container tw-bg-white dark:tw-bg-gray-800 tw-text-gray-900 dark:tw-text-gray-100\">\n @if (notifications().length === 0) {\n <div class=\"tw-py-16 tw-px-4 tw-text-center tw-bg-white dark:tw-bg-gray-800\">\n <cide-ele-icon class=\"!tw-text-6xl !tw-text-gray-300 dark:!tw-text-gray-600 tw-mb-3\">notifications_off</cide-ele-icon>\n <p class=\"tw-m-0 tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-font-medium\">No notifications yet</p>\n <p class=\"tw-m-0 tw-mt-1 tw-text-xs tw-text-gray-500 dark:tw-text-gray-500\">You're all caught up!</p>\n </div>\n } @else {\n @for (notif of notifications().slice(0, 15); track notif.id) {\n <div \n class=\"tw-flex tw-items-start tw-px-3 tw-py-2 tw-cursor-pointer tw-transition-all tw-duration-300 tw-relative tw-gap-2 tw-border-b tw-border-gray-200 dark:tw-border-gray-700 last:tw-border-b-0 hover:tw-bg-gray-50 dark:hover:tw-bg-gray-700 tw-transform tw-ease-in-out tw-bg-white dark:tw-bg-gray-800 tw-text-gray-900 dark:tw-text-gray-100\"\n [class.tw-border-l-2]=\"!isNotificationRead(notif)\"\n [class.tw-border-l-blue-500]=\"!isNotificationRead(notif)\"\n [class.dark:tw-border-l-blue-400]=\"!isNotificationRead(notif)\"\n [class.tw-bg-blue-50]=\"!isNotificationRead(notif)\"\n [class.dark:tw-bg-gray-700]=\"!isNotificationRead(notif)\"\n [class.-tw-translate-x-full]=\"isNotificationAnimating(notif)\"\n [class.tw-opacity-0]=\"isNotificationAnimating(notif)\"\n [class.tw-max-h-0]=\"isNotificationAnimating(notif)\"\n [class.tw-overflow-hidden]=\"isNotificationAnimating(notif)\"\n [class.tw-mb-0]=\"isNotificationAnimating(notif)\"\n [class.tw-p-0]=\"isNotificationAnimating(notif)\"\n (click)=\"onNotificationItemClick(notif)\">\n <!-- Icon/Avatar -->\n <div class=\"tw-flex-shrink-0 tw-relative\">\n <div class=\"tw-w-9 tw-h-9 tw-rounded-full tw-overflow-hidden tw-bg-gradient-to-br tw-from-blue-500 tw-to-blue-600 tw-flex tw-items-center tw-justify-center tw-shadow-sm tw-ring-1 tw-ring-white\">\n @if (getNotificationAvatar(notif)) {\n <img \n cideEleFileImage \n [fileId]=\"getNotificationAvatar(notif) || ''\"\n [altText]=\"getNotificationName(notif)\"\n class=\"tw-w-full tw-h-full tw-object-cover\">\n } @else {\n <cide-ele-icon class=\"!tw-text-base !tw-text-white\">{{ getNotificationIcon(notif.type) }}</cide-ele-icon>\n }\n </div>\n <!-- Green online indicator -->\n <div class=\"tw-absolute tw-bottom-0 tw-right-0 tw-w-2 tw-h-2 tw-bg-green-500 tw-rounded-full tw-border tw-border-white\"></div>\n </div>\n\n <!-- Content -->\n <div class=\"tw-flex-1 tw-min-w-0\">\n <!-- Title and relative time -->\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-1.5 tw-mb-0.5\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <h4 class=\"tw-m-0 tw-text-[11px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-tight\">\n <span class=\"tw-text-blue-600 dark:tw-text-blue-400\">{{ getNotificationName(notif) }}</span><span class=\"tw-font-normal tw-text-gray-800 dark:tw-text-gray-300\"> {{ getNotificationAction(notif) }}</span>\n </h4>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-500 dark:tw-text-gray-500 tw-mt-0.5\">{{ getTimeAgo(notif.timestamp) }}</p>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1 tw-flex-shrink-0\">\n @if (!isNotificationRead(notif)) {\n <div class=\"tw-w-1.5 tw-h-1.5 tw-rounded-full tw-bg-blue-500 dark:tw-bg-blue-400 tw-animate-pulse\"></div>\n }\n @if (isNotificationAnimating(notif) && isNotificationUndoable(notif)) {\n <button \n type=\"button\"\n (click)=\"undoNotificationRemoval(getNotificationId(notif)); $event.stopPropagation()\"\n class=\"tw-px-2 tw-py-0.5 tw-text-[9px] tw-font-semibold tw-text-blue-600 dark:tw-text-blue-400 tw-bg-blue-50 dark:tw-bg-blue-900 tw-border tw-border-blue-200 dark:tw-border-blue-700 tw-rounded-md tw-cursor-pointer tw-transition-all tw-duration-200 hover:tw-bg-blue-100 dark:hover:tw-bg-blue-800 hover:tw-border-blue-300 dark:hover:tw-border-blue-600 hover:tw-scale-105 tw-whitespace-nowrap tw-animate-pulse\">\n Undo\n </button>\n }\n </div>\n </div>\n \n <!-- Message -->\n @if (notif.message && notif.message !== notif.title) {\n <p class=\"tw-m-0 tw-mt-1 tw-text-[10px] tw-text-gray-700 dark:tw-text-gray-400 tw-leading-snug tw-line-clamp-2\">\n {{ notif.message }}\n </p>\n }\n \n <!-- Comment -->\n @if (notif.data?.comment) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-100 dark:tw-bg-gray-700 tw-rounded-md tw-border-l-2 tw-border-l-blue-500 dark:tw-border-l-blue-500\">\n <p class=\"tw-m-0 tw-text-[10px] tw-text-gray-800 dark:tw-text-gray-300 tw-leading-snug tw-italic tw-line-clamp-2\">{{ notif.data.comment }}</p>\n </div>\n }\n \n <!-- File -->\n @if (notif.data?.file) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-100 dark:tw-bg-gray-700 tw-border tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-md tw-flex tw-items-center tw-gap-2\">\n <div class=\"tw-w-7 tw-h-7 tw-bg-blue-100 dark:tw-bg-blue-900 tw-rounded-md tw-flex tw-items-center tw-justify-center tw-flex-shrink-0\">\n <span class=\"tw-text-[9px] tw-font-bold tw-text-blue-700 dark:tw-text-blue-300\">{{ getFileIcon(notif.data.file.type) }}</span>\n </div>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <p class=\"tw-m-0 tw-text-[10px] tw-font-semibold tw-text-gray-900 dark:tw-text-gray-100 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap\">{{ notif.data.file.name }}</p>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-600 dark:tw-text-gray-400\">{{ notif.data.file.size }}</p>\n </div>\n <cide-ele-icon class=\"!tw-text-sm !tw-text-blue-600 dark:!tw-text-blue-400 tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex-shrink-0 hover:tw-text-blue-700 dark:hover:tw-text-blue-300 hover:tw-scale-110\">download</cide-ele-icon>\n </div>\n }\n \n <!-- Action Buttons -->\n @if (notif.action_label) {\n <div class=\"tw-mt-1.5 tw-flex tw-gap-1.5\">\n @if (notif.action_label.toLowerCase().includes('decline')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-gray-300 dark:tw-border-gray-600 tw-bg-white dark:tw-bg-gray-800 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-gray-800 dark:tw-text-gray-300 hover:tw-bg-gray-100 dark:hover:tw-bg-gray-700 hover:tw-border-gray-400 dark:hover:tw-border-gray-500 hover:tw-scale-105\">Decline</button>\n }\n @if (notif.action_label.toLowerCase().includes('accept')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-blue-600 dark:tw-border-blue-500 tw-bg-blue-600 dark:tw-bg-blue-500 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-white hover:tw-bg-blue-700 dark:hover:tw-bg-blue-600 hover:tw-border-blue-700 dark:hover:tw-border-blue-600 hover:tw-scale-105\">Accept</button>\n }\n </div>\n }\n </div>\n </div>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n <div class=\"header-divider\"></div>\n\n <!-- Profile with Dropdown -->\n <div class=\"header-icon user-profile\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"profileItems\" [config]=\"profileConfig\"\n [triggerTemplate]=\"triggerTemplate\"\n (itemClick)=\"onProfileClick($event)\">\n <ng-template #triggerTemplate>\n @if (appStateService.currentUser()?.user_photo_id_cyfm) {\n <div class=\"profile-avatar\">\n <img cideEleFileImage [fileId]=\"(appStateService.currentUser()?.user_photo_id_cyfm || '')\"\n [altText]=\"'User Profile Photo'\" class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-full\">\n </div>\n } @else {\n <div class=\"profile-avatar\">\n <cide-ele-icon name=\"person\" class=\"tw-w-6 tw-h-6 tw-text-white\"></cide-ele-icon>\n </div>\n }\n </ng-template>\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">My Account</div>\n </div>\n </div>\n</header>", styles: [".cide-lyt-header{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(to right,rgb(var(--tw-white-rgb) / .95),rgb(var(--tw-gray-50-rgb) / .95));box-shadow:0 2px 8px #00000008;padding:0 1rem;position:relative;z-index:20;transition:all .3s cubic-bezier(.4,0,.2,1);will-change:transform;border-bottom:1px solid rgb(var(--tw-gray-200-rgb) / .8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-logo-container{height:100%;display:flex;align-items:center;padding:.5rem 0;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);border-radius:8px;outline:none}.header-logo-container img{height:30px;max-height:100%;transition:all .3s ease;border-radius:5px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.header-logo-container:hover img{transform:scale(1.03);filter:brightness(1.05);box-shadow:0 2px 6px #00000014}.header-logo-container:after{content:\"\";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(to bottom right,rgb(var(--tw-white-rgb) / 0),rgb(var(--tw-white-rgb) / .3),rgb(var(--tw-white-rgb) / 0));transform:rotate(30deg);opacity:0;transition:transform .6s ease,opacity .6s ease;pointer-events:none}.header-logo-container:hover:after,.header-logo-container:focus:after{opacity:1;transform:rotate(30deg) translate(50%,50%)}.header-search-container{flex-grow:1;max-width:600px;margin:0 2rem;position:relative;transition:all .3s ease}::ng-deep .header-search-container #cide_lyt_header_search{width:100%;background-color:rgb(var(--tw-gray-50-rgb) / .8);border-radius:20px!important;transition:all .3s ease;overflow:visible;transform:translateZ(0)}::ng-deep .header-search-container #cide_lyt_header_search:hover{box-shadow:0 3px 12px #00000014;background-color:rgb(var(--tw-white-rgb) / 1);transform:translateY(-1px)}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-input{background-color:transparent;font-size:.85rem!important;letter-spacing:.01em}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:#6b7280b3!important;font-size:1.1rem!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within{transform:translateY(-1px) scale(1.01)}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-input{border-color:#3b82f6!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-leading-icon{color:#3b82f6!important}.header-icons-container{display:flex;align-items:center;gap:1rem}.header-icon{position:relative;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-dropdown-container{position:relative;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-icon:before{content:\"\";position:absolute;inset:0;background-color:#3b82f61a;border-radius:.5rem;opacity:0;transform:scale(.8);transition:all .2s cubic-bezier(.4,0,.2,1)}.header-icon:hover:before{opacity:1;transform:scale(1)}.header-icon:hover{color:#3b82f6}.header-icon:active{transform:scale(.95)}.header-tooltip{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);background-color:rgb(var(--tw-gray-700-rgb) / .9);color:rgb(var(--tw-white-rgb) / 1);padding:.25rem .6rem;border-radius:.25rem;font-size:.7rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 5px #0003;letter-spacing:.01em;will-change:transform,opacity}.header-tooltip:before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:transparent transparent rgba(55,65,81,.9) transparent}.header-icon:hover .header-tooltip{opacity:1;transform:translate(-50%) translateY(0)}.header-badge{position:absolute;top:-2px;right:-2px;min-width:18px;height:18px;border-radius:9px;background-color:#ef4444;color:rgb(var(--tw-white-rgb) / 1);font-size:10px;display:flex!important;align-items:center;justify-content:center;padding:0 5px;box-shadow:0 2px 4px #ef444466;font-weight:700;z-index:10;transition:all .2s ease;line-height:1;border:2px solid #ffffff}.header-icon:hover .header-badge{transform:scale(1.15);box-shadow:0 3px 6px #ef444480}.header-icon.notification-icon{position:relative;overflow:visible}.header-divider{height:20px;width:1px;background-color:#e5e7ebcc;margin:0 6px}.header-year-dropdown-wrapper{position:relative;display:flex;align-items:center;justify-content:center}.header-year-pill{position:relative;display:flex;align-items:center;padding:.25rem .625rem;background:linear-gradient(135deg,#3b82f61a,#2563eb26);border:1px solid rgba(59,130,246,.3);border-radius:9999px;color:#2563eb;font-size:.6875rem;font-weight:600;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;white-space:nowrap;box-shadow:0 1px 2px #3b82f61a;min-height:22px;height:22px;line-height:1}.header-year-pill:hover{background:linear-gradient(135deg,#3b82f626,#2563eb33);border-color:#3b82f666;transform:translateY(-1px);box-shadow:0 2px 6px #3b82f626}.header-year-pill-text{max-width:180px;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em;line-height:1;display:inline-block}::ng-deep .header-dropdown-container .dropdown-trigger{background:transparent!important;border:none!important;border-radius:0!important;padding:0!important;width:100%!important;height:100%!important;min-width:auto!important;box-shadow:none!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:none!important;cursor:pointer!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover{background:transparent!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover .header-year-pill{background:linear-gradient(135deg,#3b82f626,#2563eb33)!important;border-color:#3b82f666!important;transform:translateY(-1px)!important;box-shadow:0 2px 6px #3b82f626!important}::ng-deep .header-dropdown-container .dropdown-trigger:focus,::ng-deep .header-dropdown-container .dropdown-trigger:focus-visible,::ng-deep .header-dropdown-container .dropdown-trigger:active{outline:none!important;box-shadow:0 2px 6px #3b82f626!important}.profile-avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);color:rgb(var(--tw-white-rgb) / 1);font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #2563eb33;transition:all .2s cubic-bezier(.4,0,.2,1);letter-spacing:-.5px;cursor:pointer;border:2px solid transparent}.profile-avatar:hover,.header-icon:hover .profile-avatar{transform:scale(1.08);box-shadow:0 3px 8px #2563eb4d;border-color:#3b82f64d}::ng-deep .user-profile .dropdown-trigger{background:transparent!important;border:none!important;padding:0!important;width:auto!important;height:auto!important;border-radius:0!important}::ng-deep .user-profile .dropdown-trigger:hover{background:transparent!important}::ng-deep .user-profile .dropdown-trigger:focus,::ng-deep .user-profile .dropdown-trigger:focus-visible,::ng-deep .user-profile .dropdown-trigger:active{outline:none!important;box-shadow:none!important}:root[data-theme=dark] .cide-lyt-header,:root.dark-mode .cide-lyt-header{background:linear-gradient(to right,var(--cide-theme-light-color),var(--cide-theme-hover-bg-color));border-bottom-color:var(--cide-theme-border-color);box-shadow:0 2px 8px var(--cide-theme-shadow-color)}:root[data-theme=dark] .header-icon,:root.dark-mode .header-icon{color:var(--cide-theme-text-color)}:root[data-theme=dark] .header-icon.notification-icon,:root.dark-mode .header-icon.notification-icon{color:#d1d5db}:root[data-theme=dark] .header-icon.notification-icon:hover,:root.dark-mode .header-icon.notification-icon:hover{color:#60a5fa}:root[data-theme=dark] .header-divider,:root.dark-mode .header-divider{background-color:var(--cide-theme-border-color)}:root[data-theme=dark] .header-year-pill,:root.dark-mode .header-year-pill{background:linear-gradient(135deg,#60a5fa33,#3b82f640);border-color:#60a5fa80;color:#60a5fa}:root[data-theme=dark] .header-year-pill .header-year-pill-text,:root.dark-mode .header-year-pill .header-year-pill-text{color:#60a5fa}:root[data-theme=dark] .header-year-pill cide-ele-icon,:root.dark-mode .header-year-pill cide-ele-icon{color:#60a5fa!important}:root[data-theme=dark] .header-year-pill:hover,:root.dark-mode .header-year-pill:hover{background:linear-gradient(135deg,#60a5fa4d,#3b82f659);border-color:#60a5fa99;color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover .header-year-pill-text,:root.dark-mode .header-year-pill:hover .header-year-pill-text{color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover cide-ele-icon,:root.dark-mode .header-year-pill:hover cide-ele-icon{color:#93c5fd!important}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search{background-color:var(--cide-theme-hover-bg-color);border-color:var(--cide-theme-border-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search:hover,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search:hover{background-color:var(--cide-theme-light-color);box-shadow:0 3px 12px var(--cide-theme-shadow-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:var(--cide-theme-label-color)!important}.header-avatar{width:36px;height:36px;border-radius:50%;overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1);border:2px solid transparent;box-shadow:0 2px 4px #0000001a}.header-avatar:hover{border-color:#3b82f6;transform:scale(1.05);box-shadow:0 3px 6px #3b82f64d}@media (max-width: 768px){.header-search-container{margin:0 1rem}.header-icons-container{gap:.5rem}}@media (max-width: 640px){.header-search-container{max-width:200px;margin:0 .5rem}}.notification-scroll-container{scrollbar-width:thin;scrollbar-color:var(--cide-ele-scrollbar-thumb, #d1d5db) var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar{width:6px;height:6px}.notification-scroll-container::-webkit-scrollbar-track{background:var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar-thumb{background-color:var(--cide-ele-scrollbar-thumb, #d1d5db);border-radius:3px}.notification-scroll-container::-webkit-scrollbar-thumb:hover{background-color:var(--cide-ele-scrollbar-thumb-hover, #9ca3af)}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container{background-color:#fff!important;color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container *{color:inherit}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-900{color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-800{color:#1f2937!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-700{color:#374151!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-600{color:#4b5563!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-500{color:#6b7280!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-blue-600{color:#2563eb!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-white{background-color:#fff!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-gray-50{background-color:#f9fafb!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-gray-100{background-color:#f3f4f6!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-blue-50{background-color:#eff6ff!important}\n"], dependencies: [{ kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "step", "size"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleDropdownComponent, selector: "cide-ele-dropdown", inputs: ["items", "config", "triggerTemplate", "menuTemplate"], outputs: ["itemClick", "dropdownToggle"] }, { kind: "directive", type: CideEleFileImageDirective, selector: "[cideEleFileImage]", inputs: ["fileId", "altText"] }] });
|
|
3290
3417
|
}
|
|
3291
3418
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytHeaderWrapperComponent, decorators: [{
|
|
3292
3419
|
type: Component,
|
|
3293
3420
|
args: [{ selector: 'cide-lyt-header-wrapper', imports: [CideInputComponent, CommonModule, CideIconComponent,
|
|
3294
|
-
CideEleDropdownComponent, CideEleFileImageDirective], template: "<header id=\"cide-lyt-header-wrapper\" class=\"cide-lyt-header tw-w-full tw-select-none cide-lyt-header-wrapper-hide\">\n <!-- Logo Section -->\n <div class=\"tw-flex tw-items-center tw-gap-3\">\n <div class=\"header-logo-container tw-flex tw-items-center tw-gap-3 tw-cursor-pointer\" (click)=\"onLogoClick()\"\n (keydown.enter)=\"onLogoClick()\" (keydown.space)=\"onLogoClick()\" tabindex=\"0\" role=\"button\"\n aria-label=\"Navigate to home\" title=\"Click to go to control panel home\">\n @if (appStateService.activeEntity()?.syen_photo_id_cyfm) {\n <img cideEleFileImage [fileId]=\"(appStateService.activeEntity()?.syen_photo_id_cyfm || '')\"\n [altText]=\"'Entity Logo'\" class=\"tw-w-8 tw-h-8 tw-object-contain\">\n } @else {\n <cide-ele-icon name=\"business\" class=\"tw-w-8 tw-h-8 tw-text-blue-600\"></cide-ele-icon>\n }\n\n </div>\n @if (appStateService.activeEntity()?.syen_name) {\n <span\n class=\"tw-text-md tw-font-semibold tw-text-blue-600 hover:tw-text-blue-800 tw-cursor-pointer sm:block tw-transition-colors tw-duration-200 hover:tw-underline\"\n (click)=\"onEntityNameClick()\" title=\"Click to switch entity\">\n {{ appStateService.activeEntity()?.syen_name }}\n </span>\n }\n </div>\n <!-- Search Section -->\n <div class=\"header-search-container\">\n <cide-ele-input id=\"cide_lyt_header_search\" placeholder=\"Search...\" leadingIcon=\"search\"\n size=\"md\"></cide-ele-input>\n </div>\n\n <!-- Icons Section -->\n <div class=\"header-icons-container\">\n <!-- Financial Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"financialYearItems()\" [config]=\"financialYearConfig\"\n [triggerTemplate]=\"financialYearTriggerTemplate\"\n (itemClick)=\"onFinancialYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Financial Year</div>\n </div>\n \n <ng-template #financialYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">calendar_today</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentFinancialYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Academic Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"academicYearItems()\" [config]=\"academicYearConfig\"\n [triggerTemplate]=\"academicYearTriggerTemplate\"\n (itemClick)=\"onAcademicYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Academic Year</div>\n </div>\n \n <ng-template #academicYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">school</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentAcademicYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Notifications Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown \n #notificationDropdown\n [items]=\"notificationItems()\" \n [config]=\"notificationConfig\"\n [triggerTemplate]=\"notificationTriggerTemplate\"\n [menuTemplate]=\"notificationMenuTemplate\"\n (itemClick)=\"onNotificationClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Notifications</div>\n </div>\n \n <ng-template #notificationTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-icon notification-icon\" [class.active]=\"isOpen\">\n <cide-ele-icon>notifications</cide-ele-icon>\n @if (unreadCount() > 0) {\n <div class=\"header-badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</div>\n }\n </div>\n </ng-template>\n\n <!-- Custom Notification Menu Template -->\n <ng-template #notificationMenuTemplate let-items=\"items\">\n <div class=\"tw-min-w-[380px] tw-bg-white dark:tw-bg-gray-800 tw-rounded-xl tw-shadow-2xl tw-overflow-hidden\">\n <!-- Header - Fixed/Sticky -->\n <div class=\"tw-sticky tw-top-0 tw-z-10 tw-px-2.5 tw-py-1 tw-bg-gradient-to-r tw-from-white dark:tw-from-gray-800 tw-to-gray-50 dark:tw-to-gray-700 tw-flex tw-justify-between tw-items-center tw-border-b tw-border-gray-200 dark:tw-border-gray-700 tw-backdrop-blur-sm\">\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <div class=\"tw-w-5 tw-h-5 tw-bg-blue-500 tw-rounded tw-flex tw-items-center tw-justify-center tw-shadow-sm\">\n <cide-ele-icon class=\"!tw-text-[10px] !tw-text-white\">notifications</cide-ele-icon>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <h3 class=\"tw-m-0 tw-text-[10px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-none\">Notifications</h3>\n @if (unreadCount() > 0) {\n <span class=\"tw-bg-red-500 tw-text-white tw-px-1 tw-py-0 tw-rounded-full tw-text-[7px] tw-font-bold tw-leading-none tw-animate-pulse\">{{ unreadCount() }}</span>\n }\n </div>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-0.5\">\n <button \n type=\"button\"\n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-400 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 hover:tw-text-gray-700 dark:hover:tw-text-gray-200\"\n (click)=\"openNotificationSettings(); $event.stopPropagation()\"\n title=\"Notification Settings\">\n <cide-ele-icon class=\"!tw-text-xs\">settings</cide-ele-icon>\n </button>\n @if (unreadCount() > 0) {\n <button \n type=\"button\"\n class=\"tw-bg-blue-500 tw-text-white tw-px-1 tw-py-0.5 tw-rounded tw-text-[8px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex tw-items-center tw-gap-0.5 tw-shadow-sm hover:tw-bg-blue-600 hover:tw-scale-105\"\n (click)=\"markAllAsRead(); $event.stopPropagation()\"\n title=\"Mark all as read\">\n <cide-ele-icon class=\"!tw-text-[9px]\">done_all</cide-ele-icon>\n </button>\n }\n <button \n type=\"button\" \n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-400 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-200 dark:hover:tw-bg-gray-600 hover:tw-text-gray-700 dark:hover:tw-text-gray-200 hover:tw-rotate-90\"\n (click)=\"closeNotificationDropdown($event)\"\n title=\"Close\">\n <cide-ele-icon class=\"!tw-text-xs\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n\n <!-- Notifications List -->\n <div class=\"tw-overflow-x-hidden notification-scroll-container tw-bg-white dark:tw-bg-gray-800\">\n @if (notifications().length === 0) {\n <div class=\"tw-py-16 tw-px-4 tw-text-center tw-bg-white dark:tw-bg-gray-800\">\n <cide-ele-icon class=\"!tw-text-6xl !tw-text-gray-200 dark:!tw-text-gray-600 tw-mb-3\">notifications_off</cide-ele-icon>\n <p class=\"tw-m-0 tw-text-sm tw-text-gray-500 dark:tw-text-gray-400 tw-font-medium\">No notifications yet</p>\n <p class=\"tw-m-0 tw-mt-1 tw-text-xs tw-text-gray-400 dark:tw-text-gray-500\">You're all caught up!</p>\n </div>\n } @else {\n @for (notif of notifications().slice(0, 15); track notif.id) {\n <div \n class=\"tw-flex tw-items-start tw-px-3 tw-py-2 tw-cursor-pointer tw-transition-all tw-duration-300 tw-relative tw-gap-2 tw-border-b tw-border-gray-200 dark:tw-border-gray-700 last:tw-border-b-0 hover:tw-bg-gray-50 dark:hover:tw-bg-gray-700 tw-transform tw-ease-in-out tw-bg-white dark:tw-bg-gray-800\"\n [class.tw-border-l-2]=\"!isNotificationRead(notif)\"\n [class.tw-border-l-blue-500]=\"!isNotificationRead(notif)\"\n [class.dark:tw-border-l-blue-400]=\"!isNotificationRead(notif)\"\n [class.tw-bg-blue-50]=\"!isNotificationRead(notif)\"\n [class.dark:tw-bg-gray-700]=\"!isNotificationRead(notif)\"\n [class.-tw-translate-x-full]=\"isNotificationAnimating(notif)\"\n [class.tw-opacity-0]=\"isNotificationAnimating(notif)\"\n [class.tw-max-h-0]=\"isNotificationAnimating(notif)\"\n [class.tw-overflow-hidden]=\"isNotificationAnimating(notif)\"\n [class.tw-mb-0]=\"isNotificationAnimating(notif)\"\n [class.tw-p-0]=\"isNotificationAnimating(notif)\"\n (click)=\"onNotificationItemClick(notif)\">\n <!-- Icon/Avatar -->\n <div class=\"tw-flex-shrink-0 tw-relative\">\n <div class=\"tw-w-9 tw-h-9 tw-rounded-full tw-overflow-hidden tw-bg-gradient-to-br tw-from-blue-500 tw-to-blue-600 tw-flex tw-items-center tw-justify-center tw-shadow-sm tw-ring-1 tw-ring-white\">\n @if (getNotificationAvatar(notif)) {\n <img \n cideEleFileImage \n [fileId]=\"getNotificationAvatar(notif) || ''\"\n [altText]=\"getNotificationName(notif)\"\n class=\"tw-w-full tw-h-full tw-object-cover\">\n } @else {\n <cide-ele-icon class=\"!tw-text-base !tw-text-white\">{{ getNotificationIcon(notif.type) }}</cide-ele-icon>\n }\n </div>\n <!-- Green online indicator -->\n <div class=\"tw-absolute tw-bottom-0 tw-right-0 tw-w-2 tw-h-2 tw-bg-green-500 tw-rounded-full tw-border tw-border-white\"></div>\n </div>\n\n <!-- Content -->\n <div class=\"tw-flex-1 tw-min-w-0\">\n <!-- Title and relative time -->\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-1.5 tw-mb-0.5\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <h4 class=\"tw-m-0 tw-text-[11px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-tight\">\n <span class=\"tw-text-blue-600 dark:tw-text-blue-400\">{{ getNotificationName(notif) }}</span><span class=\"tw-font-normal tw-text-gray-700 dark:tw-text-gray-300\"> {{ getNotificationAction(notif) }}</span>\n </h4>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-400 dark:tw-text-gray-500 tw-mt-0.5\">{{ getTimeAgo(notif.timestamp) }}</p>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1 tw-flex-shrink-0\">\n @if (!isNotificationRead(notif)) {\n <div class=\"tw-w-1.5 tw-h-1.5 tw-rounded-full tw-bg-blue-500 dark:tw-bg-blue-400 tw-animate-pulse\"></div>\n }\n @if (isNotificationAnimating(notif) && isNotificationUndoable(notif)) {\n <button \n type=\"button\"\n (click)=\"undoNotificationRemoval(getNotificationId(notif)); $event.stopPropagation()\"\n class=\"tw-px-2 tw-py-0.5 tw-text-[9px] tw-font-semibold tw-text-blue-600 dark:tw-text-blue-400 tw-bg-blue-50 dark:tw-bg-blue-900 tw-border tw-border-blue-200 dark:tw-border-blue-700 tw-rounded-md tw-cursor-pointer tw-transition-all tw-duration-200 hover:tw-bg-blue-100 dark:hover:tw-bg-blue-800 hover:tw-border-blue-300 dark:hover:tw-border-blue-600 hover:tw-scale-105 tw-whitespace-nowrap tw-animate-pulse\">\n Undo\n </button>\n }\n </div>\n </div>\n \n <!-- Message -->\n @if (notif.message && notif.message !== notif.title) {\n <p class=\"tw-m-0 tw-mt-1 tw-text-[10px] tw-text-gray-600 dark:tw-text-gray-400 tw-leading-snug tw-line-clamp-2\">\n {{ notif.message }}\n </p>\n }\n \n <!-- Comment -->\n @if (notif.data?.comment) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-50 dark:tw-bg-gray-700 tw-rounded-md tw-border-l-2 tw-border-l-blue-400 dark:tw-border-l-blue-500\">\n <p class=\"tw-m-0 tw-text-[10px] tw-text-gray-700 dark:tw-text-gray-300 tw-leading-snug tw-italic tw-line-clamp-2\">{{ notif.data.comment }}</p>\n </div>\n }\n \n <!-- File -->\n @if (notif.data?.file) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-50 dark:tw-bg-gray-700 tw-border tw-border-gray-200 dark:tw-border-gray-600 tw-rounded-md tw-flex tw-items-center tw-gap-2\">\n <div class=\"tw-w-7 tw-h-7 tw-bg-blue-100 dark:tw-bg-blue-900 tw-rounded-md tw-flex tw-items-center tw-justify-center tw-flex-shrink-0\">\n <span class=\"tw-text-[9px] tw-font-bold tw-text-blue-600 dark:tw-text-blue-300\">{{ getFileIcon(notif.data.file.type) }}</span>\n </div>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <p class=\"tw-m-0 tw-text-[10px] tw-font-semibold tw-text-gray-900 dark:tw-text-gray-100 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap\">{{ notif.data.file.name }}</p>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-500 dark:tw-text-gray-400\">{{ notif.data.file.size }}</p>\n </div>\n <cide-ele-icon class=\"!tw-text-sm tw-text-blue-500 dark:tw-text-blue-400 tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex-shrink-0 hover:tw-text-blue-600 dark:hover:tw-text-blue-300 hover:tw-scale-110\">download</cide-ele-icon>\n </div>\n }\n \n <!-- Action Buttons -->\n @if (notif.action_label) {\n <div class=\"tw-mt-1.5 tw-flex tw-gap-1.5\">\n @if (notif.action_label.toLowerCase().includes('decline')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-gray-300 dark:tw-border-gray-600 tw-bg-white dark:tw-bg-gray-800 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-gray-700 dark:tw-text-gray-300 hover:tw-bg-gray-50 dark:hover:tw-bg-gray-700 hover:tw-border-gray-400 dark:hover:tw-border-gray-500 hover:tw-scale-105\">Decline</button>\n }\n @if (notif.action_label.toLowerCase().includes('accept')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-blue-600 dark:tw-border-blue-500 tw-bg-blue-600 dark:tw-bg-blue-500 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-white hover:tw-bg-blue-700 dark:hover:tw-bg-blue-600 hover:tw-border-blue-700 dark:hover:tw-border-blue-600 hover:tw-scale-105\">Accept</button>\n }\n </div>\n }\n </div>\n </div>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n <div class=\"header-divider\"></div>\n\n <!-- Profile with Dropdown -->\n <div class=\"header-icon user-profile\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"profileItems\" [config]=\"profileConfig\"\n [triggerTemplate]=\"triggerTemplate\"\n (itemClick)=\"onProfileClick($event)\">\n <ng-template #triggerTemplate>\n @if (appStateService.currentUser()?.user_photo_id_cyfm) {\n <div class=\"profile-avatar\">\n <img cideEleFileImage [fileId]=\"(appStateService.currentUser()?.user_photo_id_cyfm || '')\"\n [altText]=\"'User Profile Photo'\" class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-full\">\n </div>\n } @else {\n <div class=\"profile-avatar\">\n <cide-ele-icon name=\"person\" class=\"tw-w-6 tw-h-6 tw-text-white\"></cide-ele-icon>\n </div>\n }\n </ng-template>\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">My Account</div>\n </div>\n </div>\n</header>", styles: [".cide-lyt-header{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(to right,rgb(var(--tw-white-rgb) / .95),rgb(var(--tw-gray-50-rgb) / .95));box-shadow:0 2px 8px #00000008;padding:0 1rem;position:relative;z-index:20;transition:all .3s cubic-bezier(.4,0,.2,1);will-change:transform;border-bottom:1px solid rgb(var(--tw-gray-200-rgb) / .8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-logo-container{height:100%;display:flex;align-items:center;padding:.5rem 0;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);border-radius:8px;outline:none}.header-logo-container img{height:30px;max-height:100%;transition:all .3s ease;border-radius:5px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.header-logo-container:hover img{transform:scale(1.03);filter:brightness(1.05);box-shadow:0 2px 6px #00000014}.header-logo-container:after{content:\"\";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(to bottom right,rgb(var(--tw-white-rgb) / 0),rgb(var(--tw-white-rgb) / .3),rgb(var(--tw-white-rgb) / 0));transform:rotate(30deg);opacity:0;transition:transform .6s ease,opacity .6s ease;pointer-events:none}.header-logo-container:hover:after,.header-logo-container:focus:after{opacity:1;transform:rotate(30deg) translate(50%,50%)}.header-search-container{flex-grow:1;max-width:600px;margin:0 2rem;position:relative;transition:all .3s ease}::ng-deep .header-search-container #cide_lyt_header_search{width:100%;background-color:rgb(var(--tw-gray-50-rgb) / .8);border-radius:20px!important;transition:all .3s ease;overflow:visible;transform:translateZ(0)}::ng-deep .header-search-container #cide_lyt_header_search:hover{box-shadow:0 3px 12px #00000014;background-color:rgb(var(--tw-white-rgb) / 1);transform:translateY(-1px)}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-input{background-color:transparent;font-size:.85rem!important;letter-spacing:.01em}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:#6b7280b3!important;font-size:1.1rem!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within{transform:translateY(-1px) scale(1.01)}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-input{border-color:#3b82f6!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-leading-icon{color:#3b82f6!important}.header-icons-container{display:flex;align-items:center;gap:1rem}.header-icon{position:relative;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-dropdown-container{position:relative;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-icon:before{content:\"\";position:absolute;inset:0;background-color:#3b82f61a;border-radius:.5rem;opacity:0;transform:scale(.8);transition:all .2s cubic-bezier(.4,0,.2,1)}.header-icon:hover:before{opacity:1;transform:scale(1)}.header-icon:hover{color:#3b82f6}.header-icon:active{transform:scale(.95)}.header-tooltip{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);background-color:rgb(var(--tw-gray-700-rgb) / .9);color:rgb(var(--tw-white-rgb) / 1);padding:.25rem .6rem;border-radius:.25rem;font-size:.7rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 5px #0003;letter-spacing:.01em;will-change:transform,opacity}.header-tooltip:before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:transparent transparent rgba(55,65,81,.9) transparent}.header-icon:hover .header-tooltip{opacity:1;transform:translate(-50%) translateY(0)}.header-badge{position:absolute;top:0;right:0;min-width:16px;height:16px;border-radius:8px;background-color:#ef4444;color:rgb(var(--tw-white-rgb) / 1);font-size:9px;display:flex;align-items:center;justify-content:center;padding:0 4px;box-shadow:0 1px 3px #ef44444d;font-weight:600;z-index:2;transition:all .2s ease}.header-icon:hover .header-badge{transform:scale(1.1)}.header-divider{height:20px;width:1px;background-color:#e5e7ebcc;margin:0 6px}.header-year-dropdown-wrapper{position:relative;display:flex;align-items:center;justify-content:center}.header-year-pill{position:relative;display:flex;align-items:center;padding:.25rem .625rem;background:linear-gradient(135deg,#3b82f61a,#2563eb26);border:1px solid rgba(59,130,246,.3);border-radius:9999px;color:#2563eb;font-size:.6875rem;font-weight:600;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;white-space:nowrap;box-shadow:0 1px 2px #3b82f61a;min-height:22px;height:22px;line-height:1}.header-year-pill:hover{background:linear-gradient(135deg,#3b82f626,#2563eb33);border-color:#3b82f666;transform:translateY(-1px);box-shadow:0 2px 6px #3b82f626}.header-year-pill-text{max-width:180px;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em;line-height:1;display:inline-block}::ng-deep .header-dropdown-container .dropdown-trigger{background:transparent!important;border:none!important;border-radius:0!important;padding:0!important;width:100%!important;height:100%!important;min-width:auto!important;box-shadow:none!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:none!important;cursor:pointer!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover{background:transparent!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover .header-year-pill{background:linear-gradient(135deg,#3b82f626,#2563eb33)!important;border-color:#3b82f666!important;transform:translateY(-1px)!important;box-shadow:0 2px 6px #3b82f626!important}::ng-deep .header-dropdown-container .dropdown-trigger:focus,::ng-deep .header-dropdown-container .dropdown-trigger:focus-visible,::ng-deep .header-dropdown-container .dropdown-trigger:active{outline:none!important;box-shadow:0 2px 6px #3b82f626!important}.profile-avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);color:rgb(var(--tw-white-rgb) / 1);font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #2563eb33;transition:all .2s cubic-bezier(.4,0,.2,1);letter-spacing:-.5px;cursor:pointer;border:2px solid transparent}.profile-avatar:hover,.header-icon:hover .profile-avatar{transform:scale(1.08);box-shadow:0 3px 8px #2563eb4d;border-color:#3b82f64d}::ng-deep .user-profile .dropdown-trigger{background:transparent!important;border:none!important;padding:0!important;width:auto!important;height:auto!important;border-radius:0!important}::ng-deep .user-profile .dropdown-trigger:hover{background:transparent!important}::ng-deep .user-profile .dropdown-trigger:focus,::ng-deep .user-profile .dropdown-trigger:focus-visible,::ng-deep .user-profile .dropdown-trigger:active{outline:none!important;box-shadow:none!important}:root[data-theme=dark] .cide-lyt-header,:root.dark-mode .cide-lyt-header{background:linear-gradient(to right,var(--cide-theme-light-color),var(--cide-theme-hover-bg-color));border-bottom-color:var(--cide-theme-border-color);box-shadow:0 2px 8px var(--cide-theme-shadow-color)}:root[data-theme=dark] .header-icon,:root.dark-mode .header-icon{color:var(--cide-theme-text-color)}:root[data-theme=dark] .header-icon.notification-icon,:root.dark-mode .header-icon.notification-icon{color:#d1d5db}:root[data-theme=dark] .header-icon.notification-icon:hover,:root.dark-mode .header-icon.notification-icon:hover{color:#60a5fa}:root[data-theme=dark] .header-divider,:root.dark-mode .header-divider{background-color:var(--cide-theme-border-color)}:root[data-theme=dark] .header-year-pill,:root.dark-mode .header-year-pill{background:linear-gradient(135deg,#60a5fa33,#3b82f640);border-color:#60a5fa80;color:#60a5fa}:root[data-theme=dark] .header-year-pill .header-year-pill-text,:root.dark-mode .header-year-pill .header-year-pill-text{color:#60a5fa}:root[data-theme=dark] .header-year-pill cide-ele-icon,:root.dark-mode .header-year-pill cide-ele-icon{color:#60a5fa!important}:root[data-theme=dark] .header-year-pill:hover,:root.dark-mode .header-year-pill:hover{background:linear-gradient(135deg,#60a5fa4d,#3b82f659);border-color:#60a5fa99;color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover .header-year-pill-text,:root.dark-mode .header-year-pill:hover .header-year-pill-text{color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover cide-ele-icon,:root.dark-mode .header-year-pill:hover cide-ele-icon{color:#93c5fd!important}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search{background-color:var(--cide-theme-hover-bg-color);border-color:var(--cide-theme-border-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search:hover,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search:hover{background-color:var(--cide-theme-light-color);box-shadow:0 3px 12px var(--cide-theme-shadow-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:var(--cide-theme-label-color)!important}.header-avatar{width:36px;height:36px;border-radius:50%;overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1);border:2px solid transparent;box-shadow:0 2px 4px #0000001a}.header-avatar:hover{border-color:#3b82f6;transform:scale(1.05);box-shadow:0 3px 6px #3b82f64d}@media (max-width: 768px){.header-search-container{margin:0 1rem}.header-icons-container{gap:.5rem}}@media (max-width: 640px){.header-search-container{max-width:200px;margin:0 .5rem}}.notification-scroll-container{scrollbar-width:thin;scrollbar-color:var(--cide-ele-scrollbar-thumb, #d1d5db) var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar{width:6px;height:6px}.notification-scroll-container::-webkit-scrollbar-track{background:var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar-thumb{background-color:var(--cide-ele-scrollbar-thumb, #d1d5db);border-radius:3px}.notification-scroll-container::-webkit-scrollbar-thumb:hover{background-color:var(--cide-ele-scrollbar-thumb-hover, #9ca3af)}\n"] }]
|
|
3421
|
+
CideEleDropdownComponent, CideEleFileImageDirective], template: "<header id=\"cide-lyt-header-wrapper\" class=\"cide-lyt-header tw-w-full tw-select-none cide-lyt-header-wrapper-hide\">\n <!-- Logo Section -->\n <div class=\"tw-flex tw-items-center tw-gap-3\">\n <div class=\"header-logo-container tw-flex tw-items-center tw-gap-3 tw-cursor-pointer\" (click)=\"onLogoClick()\"\n (keydown.enter)=\"onLogoClick()\" (keydown.space)=\"onLogoClick()\" tabindex=\"0\" role=\"button\"\n aria-label=\"Navigate to home\" title=\"Click to go to control panel home\">\n @if (appStateService.activeEntity()?.syen_photo_id_cyfm) {\n <img cideEleFileImage [fileId]=\"(appStateService.activeEntity()?.syen_photo_id_cyfm || '')\"\n [altText]=\"'Entity Logo'\" class=\"tw-w-8 tw-h-8 tw-object-contain\">\n } @else {\n <cide-ele-icon name=\"business\" class=\"tw-w-8 tw-h-8 tw-text-blue-600\"></cide-ele-icon>\n }\n\n </div>\n @if (appStateService.activeEntity()?.syen_name) {\n <span\n class=\"tw-text-md tw-font-semibold tw-text-blue-600 hover:tw-text-blue-800 tw-cursor-pointer sm:block tw-transition-colors tw-duration-200 hover:tw-underline\"\n (click)=\"onEntityNameClick()\" title=\"Click to switch entity\">\n {{ appStateService.activeEntity()?.syen_name }}\n </span>\n }\n </div>\n <!-- Search Section -->\n <div class=\"header-search-container\">\n <cide-ele-input id=\"cide_lyt_header_search\" placeholder=\"Search...\" leadingIcon=\"search\"\n size=\"md\"></cide-ele-input>\n </div>\n\n <!-- Icons Section -->\n <div class=\"header-icons-container\">\n <!-- Financial Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"financialYearItems()\" [config]=\"financialYearConfig\"\n [triggerTemplate]=\"financialYearTriggerTemplate\"\n (itemClick)=\"onFinancialYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Financial Year</div>\n </div>\n \n <ng-template #financialYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">calendar_today</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentFinancialYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Academic Year Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"academicYearItems()\" [config]=\"academicYearConfig\"\n [triggerTemplate]=\"academicYearTriggerTemplate\"\n (itemClick)=\"onAcademicYearClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Academic Year</div>\n </div>\n \n <ng-template #academicYearTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-year-pill\">\n <cide-ele-icon size=\"2xs\" type=\"none\" class=\"tw-mr-1\">school</cide-ele-icon>\n <span class=\"header-year-pill-text\">{{ currentAcademicYearName() }}</span>\n </div>\n </ng-template>\n\n <!-- Notifications Dropdown -->\n <div class=\"header-dropdown-container\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown \n #notificationDropdown\n [items]=\"notificationItems()\" \n [config]=\"notificationConfig\"\n [triggerTemplate]=\"notificationTriggerTemplate\"\n [menuTemplate]=\"notificationMenuTemplate\"\n (itemClick)=\"onNotificationClick($event)\">\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">Notifications</div>\n </div>\n \n <ng-template #notificationTriggerTemplate let-isOpen=\"isOpen\">\n <div class=\"header-icon notification-icon\" [class.active]=\"isOpen\">\n <cide-ele-icon>notifications</cide-ele-icon>\n @if (unreadCount() > 0) {\n <div class=\"header-badge\">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</div>\n }\n </div>\n </ng-template>\n\n <!-- Custom Notification Menu Template -->\n <ng-template #notificationMenuTemplate let-items=\"items\">\n <div class=\"tw-min-w-[380px] tw-bg-white dark:tw-bg-gray-800 tw-rounded-xl tw-shadow-2xl tw-overflow-hidden tw-text-gray-900 dark:tw-text-gray-100\">\n <!-- Header - Fixed/Sticky -->\n <div class=\"tw-sticky tw-top-0 tw-z-10 tw-px-2.5 tw-py-1 tw-bg-white dark:tw-bg-gray-800 tw-flex tw-justify-between tw-items-center tw-border-b tw-border-gray-200 dark:tw-border-gray-700 tw-backdrop-blur-sm\">\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <div class=\"tw-w-5 tw-h-5 tw-bg-blue-500 tw-rounded tw-flex tw-items-center tw-justify-center tw-shadow-sm\">\n <cide-ele-icon class=\"!tw-text-[10px] !tw-text-white\">notifications</cide-ele-icon>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1\">\n <h3 class=\"tw-m-0 tw-text-[10px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-none\">Notifications</h3>\n @if (unreadCount() > 0) {\n <span class=\"tw-bg-red-500 tw-text-white tw-px-1 tw-py-0 tw-rounded-full tw-text-[7px] tw-font-bold tw-leading-none tw-animate-pulse\">{{ unreadCount() }}</span>\n }\n </div>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-0.5\">\n <button \n type=\"button\"\n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-600 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-100 dark:hover:tw-bg-gray-600 hover:tw-text-gray-900 dark:hover:tw-text-gray-200\"\n (click)=\"openNotificationSettings(); $event.stopPropagation()\"\n title=\"Notification Settings\">\n <cide-ele-icon class=\"!tw-text-xs\">settings</cide-ele-icon>\n </button>\n @if (unreadCount() > 0) {\n <button \n type=\"button\"\n class=\"tw-bg-blue-500 tw-text-white tw-px-1 tw-py-0.5 tw-rounded tw-text-[8px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex tw-items-center tw-gap-0.5 tw-shadow-sm hover:tw-bg-blue-600 hover:tw-scale-105\"\n (click)=\"markAllAsRead(); $event.stopPropagation()\"\n title=\"Mark all as read\">\n <cide-ele-icon class=\"!tw-text-[9px]\">done_all</cide-ele-icon>\n </button>\n }\n <button \n type=\"button\" \n class=\"tw-bg-transparent tw-border-none tw-p-0.5 tw-cursor-pointer tw-text-gray-600 dark:tw-text-gray-400 tw-flex tw-items-center tw-justify-center tw-rounded tw-transition-all tw-duration-200 hover:tw-bg-gray-100 dark:hover:tw-bg-gray-600 hover:tw-text-gray-900 dark:hover:tw-text-gray-200 hover:tw-rotate-90\"\n (click)=\"closeNotificationDropdown($event)\"\n title=\"Close\">\n <cide-ele-icon class=\"!tw-text-xs\">close</cide-ele-icon>\n </button>\n </div>\n </div>\n\n <!-- Notifications List -->\n <div class=\"tw-overflow-x-hidden notification-scroll-container tw-bg-white dark:tw-bg-gray-800 tw-text-gray-900 dark:tw-text-gray-100\">\n @if (notifications().length === 0) {\n <div class=\"tw-py-16 tw-px-4 tw-text-center tw-bg-white dark:tw-bg-gray-800\">\n <cide-ele-icon class=\"!tw-text-6xl !tw-text-gray-300 dark:!tw-text-gray-600 tw-mb-3\">notifications_off</cide-ele-icon>\n <p class=\"tw-m-0 tw-text-sm tw-text-gray-600 dark:tw-text-gray-400 tw-font-medium\">No notifications yet</p>\n <p class=\"tw-m-0 tw-mt-1 tw-text-xs tw-text-gray-500 dark:tw-text-gray-500\">You're all caught up!</p>\n </div>\n } @else {\n @for (notif of notifications().slice(0, 15); track notif.id) {\n <div \n class=\"tw-flex tw-items-start tw-px-3 tw-py-2 tw-cursor-pointer tw-transition-all tw-duration-300 tw-relative tw-gap-2 tw-border-b tw-border-gray-200 dark:tw-border-gray-700 last:tw-border-b-0 hover:tw-bg-gray-50 dark:hover:tw-bg-gray-700 tw-transform tw-ease-in-out tw-bg-white dark:tw-bg-gray-800 tw-text-gray-900 dark:tw-text-gray-100\"\n [class.tw-border-l-2]=\"!isNotificationRead(notif)\"\n [class.tw-border-l-blue-500]=\"!isNotificationRead(notif)\"\n [class.dark:tw-border-l-blue-400]=\"!isNotificationRead(notif)\"\n [class.tw-bg-blue-50]=\"!isNotificationRead(notif)\"\n [class.dark:tw-bg-gray-700]=\"!isNotificationRead(notif)\"\n [class.-tw-translate-x-full]=\"isNotificationAnimating(notif)\"\n [class.tw-opacity-0]=\"isNotificationAnimating(notif)\"\n [class.tw-max-h-0]=\"isNotificationAnimating(notif)\"\n [class.tw-overflow-hidden]=\"isNotificationAnimating(notif)\"\n [class.tw-mb-0]=\"isNotificationAnimating(notif)\"\n [class.tw-p-0]=\"isNotificationAnimating(notif)\"\n (click)=\"onNotificationItemClick(notif)\">\n <!-- Icon/Avatar -->\n <div class=\"tw-flex-shrink-0 tw-relative\">\n <div class=\"tw-w-9 tw-h-9 tw-rounded-full tw-overflow-hidden tw-bg-gradient-to-br tw-from-blue-500 tw-to-blue-600 tw-flex tw-items-center tw-justify-center tw-shadow-sm tw-ring-1 tw-ring-white\">\n @if (getNotificationAvatar(notif)) {\n <img \n cideEleFileImage \n [fileId]=\"getNotificationAvatar(notif) || ''\"\n [altText]=\"getNotificationName(notif)\"\n class=\"tw-w-full tw-h-full tw-object-cover\">\n } @else {\n <cide-ele-icon class=\"!tw-text-base !tw-text-white\">{{ getNotificationIcon(notif.type) }}</cide-ele-icon>\n }\n </div>\n <!-- Green online indicator -->\n <div class=\"tw-absolute tw-bottom-0 tw-right-0 tw-w-2 tw-h-2 tw-bg-green-500 tw-rounded-full tw-border tw-border-white\"></div>\n </div>\n\n <!-- Content -->\n <div class=\"tw-flex-1 tw-min-w-0\">\n <!-- Title and relative time -->\n <div class=\"tw-flex tw-items-start tw-justify-between tw-gap-1.5 tw-mb-0.5\">\n <div class=\"tw-flex-1 tw-min-w-0\">\n <h4 class=\"tw-m-0 tw-text-[11px] tw-font-bold tw-text-gray-900 dark:tw-text-gray-100 tw-leading-tight\">\n <span class=\"tw-text-blue-600 dark:tw-text-blue-400\">{{ getNotificationName(notif) }}</span><span class=\"tw-font-normal tw-text-gray-800 dark:tw-text-gray-300\"> {{ getNotificationAction(notif) }}</span>\n </h4>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-500 dark:tw-text-gray-500 tw-mt-0.5\">{{ getTimeAgo(notif.timestamp) }}</p>\n </div>\n <div class=\"tw-flex tw-items-center tw-gap-1 tw-flex-shrink-0\">\n @if (!isNotificationRead(notif)) {\n <div class=\"tw-w-1.5 tw-h-1.5 tw-rounded-full tw-bg-blue-500 dark:tw-bg-blue-400 tw-animate-pulse\"></div>\n }\n @if (isNotificationAnimating(notif) && isNotificationUndoable(notif)) {\n <button \n type=\"button\"\n (click)=\"undoNotificationRemoval(getNotificationId(notif)); $event.stopPropagation()\"\n class=\"tw-px-2 tw-py-0.5 tw-text-[9px] tw-font-semibold tw-text-blue-600 dark:tw-text-blue-400 tw-bg-blue-50 dark:tw-bg-blue-900 tw-border tw-border-blue-200 dark:tw-border-blue-700 tw-rounded-md tw-cursor-pointer tw-transition-all tw-duration-200 hover:tw-bg-blue-100 dark:hover:tw-bg-blue-800 hover:tw-border-blue-300 dark:hover:tw-border-blue-600 hover:tw-scale-105 tw-whitespace-nowrap tw-animate-pulse\">\n Undo\n </button>\n }\n </div>\n </div>\n \n <!-- Message -->\n @if (notif.message && notif.message !== notif.title) {\n <p class=\"tw-m-0 tw-mt-1 tw-text-[10px] tw-text-gray-700 dark:tw-text-gray-400 tw-leading-snug tw-line-clamp-2\">\n {{ notif.message }}\n </p>\n }\n \n <!-- Comment -->\n @if (notif.data?.comment) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-100 dark:tw-bg-gray-700 tw-rounded-md tw-border-l-2 tw-border-l-blue-500 dark:tw-border-l-blue-500\">\n <p class=\"tw-m-0 tw-text-[10px] tw-text-gray-800 dark:tw-text-gray-300 tw-leading-snug tw-italic tw-line-clamp-2\">{{ notif.data.comment }}</p>\n </div>\n }\n \n <!-- File -->\n @if (notif.data?.file) {\n <div class=\"tw-mt-1.5 tw-py-1.5 tw-px-2.5 tw-bg-gray-100 dark:tw-bg-gray-700 tw-border tw-border-gray-300 dark:tw-border-gray-600 tw-rounded-md tw-flex tw-items-center tw-gap-2\">\n <div class=\"tw-w-7 tw-h-7 tw-bg-blue-100 dark:tw-bg-blue-900 tw-rounded-md tw-flex tw-items-center tw-justify-center tw-flex-shrink-0\">\n <span class=\"tw-text-[9px] tw-font-bold tw-text-blue-700 dark:tw-text-blue-300\">{{ getFileIcon(notif.data.file.type) }}</span>\n </div>\n <div class=\"tw-flex-1 tw-min-w-0\">\n <p class=\"tw-m-0 tw-text-[10px] tw-font-semibold tw-text-gray-900 dark:tw-text-gray-100 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap\">{{ notif.data.file.name }}</p>\n <p class=\"tw-m-0 tw-text-[9px] tw-text-gray-600 dark:tw-text-gray-400\">{{ notif.data.file.size }}</p>\n </div>\n <cide-ele-icon class=\"!tw-text-sm !tw-text-blue-600 dark:!tw-text-blue-400 tw-cursor-pointer tw-transition-all tw-duration-200 tw-flex-shrink-0 hover:tw-text-blue-700 dark:hover:tw-text-blue-300 hover:tw-scale-110\">download</cide-ele-icon>\n </div>\n }\n \n <!-- Action Buttons -->\n @if (notif.action_label) {\n <div class=\"tw-mt-1.5 tw-flex tw-gap-1.5\">\n @if (notif.action_label.toLowerCase().includes('decline')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-gray-300 dark:tw-border-gray-600 tw-bg-white dark:tw-bg-gray-800 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-gray-800 dark:tw-text-gray-300 hover:tw-bg-gray-100 dark:hover:tw-bg-gray-700 hover:tw-border-gray-400 dark:hover:tw-border-gray-500 hover:tw-scale-105\">Decline</button>\n }\n @if (notif.action_label.toLowerCase().includes('accept')) {\n <button type=\"button\" class=\"tw-py-1 tw-px-3 tw-border tw-border-blue-600 dark:tw-border-blue-500 tw-bg-blue-600 dark:tw-bg-blue-500 tw-rounded-md tw-text-[10px] tw-font-semibold tw-cursor-pointer tw-transition-all tw-duration-200 tw-text-white hover:tw-bg-blue-700 dark:hover:tw-bg-blue-600 hover:tw-border-blue-700 dark:hover:tw-border-blue-600 hover:tw-scale-105\">Accept</button>\n }\n </div>\n }\n </div>\n </div>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n <div class=\"header-divider\"></div>\n\n <!-- Profile with Dropdown -->\n <div class=\"header-icon user-profile\" (mouseenter)=\"updateTooltipPosition($event)\">\n <cide-ele-dropdown [items]=\"profileItems\" [config]=\"profileConfig\"\n [triggerTemplate]=\"triggerTemplate\"\n (itemClick)=\"onProfileClick($event)\">\n <ng-template #triggerTemplate>\n @if (appStateService.currentUser()?.user_photo_id_cyfm) {\n <div class=\"profile-avatar\">\n <img cideEleFileImage [fileId]=\"(appStateService.currentUser()?.user_photo_id_cyfm || '')\"\n [altText]=\"'User Profile Photo'\" class=\"tw-w-full tw-h-full tw-object-cover tw-rounded-full\">\n </div>\n } @else {\n <div class=\"profile-avatar\">\n <cide-ele-icon name=\"person\" class=\"tw-w-6 tw-h-6 tw-text-white\"></cide-ele-icon>\n </div>\n }\n </ng-template>\n </cide-ele-dropdown>\n <div class=\"header-tooltip\">My Account</div>\n </div>\n </div>\n</header>", styles: [".cide-lyt-header{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(to right,rgb(var(--tw-white-rgb) / .95),rgb(var(--tw-gray-50-rgb) / .95));box-shadow:0 2px 8px #00000008;padding:0 1rem;position:relative;z-index:20;transition:all .3s cubic-bezier(.4,0,.2,1);will-change:transform;border-bottom:1px solid rgb(var(--tw-gray-200-rgb) / .8);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.header-logo-container{height:100%;display:flex;align-items:center;padding:.5rem 0;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);border-radius:8px;outline:none}.header-logo-container img{height:30px;max-height:100%;transition:all .3s ease;border-radius:5px;overflow:hidden;box-shadow:0 1px 4px #0000000d}.header-logo-container:hover img{transform:scale(1.03);filter:brightness(1.05);box-shadow:0 2px 6px #00000014}.header-logo-container:after{content:\"\";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:linear-gradient(to bottom right,rgb(var(--tw-white-rgb) / 0),rgb(var(--tw-white-rgb) / .3),rgb(var(--tw-white-rgb) / 0));transform:rotate(30deg);opacity:0;transition:transform .6s ease,opacity .6s ease;pointer-events:none}.header-logo-container:hover:after,.header-logo-container:focus:after{opacity:1;transform:rotate(30deg) translate(50%,50%)}.header-search-container{flex-grow:1;max-width:600px;margin:0 2rem;position:relative;transition:all .3s ease}::ng-deep .header-search-container #cide_lyt_header_search{width:100%;background-color:rgb(var(--tw-gray-50-rgb) / .8);border-radius:20px!important;transition:all .3s ease;overflow:visible;transform:translateZ(0)}::ng-deep .header-search-container #cide_lyt_header_search:hover{box-shadow:0 3px 12px #00000014;background-color:rgb(var(--tw-white-rgb) / 1);transform:translateY(-1px)}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-input{background-color:transparent;font-size:.85rem!important;letter-spacing:.01em}::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:#6b7280b3!important;font-size:1.1rem!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within{transform:translateY(-1px) scale(1.01)}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-input{border-color:#3b82f6!important}::ng-deep .header-search-container #cide_lyt_header_search:focus-within .cide-input-leading-icon{color:#3b82f6!important}.header-icons-container{display:flex;align-items:center;gap:1rem}.header-icon{position:relative;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-dropdown-container{position:relative;display:flex;align-items:center;justify-content:center;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;color:#374151;border-radius:.4rem;margin:0 2px}.header-icon:before{content:\"\";position:absolute;inset:0;background-color:#3b82f61a;border-radius:.5rem;opacity:0;transform:scale(.8);transition:all .2s cubic-bezier(.4,0,.2,1)}.header-icon:hover:before{opacity:1;transform:scale(1)}.header-icon:hover{color:#3b82f6}.header-icon:active{transform:scale(.95)}.header-tooltip{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);background-color:rgb(var(--tw-gray-700-rgb) / .9);color:rgb(var(--tw-white-rgb) / 1);padding:.25rem .6rem;border-radius:.25rem;font-size:.7rem;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s cubic-bezier(.4,0,.2,1);z-index:1000;box-shadow:0 2px 5px #0003;letter-spacing:.01em;will-change:transform,opacity}.header-tooltip:before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:transparent transparent rgba(55,65,81,.9) transparent}.header-icon:hover .header-tooltip{opacity:1;transform:translate(-50%) translateY(0)}.header-badge{position:absolute;top:-2px;right:-2px;min-width:18px;height:18px;border-radius:9px;background-color:#ef4444;color:rgb(var(--tw-white-rgb) / 1);font-size:10px;display:flex!important;align-items:center;justify-content:center;padding:0 5px;box-shadow:0 2px 4px #ef444466;font-weight:700;z-index:10;transition:all .2s ease;line-height:1;border:2px solid #ffffff}.header-icon:hover .header-badge{transform:scale(1.15);box-shadow:0 3px 6px #ef444480}.header-icon.notification-icon{position:relative;overflow:visible}.header-divider{height:20px;width:1px;background-color:#e5e7ebcc;margin:0 6px}.header-year-dropdown-wrapper{position:relative;display:flex;align-items:center;justify-content:center}.header-year-pill{position:relative;display:flex;align-items:center;padding:.25rem .625rem;background:linear-gradient(135deg,#3b82f61a,#2563eb26);border:1px solid rgba(59,130,246,.3);border-radius:9999px;color:#2563eb;font-size:.6875rem;font-weight:600;transition:all .2s cubic-bezier(.4,0,.2,1);cursor:pointer;white-space:nowrap;box-shadow:0 1px 2px #3b82f61a;min-height:22px;height:22px;line-height:1}.header-year-pill:hover{background:linear-gradient(135deg,#3b82f626,#2563eb33);border-color:#3b82f666;transform:translateY(-1px);box-shadow:0 2px 6px #3b82f626}.header-year-pill-text{max-width:180px;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em;line-height:1;display:inline-block}::ng-deep .header-dropdown-container .dropdown-trigger{background:transparent!important;border:none!important;border-radius:0!important;padding:0!important;width:100%!important;height:100%!important;min-width:auto!important;box-shadow:none!important;display:flex!important;align-items:center!important;justify-content:center!important;transition:none!important;cursor:pointer!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover{background:transparent!important}::ng-deep .header-dropdown-container .dropdown-trigger:hover .header-year-pill{background:linear-gradient(135deg,#3b82f626,#2563eb33)!important;border-color:#3b82f666!important;transform:translateY(-1px)!important;box-shadow:0 2px 6px #3b82f626!important}::ng-deep .header-dropdown-container .dropdown-trigger:focus,::ng-deep .header-dropdown-container .dropdown-trigger:focus-visible,::ng-deep .header-dropdown-container .dropdown-trigger:active{outline:none!important;box-shadow:0 2px 6px #3b82f626!important}.profile-avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#2563eb);color:rgb(var(--tw-white-rgb) / 1);font-size:.75rem;font-weight:600;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px #2563eb33;transition:all .2s cubic-bezier(.4,0,.2,1);letter-spacing:-.5px;cursor:pointer;border:2px solid transparent}.profile-avatar:hover,.header-icon:hover .profile-avatar{transform:scale(1.08);box-shadow:0 3px 8px #2563eb4d;border-color:#3b82f64d}::ng-deep .user-profile .dropdown-trigger{background:transparent!important;border:none!important;padding:0!important;width:auto!important;height:auto!important;border-radius:0!important}::ng-deep .user-profile .dropdown-trigger:hover{background:transparent!important}::ng-deep .user-profile .dropdown-trigger:focus,::ng-deep .user-profile .dropdown-trigger:focus-visible,::ng-deep .user-profile .dropdown-trigger:active{outline:none!important;box-shadow:none!important}:root[data-theme=dark] .cide-lyt-header,:root.dark-mode .cide-lyt-header{background:linear-gradient(to right,var(--cide-theme-light-color),var(--cide-theme-hover-bg-color));border-bottom-color:var(--cide-theme-border-color);box-shadow:0 2px 8px var(--cide-theme-shadow-color)}:root[data-theme=dark] .header-icon,:root.dark-mode .header-icon{color:var(--cide-theme-text-color)}:root[data-theme=dark] .header-icon.notification-icon,:root.dark-mode .header-icon.notification-icon{color:#d1d5db}:root[data-theme=dark] .header-icon.notification-icon:hover,:root.dark-mode .header-icon.notification-icon:hover{color:#60a5fa}:root[data-theme=dark] .header-divider,:root.dark-mode .header-divider{background-color:var(--cide-theme-border-color)}:root[data-theme=dark] .header-year-pill,:root.dark-mode .header-year-pill{background:linear-gradient(135deg,#60a5fa33,#3b82f640);border-color:#60a5fa80;color:#60a5fa}:root[data-theme=dark] .header-year-pill .header-year-pill-text,:root.dark-mode .header-year-pill .header-year-pill-text{color:#60a5fa}:root[data-theme=dark] .header-year-pill cide-ele-icon,:root.dark-mode .header-year-pill cide-ele-icon{color:#60a5fa!important}:root[data-theme=dark] .header-year-pill:hover,:root.dark-mode .header-year-pill:hover{background:linear-gradient(135deg,#60a5fa4d,#3b82f659);border-color:#60a5fa99;color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover .header-year-pill-text,:root.dark-mode .header-year-pill:hover .header-year-pill-text{color:#93c5fd}:root[data-theme=dark] .header-year-pill:hover cide-ele-icon,:root.dark-mode .header-year-pill:hover cide-ele-icon{color:#93c5fd!important}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search{background-color:var(--cide-theme-hover-bg-color);border-color:var(--cide-theme-border-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search:hover,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search:hover{background-color:var(--cide-theme-light-color);box-shadow:0 3px 12px var(--cide-theme-shadow-color)}:root[data-theme=dark] ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon,:root.dark-mode ::ng-deep .header-search-container #cide_lyt_header_search .cide-input-leading-icon{color:var(--cide-theme-label-color)!important}.header-avatar{width:36px;height:36px;border-radius:50%;overflow:hidden;transition:all .2s cubic-bezier(.4,0,.2,1);border:2px solid transparent;box-shadow:0 2px 4px #0000001a}.header-avatar:hover{border-color:#3b82f6;transform:scale(1.05);box-shadow:0 3px 6px #3b82f64d}@media (max-width: 768px){.header-search-container{margin:0 1rem}.header-icons-container{gap:.5rem}}@media (max-width: 640px){.header-search-container{max-width:200px;margin:0 .5rem}}.notification-scroll-container{scrollbar-width:thin;scrollbar-color:var(--cide-ele-scrollbar-thumb, #d1d5db) var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar{width:6px;height:6px}.notification-scroll-container::-webkit-scrollbar-track{background:var(--cide-ele-scrollbar-track, transparent)}.notification-scroll-container::-webkit-scrollbar-thumb{background-color:var(--cide-ele-scrollbar-thumb, #d1d5db);border-radius:3px}.notification-scroll-container::-webkit-scrollbar-thumb:hover{background-color:var(--cide-ele-scrollbar-thumb-hover, #9ca3af)}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container{background-color:#fff!important;color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container *{color:inherit}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-900{color:#111827!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-800{color:#1f2937!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-700{color:#374151!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-600{color:#4b5563!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-gray-500{color:#6b7280!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-text-blue-600{color:#2563eb!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-white{background-color:#fff!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-gray-50{background-color:#f9fafb!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-gray-100{background-color:#f3f4f6!important}:root:not([data-theme=dark]):not(.dark-mode) .notification-scroll-container .tw-bg-blue-50{background-color:#eff6ff!important}\n"] }]
|
|
3295
3422
|
}], ctorParameters: () => [], propDecorators: { triggerTemplate: [{
|
|
3296
3423
|
type: ViewChild,
|
|
3297
3424
|
args: ['triggerTemplate']
|
|
@@ -4693,8 +4820,8 @@ class CideLytSidedrawerWrapperComponent {
|
|
|
4693
4820
|
}
|
|
4694
4821
|
ngOnInit() {
|
|
4695
4822
|
// Initialize the component map (You'd likely populate this from a config or service)
|
|
4696
|
-
this.componentMap['drowar_notes'] = () => import('./cloud-ide-layout-sidedrawer-notes.component-
|
|
4697
|
-
this.componentMap['drawer_theme'] = () => import('./cloud-ide-layout-drawer-theme.component-
|
|
4823
|
+
this.componentMap['drowar_notes'] = () => import('./cloud-ide-layout-sidedrawer-notes.component-C0wGoiPy.mjs').then(m => m.CideLytSidedrawerNotesComponent);
|
|
4824
|
+
this.componentMap['drawer_theme'] = () => import('./cloud-ide-layout-drawer-theme.component-DlUZDcaF.mjs').then(m => m.CideLytDrawerThemeComponent);
|
|
4698
4825
|
}
|
|
4699
4826
|
async loadComponent(configFor) {
|
|
4700
4827
|
console.log('🔍 SIDEDRAWER - Loading component:', configFor, 'Current tab:', this.currentTabId);
|
|
@@ -4865,6 +4992,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
4865
4992
|
class ShortcutsPanelComponent {
|
|
4866
4993
|
keyboardShortcutService = inject(KeyboardShortcutService);
|
|
4867
4994
|
sanitizer = inject(DomSanitizer);
|
|
4995
|
+
// Inject FloatingContainerShortcutsService to ensure it's initialized and shortcuts are registered
|
|
4996
|
+
floatingContainerShortcutsService = inject(FloatingContainerShortcutsService);
|
|
4868
4997
|
refreshInterval;
|
|
4869
4998
|
shortcuts = signal([], ...(ngDevMode ? [{ debugName: "shortcuts" }] : []));
|
|
4870
4999
|
searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
|
|
@@ -4887,6 +5016,8 @@ class ShortcutsPanelComponent {
|
|
|
4887
5016
|
return Array.from(cats).sort();
|
|
4888
5017
|
}, ...(ngDevMode ? [{ debugName: "categories" }] : []));
|
|
4889
5018
|
ngOnInit() {
|
|
5019
|
+
// FloatingContainerShortcutsService is now injected, so it's initialized
|
|
5020
|
+
// Load shortcuts immediately
|
|
4890
5021
|
this.loadShortcuts();
|
|
4891
5022
|
// Reload shortcuts periodically to catch dynamically registered shortcuts
|
|
4892
5023
|
// This ensures floating container shortcuts and custom shortcuts are shown
|
|
@@ -5353,7 +5484,7 @@ class SettingsContainerComponent {
|
|
|
5353
5484
|
}
|
|
5354
5485
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: SettingsContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5355
5486
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: SettingsContainerComponent, isStandalone: true, selector: "cide-lyt-settings-container", ngImport: i0, template: `
|
|
5356
|
-
<div class="tw-flex tw-h-full tw-bg-white dark:tw-bg-gray-900">
|
|
5487
|
+
<div class="tw-flex tw-h-full tw-bg-white dark:tw-bg-gray-900 tw-text-gray-900 dark:tw-text-gray-100">
|
|
5357
5488
|
<!-- Left Sidebar Navigation -->
|
|
5358
5489
|
<div class="tw-w-64 tw-border-r tw-border-gray-200 dark:tw-border-gray-700 tw-bg-gray-50 dark:tw-bg-gray-800 tw-flex tw-flex-col">
|
|
5359
5490
|
<!-- Header -->
|
|
@@ -5447,7 +5578,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
5447
5578
|
NotificationSettingsComponent,
|
|
5448
5579
|
ShortcutsPanelComponent
|
|
5449
5580
|
], template: `
|
|
5450
|
-
<div class="tw-flex tw-h-full tw-bg-white dark:tw-bg-gray-900">
|
|
5581
|
+
<div class="tw-flex tw-h-full tw-bg-white dark:tw-bg-gray-900 tw-text-gray-900 dark:tw-text-gray-100">
|
|
5451
5582
|
<!-- Left Sidebar Navigation -->
|
|
5452
5583
|
<div class="tw-w-64 tw-border-r tw-border-gray-200 dark:tw-border-gray-700 tw-bg-gray-50 dark:tw-bg-gray-800 tw-flex tw-flex-col">
|
|
5453
5584
|
<!-- Header -->
|
|
@@ -5779,6 +5910,117 @@ var cloudIdeLayout_component = /*#__PURE__*/Object.freeze({
|
|
|
5779
5910
|
CloudIdeLayoutComponent: CloudIdeLayoutComponent
|
|
5780
5911
|
});
|
|
5781
5912
|
|
|
5913
|
+
/**
|
|
5914
|
+
* Service to detect component context (tab or floating container) and close appropriately
|
|
5915
|
+
* This allows components to close themselves without knowing their context
|
|
5916
|
+
*/
|
|
5917
|
+
class ComponentContextService {
|
|
5918
|
+
requestService = inject(CideLytRequestService);
|
|
5919
|
+
floatingContainerService = inject(CideEleFloatingContainerService);
|
|
5920
|
+
router = inject(Router);
|
|
5921
|
+
route = inject(ActivatedRoute);
|
|
5922
|
+
/**
|
|
5923
|
+
* Close the current component context
|
|
5924
|
+
* - If in a tab: closes the tab
|
|
5925
|
+
* - If in a floating container: closes the floating container
|
|
5926
|
+
* - If neither: navigates to a fallback route (optional)
|
|
5927
|
+
*
|
|
5928
|
+
* @param fallbackRoute Optional route to navigate to if not in tab or floating container
|
|
5929
|
+
* @param containerId Optional specific container ID to close (if known)
|
|
5930
|
+
*/
|
|
5931
|
+
close(fallbackRoute, containerId) {
|
|
5932
|
+
// First, try to close floating container if containerId is provided
|
|
5933
|
+
if (containerId) {
|
|
5934
|
+
const containers = this.floatingContainerService.visibleContainers();
|
|
5935
|
+
const container = containers.find(c => c.id === containerId);
|
|
5936
|
+
if (container) {
|
|
5937
|
+
console.log('🔒 [ComponentContext] Closing floating container:', containerId);
|
|
5938
|
+
this.floatingContainerService.hide(containerId);
|
|
5939
|
+
return;
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
// Try to find and close any floating container that might contain this component
|
|
5943
|
+
// We check if there are any visible containers
|
|
5944
|
+
const visibleContainers = this.floatingContainerService.visibleContainers();
|
|
5945
|
+
if (visibleContainers.length > 0) {
|
|
5946
|
+
// Close the topmost container (last in array is typically the most recent)
|
|
5947
|
+
const topContainer = visibleContainers[visibleContainers.length - 1];
|
|
5948
|
+
console.log('🔒 [ComponentContext] Closing topmost floating container:', topContainer.id);
|
|
5949
|
+
this.floatingContainerService.hide(topContainer.id);
|
|
5950
|
+
return;
|
|
5951
|
+
}
|
|
5952
|
+
// Check if we're in a tab
|
|
5953
|
+
const activeTab = this.requestService.activeTab();
|
|
5954
|
+
if (activeTab) {
|
|
5955
|
+
// Get current route to match with tab
|
|
5956
|
+
const currentUrl = this.router.url.split('?')[0]; // Remove query params for comparison
|
|
5957
|
+
const tabRoute = activeTab.route;
|
|
5958
|
+
// Check if current route matches the active tab route
|
|
5959
|
+
if (currentUrl === tabRoute || currentUrl.startsWith(tabRoute + '/')) {
|
|
5960
|
+
console.log('🔒 [ComponentContext] Closing active tab:', activeTab.id);
|
|
5961
|
+
this.requestService.closeTab(activeTab.id);
|
|
5962
|
+
return;
|
|
5963
|
+
}
|
|
5964
|
+
// Check all tabs to find a match
|
|
5965
|
+
const allTabs = this.requestService.tabs();
|
|
5966
|
+
const matchingTab = allTabs.find(tab => {
|
|
5967
|
+
const tabRoutePath = tab.route.split('?')[0];
|
|
5968
|
+
return currentUrl === tabRoutePath || currentUrl.startsWith(tabRoutePath + '/');
|
|
5969
|
+
});
|
|
5970
|
+
if (matchingTab) {
|
|
5971
|
+
console.log('🔒 [ComponentContext] Closing matching tab:', matchingTab.id);
|
|
5972
|
+
this.requestService.closeTab(matchingTab.id);
|
|
5973
|
+
return;
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
// If neither tab nor floating container, navigate to fallback route
|
|
5977
|
+
if (fallbackRoute && fallbackRoute.length > 0) {
|
|
5978
|
+
console.log('🔒 [ComponentContext] Not in tab or container, navigating to fallback:', fallbackRoute);
|
|
5979
|
+
this.router.navigate(fallbackRoute, { relativeTo: this.route });
|
|
5980
|
+
}
|
|
5981
|
+
else {
|
|
5982
|
+
console.warn('⚠️ [ComponentContext] No context found to close and no fallback route provided');
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
/**
|
|
5986
|
+
* Check if component is in a tab
|
|
5987
|
+
*/
|
|
5988
|
+
isInTab() {
|
|
5989
|
+
const activeTab = this.requestService.activeTab();
|
|
5990
|
+
if (!activeTab)
|
|
5991
|
+
return false;
|
|
5992
|
+
const currentUrl = this.router.url.split('?')[0];
|
|
5993
|
+
const tabRoute = activeTab.route.split('?')[0];
|
|
5994
|
+
return currentUrl === tabRoute || currentUrl.startsWith(tabRoute + '/');
|
|
5995
|
+
}
|
|
5996
|
+
/**
|
|
5997
|
+
* Check if component is in a floating container
|
|
5998
|
+
*/
|
|
5999
|
+
isInFloatingContainer() {
|
|
6000
|
+
return this.floatingContainerService.visibleContainers().length > 0;
|
|
6001
|
+
}
|
|
6002
|
+
/**
|
|
6003
|
+
* Get the current context type
|
|
6004
|
+
*/
|
|
6005
|
+
getContextType() {
|
|
6006
|
+
if (this.isInFloatingContainer()) {
|
|
6007
|
+
return 'floating-container';
|
|
6008
|
+
}
|
|
6009
|
+
if (this.isInTab()) {
|
|
6010
|
+
return 'tab';
|
|
6011
|
+
}
|
|
6012
|
+
return 'none';
|
|
6013
|
+
}
|
|
6014
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: ComponentContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6015
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: ComponentContextService, providedIn: 'root' });
|
|
6016
|
+
}
|
|
6017
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: ComponentContextService, decorators: [{
|
|
6018
|
+
type: Injectable,
|
|
6019
|
+
args: [{
|
|
6020
|
+
providedIn: 'root'
|
|
6021
|
+
}]
|
|
6022
|
+
}] });
|
|
6023
|
+
|
|
5782
6024
|
class CideLytSharedWrapperComponent {
|
|
5783
6025
|
breadcrumb = viewChild(CideEleBreadcrumbComponent, ...(ngDevMode ? [{ debugName: "breadcrumb" }] : []));
|
|
5784
6026
|
shared_wrapper_setup_param = input({}, ...(ngDevMode ? [{ debugName: "shared_wrapper_setup_param" }] : []));
|
|
@@ -5788,6 +6030,7 @@ class CideLytSharedWrapperComponent {
|
|
|
5788
6030
|
sidedrawerService = inject(CideLytSidedrawerService);
|
|
5789
6031
|
router = inject(Router);
|
|
5790
6032
|
appState = inject(AppStateHelperService);
|
|
6033
|
+
componentContextService = inject(ComponentContextService);
|
|
5791
6034
|
// Signal for additional breadcrumb items that will be appended after Module > Current Page
|
|
5792
6035
|
additionalBreadcrumbItems = signal([], ...(ngDevMode ? [{ debugName: "additionalBreadcrumbItems" }] : []));
|
|
5793
6036
|
// Computed signal that combines default breadcrumb with additional items
|
|
@@ -5906,6 +6149,21 @@ class CideLytSharedWrapperComponent {
|
|
|
5906
6149
|
onBreadcrumbHomeClick() {
|
|
5907
6150
|
this.router.navigate(['/control-panel']);
|
|
5908
6151
|
}
|
|
6152
|
+
/**
|
|
6153
|
+
* Close the current component context
|
|
6154
|
+
* - If in a tab: closes the tab
|
|
6155
|
+
* - If in a floating container: closes the floating container
|
|
6156
|
+
* - If neither: navigates to fallback route (optional)
|
|
6157
|
+
*
|
|
6158
|
+
* This method can be called from child components to close themselves
|
|
6159
|
+
* without needing to know their context (tab vs floating container)
|
|
6160
|
+
*
|
|
6161
|
+
* @param fallbackRoute Optional route to navigate to if not in tab or floating container
|
|
6162
|
+
* @param containerId Optional specific container ID to close (if known)
|
|
6163
|
+
*/
|
|
6164
|
+
close(fallbackRoute, containerId) {
|
|
6165
|
+
this.componentContextService.close(fallbackRoute, containerId);
|
|
6166
|
+
}
|
|
5909
6167
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideLytSharedWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5910
6168
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.1.7", type: CideLytSharedWrapperComponent, isStandalone: true, selector: "cide-lyt-shared-wrapper", inputs: { shared_wrapper_setup_param: { classPropertyName: "shared_wrapper_setup_param", publicName: "shared_wrapper_setup_param", isSignal: true, isRequired: false, transformFunction: null }, breadcrumb_data: { classPropertyName: "breadcrumb_data", publicName: "breadcrumb_data", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "breadcrumb", first: true, predicate: CideEleBreadcrumbComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tw-w-full tw-h-full tw-table tw-max-w-full tw-overflow-hidden\">\n <div\n class=\"tw-sticky tw-table-row tw-w-full tw-max-w-full tw-top-0 tw-z-50 tw-bg-white tw-border-b tw-border-gray-200 tw-shadow-sm\">\n <div class=\"tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-0.5 tw-min-w-0 tw-overflow-hidden\">\n <div class=\"tw-flex-1 tw-min-w-0 tw-overflow-hidden\">\n <cide-ele-breadcrumb style=\"modern\" [compact]=\"true\" (homeClick)=\"onBreadcrumbHomeClick()\"></cide-ele-breadcrumb>\n </div>\n <div class=\"tw-flex-shrink-0 tw-ml-4\">\n <ng-content select=\"[breadcrumb-actions]\"></ng-content>\n </div>\n </div>\n </div>\n\n <div class=\"tw-table-row tw-h-full tw-w-full tw-max-w-full tw-overflow-y-auto tw-overflow-x-hidden\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [":host{display:block;height:100%}:host>div{display:flex;flex-direction:column}::ng-deep cide-lyt-shared-wrapper{height:100%!important;display:block!important;width:100%!important}\n"], dependencies: [{ kind: "component", type: CideEleBreadcrumbComponent, selector: "cide-ele-breadcrumb", inputs: ["items", "style", "separator", "showHomeIcon", "homeIcon", "maxItems", "showDropdownOnOverflow", "dropdownOptions", "clickableItems", "showTooltips", "responsive", "compact", "animated", "loadingInput", "disabled", "contextId", "pageCode"], outputs: ["itemClick", "dropdownOptionClick", "homeClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5911
6169
|
}
|
|
@@ -5926,7 +6184,7 @@ const layoutControlPannelChildRoutes = [{
|
|
|
5926
6184
|
},
|
|
5927
6185
|
{
|
|
5928
6186
|
path: "home",
|
|
5929
|
-
loadComponent: () => import('./cloud-ide-layout-home-wrapper.component-
|
|
6187
|
+
loadComponent: () => import('./cloud-ide-layout-home-wrapper.component-DVX-kibW.mjs').then(c => c.CideLytHomeWrapperComponent),
|
|
5930
6188
|
canActivate: [authGuard],
|
|
5931
6189
|
data: {
|
|
5932
6190
|
sypg_page_code: "cide_lyt_home" // Used by RequestService to fetch tab properties
|
|
@@ -7492,5 +7750,5 @@ var floatingEntityRightsSharing_component = /*#__PURE__*/Object.freeze({
|
|
|
7492
7750
|
* Generated bundle index. Do not edit.
|
|
7493
7751
|
*/
|
|
7494
7752
|
|
|
7495
|
-
export { AppStateHelperService as A, CideLytSharedWrapperComponent as C, ENVIRONMENT_CONFIG as E, NotificationSettingsService as N, CideLytSidebarService as a, CideLytRequestService as b, CideLytSidedrawerService as c, CideLytThemeService as d, AppStateService as e, CloudIdeLayoutService as f, CloudIdeLayoutComponent as g, CideLytSharedService as h,
|
|
7496
|
-
//# sourceMappingURL=cloud-ide-layout-cloud-ide-layout-
|
|
7753
|
+
export { AppStateHelperService as A, CideLytSharedWrapperComponent as C, ENVIRONMENT_CONFIG as E, NotificationSettingsService as N, CideLytSidebarService as a, CideLytRequestService as b, CideLytSidedrawerService as c, CideLytThemeService as d, AppStateService as e, CloudIdeLayoutService as f, CloudIdeLayoutComponent as g, CideLytSharedService as h, ComponentContextService as i, layoutControlPannelChildRoutes as j, CustomRouteReuseStrategy as k, layoutRoutes as l, CideLytUserStatusService as m, CacheManagerService as n, CideLytFileManagerService as o, processThemeVariable as p, CideLytFloatingEntityRightsSharingComponent as q, CideLytFloatingEntityRightsSharingService as r, setCSSVariable as s, themeFactory as t };
|
|
7754
|
+
//# sourceMappingURL=cloud-ide-layout-cloud-ide-layout-B69VSPoc.mjs.map
|