c03d2a97ab
- Insurance overview page (/insurance): current policies table with type, provider, premium, franchise, coverage, and document links - Documents page: upload and manage insurance documents - Analysis page: coverage gap analysis per insurance type - Priminfo integration (/insurance/priminfo): KVG premium comparison by insurer, model (TAR/HMO/etc.), franchise level, and accident coverage via embedded Priminfo iframe (no public API available) - Backend: Insurance, PraemienEntry, PraemienPolice models with migrations - Sidebar: insurance nav group with flyout and dropdown - i18n: all keys in DE/EN/FR/IT
332 lines
26 KiB
HTML
332 lines
26 KiB
HTML
<!-- Backdrop: closes flyout when clicking outside -->
|
|
@if (sidebarService.openFlyout()) {
|
|
<div class="fixed inset-0 z-40" (click)="sidebarService.closeFlyout()"></div>
|
|
}
|
|
|
|
<aside id="default-sidebar"
|
|
[class]="(sidebarService.collapsed() ? 'w-16' : 'w-64') + (sidebarService.mobileOpen() ? ' translate-x-0' : ' -translate-x-full lg:translate-x-0')"
|
|
class="fixed top-0 left-0 z-40 h-screen pt-14 bg-white border-r border-gray-200 dark:bg-gray-800 dark:border-gray-700 transition-all duration-300"
|
|
aria-label="Sidenav">
|
|
|
|
<div [class]="sidebarService.collapsed() ? 'overflow-visible' : 'overflow-y-auto'" class="py-5 px-3 h-full flex flex-col">
|
|
|
|
<ul class="space-y-2 flex-1">
|
|
|
|
<!-- Dashboard -->
|
|
<li>
|
|
<a routerLink="/dashboard" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300"
|
|
(click)="sidebarService.closeMobile()"
|
|
[class]="sidebarService.collapsed() ? 'justify-center relative' : ''"
|
|
class="flex items-center p-2 text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path>
|
|
<path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z"></path>
|
|
</svg>
|
|
@if (!sidebarService.collapsed()) {
|
|
<span class="ml-3 whitespace-nowrap">{{ 'sidebar.dashboard' | translate }}</span>
|
|
}
|
|
@if (sidebarService.collapsed()) {
|
|
<span class="pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-50">
|
|
{{ 'sidebar.dashboard' | translate }}
|
|
</span>
|
|
}
|
|
</a>
|
|
</li>
|
|
|
|
<!-- Budgets -->
|
|
<li class="relative">
|
|
@if (sidebarService.collapsed()) {
|
|
<!-- Collapsed: icon button opens flyout -->
|
|
<button (click)="sidebarService.toggleFlyout('budgets')"
|
|
[class]="sidebarService.openFlyout() === 'budgets' ? 'bg-gray-100 dark:bg-gray-700' : ''"
|
|
class="relative flex items-center justify-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z"></path>
|
|
<path fill-rule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
@if (sidebarService.openFlyout() !== 'budgets') {
|
|
<span class="pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-50">
|
|
{{ 'sidebar.budgets' | translate }}
|
|
</span>
|
|
}
|
|
</button>
|
|
<!-- Flyout -->
|
|
@if (sidebarService.openFlyout() === 'budgets') {
|
|
<div class="absolute left-full top-0 ml-2 z-50 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1">
|
|
<p class="px-3 py-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">{{ 'sidebar.budgets' | translate }}</p>
|
|
<a routerLink="/budgets" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.fixed_costs' | translate }}
|
|
</a>
|
|
<a routerLink="/expenses" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.expenses' | translate }}
|
|
</a>
|
|
</div>
|
|
}
|
|
} @else {
|
|
<!-- Expanded: Angular-controlled dropdown -->
|
|
<button type="button" (click)="sidebarService.toggleBudgets()"
|
|
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z"></path>
|
|
<path fill-rule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
<span class="flex-1 ml-3 text-left whitespace-nowrap">{{ 'sidebar.budgets' | translate }}</span>
|
|
<svg [class.rotate-180]="sidebarService.budgetsOpen()" class="w-4 h-4 transition-transform duration-200" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
@if (sidebarService.budgetsOpen()) {
|
|
<ul class="py-2 space-y-2">
|
|
<li><a routerLink="/budgets" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.fixed_costs' | translate }}</a></li>
|
|
<li><a routerLink="/expenses" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.expenses' | translate }}</a></li>
|
|
</ul>
|
|
}
|
|
}
|
|
</li>
|
|
|
|
<!-- Calendar -->
|
|
<li>
|
|
<a routerLink="/calendar" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300"
|
|
(click)="sidebarService.closeMobile()"
|
|
[class]="sidebarService.collapsed() ? 'justify-center relative' : ''"
|
|
class="flex items-center p-2 text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
@if (!sidebarService.collapsed()) {
|
|
<span class="ml-3 whitespace-nowrap">{{ 'sidebar.calendar' | translate }}</span>
|
|
}
|
|
@if (sidebarService.collapsed()) {
|
|
<span class="pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-50">
|
|
{{ 'sidebar.calendar' | translate }}
|
|
</span>
|
|
}
|
|
</a>
|
|
</li>
|
|
|
|
<!-- Accounts -->
|
|
<li class="relative">
|
|
@if (sidebarService.collapsed()) {
|
|
<!-- Collapsed: icon button opens flyout -->
|
|
<button (click)="sidebarService.toggleFlyout('accounts')"
|
|
[class]="sidebarService.openFlyout() === 'accounts' ? 'bg-gray-100 dark:bg-gray-700' : ''"
|
|
class="relative flex items-center justify-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
@if (sidebarService.openFlyout() !== 'accounts') {
|
|
<span class="pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-50">
|
|
{{ 'sidebar.accounts' | translate }}
|
|
</span>
|
|
}
|
|
</button>
|
|
<!-- Flyout -->
|
|
@if (sidebarService.openFlyout() === 'accounts') {
|
|
<div class="absolute left-full top-0 ml-2 z-50 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1">
|
|
<p class="px-3 py-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">{{ 'sidebar.accounts' | translate }}</p>
|
|
<a routerLink="/accounts" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.revenue_accounts' | translate }}
|
|
</a>
|
|
<a routerLink="/transactions" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.transactions' | translate }}
|
|
</a>
|
|
</div>
|
|
}
|
|
} @else {
|
|
<!-- Expanded: Angular-controlled dropdown -->
|
|
<button type="button" (click)="sidebarService.toggleAccounts()"
|
|
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
<span class="flex-1 ml-3 text-left whitespace-nowrap">{{ 'sidebar.accounts' | translate }}</span>
|
|
<svg [class.rotate-180]="sidebarService.accountsOpen()" class="w-4 h-4 transition-transform duration-200" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
@if (sidebarService.accountsOpen()) {
|
|
<ul class="py-2 space-y-2">
|
|
<li><a routerLink="/accounts" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.revenue_accounts' | translate }}</a></li>
|
|
<li><a routerLink="/transactions" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.transactions' | translate }}</a></li>
|
|
</ul>
|
|
}
|
|
}
|
|
</li>
|
|
|
|
<!-- Versicherungen -->
|
|
<li class="relative">
|
|
@if (sidebarService.collapsed()) {
|
|
<!-- Collapsed: icon button opens flyout -->
|
|
<button (click)="sidebarService.toggleFlyout('insurance')"
|
|
[class]="sidebarService.openFlyout() === 'insurance' ? 'bg-gray-100 dark:bg-gray-700' : ''"
|
|
class="relative flex items-center justify-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 24 24">
|
|
<path fill-rule="evenodd" d="M11.644 3.066a1 1 0 0 1 .712 0l7 2.666A1 1 0 0 1 20 6.68a17.694 17.694 0 0 1-2.023 7.98 17.406 17.406 0 0 1-5.402 6.158 1 1 0 0 1-1.15 0 17.405 17.405 0 0 1-5.403-6.157A17.695 17.695 0 0 1 4 6.68a1 1 0 0 1 .644-.949l7-2.666Zm4.014 7.187a1 1 0 0 0-1.316-1.506l-3.296 2.884-.839-.838a1 1 0 0 0-1.414 1.414l1.5 1.5a1 1 0 0 0 1.366.046l4-3.5Z" clip-rule="evenodd"/>
|
|
</svg>
|
|
@if (sidebarService.openFlyout() !== 'insurance') {
|
|
<span class="pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-50">
|
|
{{ 'sidebar.insurance' | translate }}
|
|
</span>
|
|
}
|
|
</button>
|
|
<!-- Flyout -->
|
|
@if (sidebarService.openFlyout() === 'insurance') {
|
|
<div class="absolute left-full top-0 ml-2 z-50 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1">
|
|
<p class="px-3 py-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">{{ 'sidebar.insurance' | translate }}</p>
|
|
<a routerLink="/insurance" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.insurance_overview' | translate }}
|
|
</a>
|
|
<a routerLink="/insurance-documents" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.insurance_documents' | translate }}
|
|
</a>
|
|
<a routerLink="/insurance-analyse" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.insurance_analyse' | translate }}
|
|
</a>
|
|
<a routerLink="/insurance-priminfo" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
|
|
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
|
|
{{ 'sidebar.insurance_priminfo' | translate }}
|
|
</a>
|
|
</div>
|
|
}
|
|
} @else {
|
|
<!-- Expanded: Angular-controlled dropdown -->
|
|
<button type="button" (click)="sidebarService.toggleInsurance()"
|
|
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 24 24">
|
|
<path fill-rule="evenodd" d="M11.644 3.066a1 1 0 0 1 .712 0l7 2.666A1 1 0 0 1 20 6.68a17.694 17.694 0 0 1-2.023 7.98 17.406 17.406 0 0 1-5.402 6.158 1 1 0 0 1-1.15 0 17.405 17.405 0 0 1-5.403-6.157A17.695 17.695 0 0 1 4 6.68a1 1 0 0 1 .644-.949l7-2.666Zm4.014 7.187a1 1 0 0 0-1.316-1.506l-3.296 2.884-.839-.838a1 1 0 0 0-1.414 1.414l1.5 1.5a1 1 0 0 0 1.366.046l4-3.5Z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<span class="flex-1 ml-3 text-left whitespace-nowrap">{{ 'sidebar.insurance' | translate }}</span>
|
|
<svg [class.rotate-180]="sidebarService.insuranceOpen()" class="w-4 h-4 transition-transform duration-200" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
@if (sidebarService.insuranceOpen()) {
|
|
<ul class="py-2 space-y-2">
|
|
<li><a routerLink="/insurance" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.insurance_overview' | translate }}</a></li>
|
|
<li><a routerLink="/insurance-documents" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.insurance_documents' | translate }}</a></li>
|
|
<li><a routerLink="/insurance-analyse" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.insurance_analyse' | translate }}</a></li>
|
|
<li><a routerLink="/insurance-priminfo" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.insurance_priminfo' | translate }}</a></li>
|
|
</ul>
|
|
}
|
|
}
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
<!-- Mobile: Notifications, Theme, Profile, Logout (hidden on desktop — those are in the navbar) -->
|
|
<div class="lg:hidden mt-4 border-t border-gray-200 dark:border-gray-700 pt-2 space-y-0.5">
|
|
|
|
<!-- Notifications -->
|
|
<button type="button" (click)="notifOpen = !notifOpen"
|
|
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<div class="relative flex-shrink-0">
|
|
<!-- Flowbite: outline/general/bell -->
|
|
<svg class="w-6 h-6 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5.365V3m0 2.365a5.338 5.338 0 0 1 5.133 5.368v1.8c0 2.386 1.867 2.982 1.867 4.175 0 .593 0 1.292-.538 1.292H5.538C5 18 5 17.301 5 16.708c0-1.193 1.867-1.789 1.867-4.175v-1.8A5.338 5.338 0 0 1 12 5.365ZM8.733 18c.094.852.306 1.54.944 2.112a3.48 3.48 0 0 0 4.646 0c.638-.572 1.236-1.26 1.33-2.112h-6.92Z"/>
|
|
</svg>
|
|
@if (notifService.notifications().length > 0) {
|
|
<span class="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white dark:border-gray-800"></span>
|
|
}
|
|
</div>
|
|
<span class="ml-3 flex-1 text-left">{{ 'nav.notifications' | translate }}</span>
|
|
@if (notifService.notifications().length > 0) {
|
|
<span class="text-xs font-medium text-white bg-red-500 rounded-full px-2 py-0.5">{{ notifService.notifications().length }}</span>
|
|
}
|
|
</button>
|
|
|
|
@if (notifOpen) {
|
|
@if (notifService.notifications().length === 0) {
|
|
<p class="px-4 py-3 text-sm text-center text-gray-400 dark:text-gray-500">{{ 'nav.no_notifications' | translate }}</p>
|
|
} @else {
|
|
<ul class="mb-1 divide-y divide-gray-100 dark:divide-gray-700 border border-gray-100 dark:border-gray-700 rounded-lg overflow-hidden">
|
|
@for (n of notifService.notifications(); track n.event_id + n.event_type) {
|
|
<li class="flex items-start justify-between gap-2 px-3 py-2.5 hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
<button (click)="openNotification(n)" class="flex items-start gap-2 flex-1 text-left">
|
|
<span class="mt-1.5 w-2 h-2 rounded-full flex-shrink-0"
|
|
[class]="n.event_type === 'deadline' ? 'bg-blue-400' : 'bg-violet-400'"></span>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white leading-tight">{{ n.title }}</p>
|
|
<p class="text-xs text-gray-400 mt-0.5">{{ n.date }}</p>
|
|
</div>
|
|
</button>
|
|
<button (click)="notifService.markRead(n)" class="text-gray-300 hover:text-gray-500 dark:hover:text-gray-300 flex-shrink-0 mt-0.5">
|
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/>
|
|
</svg>
|
|
</button>
|
|
</li>
|
|
}
|
|
</ul>
|
|
}
|
|
}
|
|
|
|
<!-- Dark/Light Toggle -->
|
|
<button (click)="themeService.toggle()" type="button"
|
|
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
@if (themeService.isDark()) {
|
|
<!-- Flowbite: solid/weather/sun -->
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 24 24">
|
|
<path fill-rule="evenodd" d="M13 3a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0V3ZM6.343 4.929A1 1 0 0 0 4.93 6.343l1.414 1.414a1 1 0 0 0 1.414-1.414L6.343 4.929Zm12.728 1.414a1 1 0 0 0-1.414-1.414l-1.414 1.414a1 1 0 0 0 1.414 1.414l1.414-1.414ZM12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10Zm-9 4a1 1 0 1 0 0 2h2a1 1 0 1 0 0-2H3Zm16 0a1 1 0 1 0 0 2h2a1 1 0 1 0 0-2h-2ZM7.757 17.657a1 1 0 1 0-1.414-1.414l-1.414 1.414a1 1 0 1 0 1.414 1.414l1.414-1.414Zm9.9-1.414a1 1 0 0 0-1.414 1.414l1.414 1.414a1 1 0 0 0 1.414-1.414l-1.414-1.414ZM13 19a1 1 0 1 0-2 0v2a1 1 0 1 0 2 0v-2Z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<span class="ml-3">{{ 'nav.light_mode' | translate }}</span>
|
|
} @else {
|
|
<!-- Flowbite: solid/weather/moon -->
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 24 24">
|
|
<path fill-rule="evenodd" d="M11.675 2.015a.998.998 0 0 0-.403.011C6.09 2.4 2 6.722 2 12c0 5.523 4.477 10 10 10 4.356 0 8.058-2.784 9.43-6.667a1 1 0 0 0-1.02-1.33c-.08.006-.105.005-.127.005h-.001l-.028-.002A5.227 5.227 0 0 0 20 14a8 8 0 0 1-8-8c0-.952.121-1.752.404-2.558a.996.996 0 0 0 .096-.428V3a1 1 0 0 0-.825-.985Z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<span class="ml-3">{{ 'nav.dark_mode' | translate }}</span>
|
|
}
|
|
</button>
|
|
|
|
<!-- Profile -->
|
|
<a routerLink="/profile" (click)="sidebarService.closeMobile()"
|
|
class="flex items-center p-2 text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
<div class="flex items-center justify-center w-6 h-6 rounded-full text-white text-xs font-bold overflow-hidden flex-shrink-0"
|
|
[style.background]="avatarImageUrl() ? 'transparent' : avatarColor()">
|
|
@if (avatarImageUrl()) {
|
|
<img [src]="avatarImageUrl()" alt="Avatar" class="w-6 h-6 rounded-full object-cover" />
|
|
} @else {
|
|
{{ initials() }}
|
|
}
|
|
</div>
|
|
<span class="ml-3">{{ 'nav.profile' | translate }}</span>
|
|
</a>
|
|
|
|
<!-- Settings -->
|
|
<a routerLink="/settings" (click)="sidebarService.closeMobile()"
|
|
class="flex items-center p-2 text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 0 1-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 0 1 .947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 0 1 2.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 0 1 2.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 0 1 .947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 0 1-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 0 1-2.287-.947zM10 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<span class="ml-3">{{ 'nav.settings' | translate }}</span>
|
|
</a>
|
|
|
|
<!-- Logout -->
|
|
<button (click)="logout()"
|
|
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
|
<!-- Flowbite: outline/arrows/arrow-right-to-bracket -->
|
|
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="none" viewBox="0 0 24 24">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H8m12 0-4 4m4-4-4-4M9 4H7a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h2"/>
|
|
</svg>
|
|
<span class="ml-3">{{ 'nav.sign_out' | translate }}</span>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<!-- Version -->
|
|
@if (!sidebarService.collapsed()) {
|
|
<div class="pt-5 mt-5 border-t border-gray-200 dark:border-gray-700 px-2">
|
|
<p class="text-xs text-gray-400 dark:text-gray-500">Version 1.1.0</p>
|
|
</div>
|
|
}
|
|
|
|
</div>
|
|
</aside>
|