Files
arrr-erp/app/ui/templates/base/layout.html
T
2026-07-06 13:07:49 +05:30

642 lines
53 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{% set __title_user = current_user if current_user is defined else None %}
{% set __firm_branding = get_current_firm_branding(request, __title_user) %}
<meta name="theme-color" content="{{ __firm_branding.primary_color or '#1e3a8a' }}" />
{% if __firm_branding.favicon_url %}<link rel="icon" href="{{ __firm_branding.favicon_url }}" />{% endif %}
{% set __title_auth = __title_user and request.session.get("otp_verified", False) %}
{% if __title_auth %}
{% set __title_firm = get_current_tenant_name(request, __title_user) %}
{% set __title_branch = get_current_branch_name(request, __title_user) %}
<title>{{ title or "Workspace" }} | {{ __title_firm }}{% if __title_branch and __title_branch != "-" %} - {{ __title_branch }}{% endif %}</title>
{% else %}
<title>{{ title or "Welcome" }} | {{ __firm_branding.firm_name or "Audit Firm ERP" }}</title>
{% endif %}
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
50: 'var(--af-color-brand-50)',
100: 'var(--af-color-brand-100)',
200: 'var(--af-color-brand-200)',
300: 'var(--af-color-brand-300)',
400: 'var(--af-color-brand-400)',
500: 'var(--af-color-brand-500)',
600: 'var(--af-color-brand-600)',
700: 'var(--af-color-brand-700)',
800: 'var(--af-color-brand-800)',
900: 'var(--af-color-brand-900)'
},
afsuccess: {
50: 'var(--af-color-success-50)',
600: 'var(--af-color-success-600)',
700: 'var(--af-color-success-700)'
},
afwarning: {
50: 'var(--af-color-warning-50)',
600: 'var(--af-color-warning-600)',
700: 'var(--af-color-warning-700)'
},
afdanger: {
50: 'var(--af-color-danger-50)',
600: 'var(--af-color-danger-600)',
700: 'var(--af-color-danger-700)'
},
afinfo: {
50: 'var(--af-color-info-50)',
600: 'var(--af-color-info-600)',
700: 'var(--af-color-info-700)'
}
},
boxShadow: {
soft: 'var(--af-shadow-soft)',
card: 'var(--af-shadow-card)',
focus: '0 0 0 4px rgba(37, 99, 235, 0.12)'
},
borderRadius: {
card: 'var(--af-radius-card)',
control: 'var(--af-radius-control)'
}
}
}
}
</script>
<link rel="stylesheet" href="/static/css/theme_tokens.css?v=7q-dashboard-align-strong" />
<style>
:root {
--af-color-brand-500: {{ __firm_branding.primary_color or '#2563eb' }};
--af-color-brand-600: {{ __firm_branding.primary_color or '#1d4ed8' }};
--af-color-brand-700: {{ __firm_branding.accent_color or '#1e40af' }};
--af-color-brand-800: {{ __firm_branding.accent_color or '#1e3a8a' }};
--af-color-brand-900: {{ __firm_branding.accent_color or '#172554' }};
}
</style>
</head>
<body class="min-h-screen bg-slate-100 text-slate-900">
{% set otp_ok = request.session.get("otp_verified", False) %}
{% set full_auth = current_user and otp_ok %}
{% set ui_perms = current_user_permissions if full_auth else [] %}
{% set ui_roles = current_user_roles if full_auth else [] %}
{% set active_tenant_id = get_active_tenant_id(request, current_user) if full_auth else None %}
{% set active_branch_id = get_active_branch_id(request, current_user) if full_auth else None %}
{% set active_financial_year = get_active_financial_year(request, current_user) if full_auth else None %}
{% set active_assessment_year = get_active_assessment_year(request, current_user) if full_auth else None %}
{% set unread_alert_count = get_unread_alert_count(request, current_user) if full_auth else 0 %}
{% set current_path = request.url.path %}
{% set document_menu_roles = ["System Admin", "Firm Admin", "Partner"] %}
{% set can_view_documents_menu = full_auth and can_view_documents(current_user, ui_perms, ui_roles) and (ui_roles|select("in", document_menu_roles)|list|length > 0) %}
{% set management_menu_roles = ["System Admin", "Firm Admin", "Partner", "Manager", "Branch Manager"] %}
{% set can_view_management_menus = full_auth and (ui_roles|select("in", management_menu_roles)|list|length > 0) %}
{% set firm_branding = get_current_firm_branding(request, current_user) if full_auth else __firm_branding %}
{% set current_user_photo_url = get_user_profile_photo_url(current_user) if full_auth else None %}
{% set current_user_initials = get_user_initials(current_user) if full_auth else "U" %}
{% set current_firm_name = firm_branding.firm_name if firm_branding else "Audit Firm ERP" %}
{% set current_branch_name = firm_branding.branch_name if firm_branding else "" %}
{% set domain_context = get_domain_context(request) %}
{% set is_system_admin_user = full_auth and ("System Admin" in ui_roles) %}
{% set can_manage_local_storage_agent = full_auth and can_view_documents(current_user, ui_perms, ui_roles) and can_upload_documents(current_user, ui_perms, ui_roles) and (ui_roles|select("in", ["Firm Admin", "Partner", "Branch Manager"])|list|length > 0) %}
{% set clean_workspace_paths = [
'/system-admin/dashboard',
'/firm-admin/dashboard',
'/partner/dashboard',
'/manager/dashboard',
'/employee/dashboard',
'/client/dashboard',
'/consultant/dashboard',
'/workspaces'
] %}
{% set clean_nav_state = namespace(enabled=False) %}
{% for clean_path in clean_workspace_paths %}
{% if current_path.startswith(clean_path) %}{% set clean_nav_state.enabled = True %}{% endif %}
{% endfor %}
{% set use_clean_top_nav = full_auth and clean_nav_state.enabled %}
<div class="min-h-screen {% if not use_clean_top_nav %}lg:grid lg:grid-cols-[260px_minmax(0,1fr)]{% endif %}">
{% if not use_clean_top_nav %}
<aside class="border-b border-slate-200 bg-slate-900 text-slate-100 lg:min-h-screen lg:border-b-0 lg:border-r lg:border-slate-800">
<div class="flex items-center gap-3 px-5 py-5">
{% if firm_branding.logo_url %}<img src="{{ firm_branding.logo_url }}" alt="{{ current_firm_name }} logo" class="h-11 w-11 rounded-2xl bg-white object-contain p-1 shadow-soft" />{% else %}<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-brand-500 font-bold text-white shadow-soft">{{ (current_firm_name[:2] if current_firm_name else 'AF')|upper }}</div>{% endif %}
<div class="min-w-0">
<div class="truncate text-sm font-semibold tracking-wide">{{ current_firm_name }}</div>
<div class="truncate text-xs text-slate-400">{% if full_auth %}{{ current_branch_name }}{% if current_branch_name and current_branch_name != "-" %} Branch{% endif %} • Workspace{% elif domain_context.is_resolved %}{{ current_branch_name or 'Domain Workspace' }}{% else %}Secure Practice Workspace{% endif %}</div>
</div>
</div>
{% if full_auth and ('Client' in ui_roles) %}
{% set sidebar_auditor = get_client_sidebar_auditor_card(request, current_user) %}
{% if sidebar_auditor %}
<div class="mx-4 mb-4 rounded-2xl border border-slate-700 bg-slate-800/80 p-3 shadow-soft">
<div class="text-[10px] font-semibold uppercase tracking-[0.20em] text-brand-200">Your Auditor</div>
<div class="mt-3 flex items-center gap-3">
{% if sidebar_auditor.photo_url %}
<img src="{{ sidebar_auditor.photo_url }}" alt="{{ sidebar_auditor.name }}" class="h-11 w-11 rounded-xl border border-white/10 object-cover" />
{% else %}
<div class="flex h-11 w-11 items-center justify-center rounded-xl border border-white/10 bg-brand-600 text-sm font-bold text-white">{{ sidebar_auditor.initials or 'AU' }}</div>
{% endif %}
<div class="min-w-0">
<div class="truncate text-sm font-semibold text-white">{{ sidebar_auditor.name or 'Firm team' }}</div>
<div class="truncate text-xs text-slate-300">{{ sidebar_auditor.designation or 'Auditor' }}</div>
{% if sidebar_auditor.qualification %}<div class="truncate text-[11px] text-slate-400">{{ sidebar_auditor.qualification }}</div>{% endif %}
</div>
</div>
<div class="mt-3 space-y-1 text-[11px] leading-5 text-slate-300">
{% if sidebar_auditor.mobile %}<div class="truncate">Mobile: {{ sidebar_auditor.mobile }}</div>{% endif %}
{% if sidebar_auditor.email %}<a href="mailto:{{ sidebar_auditor.email }}" class="block truncate text-brand-200 hover:text-white">{{ sidebar_auditor.email }}</a>{% endif %}
</div>
</div>
{% endif %}
{% endif %}
{% if full_auth %}
<nav class="space-y-4 px-4 pb-6">
{% set has_employee_self_menu = can_view_employee_portal(current_user, ui_perms, ui_roles) or can_view_own_employee_work(current_user, ui_perms, ui_roles) or can_view_own_employee_attendance(current_user, ui_perms, ui_roles) or can_view_own_employee_leave(current_user, ui_perms, ui_roles) or can_view_own_employee_documents(current_user, ui_perms, ui_roles) or can_view_own_employee_payslips(current_user, ui_perms, ui_roles) or can_request_own_employee_offboarding(current_user, ui_perms, ui_roles) %}
{% set has_team_workspace_menu = can_manage_employee_work(current_user, ui_perms, ui_roles) or can_view_employee_progress(current_user, ui_perms, ui_roles) or can_view_all_employee_attendance(current_user, ui_perms, ui_roles) or can_view_all_employee_leave(current_user, ui_perms, ui_roles) %}
{% set has_team_admin_menu = can_view_employee_dashboard(current_user, ui_perms, ui_roles) or can_view_employees(current_user, ui_perms, ui_roles) or can_approve_employee_registrations(current_user, ui_perms, ui_roles) or can_import_employee_hr(current_user, ui_perms, ui_roles) or can_manage_employee_leave_types(current_user, ui_perms, ui_roles) or can_manage_employee_leave_balances(current_user, ui_perms, ui_roles) or can_view_all_employee_documents(current_user, ui_perms, ui_roles) or can_manage_employee_document_types(current_user, ui_perms, ui_roles) or can_view_employee_onboarding(current_user, ui_perms, ui_roles) or can_manage_employee_onboarding(current_user, ui_perms, ui_roles) or can_view_employee_offboarding(current_user, ui_perms, ui_roles) or can_manage_employee_payroll_structures(current_user, ui_perms, ui_roles) or can_run_employee_payroll(current_user, ui_perms, ui_roles) or can_view_employee_payroll(current_user, ui_perms, ui_roles) %}
{% set has_firm_users_menu = is_system_admin_user and (can_view_users(current_user, ui_perms, ui_roles) or can_manage_users(current_user, ui_perms, ui_roles) or can_view_rbac(current_user, ui_perms, ui_roles)) %}
{% set has_firm_services_menu = can_view_services(current_user, ui_perms, ui_roles) %}
{% set has_firm_clients_menu = can_view_clients(current_user, ui_perms, ui_roles) or can_manage_clients(current_user, ui_perms, ui_roles) or can_export_clients(current_user, ui_perms, ui_roles) %}
{% set has_firm_consultants_menu = can_view_consultants(current_user, ui_perms, ui_roles) or can_manage_consultants(current_user, ui_perms, ui_roles) or can_manage_consultant_service_requests(current_user, ui_perms, ui_roles) or can_manage_consultant_conversions(current_user, ui_perms, ui_roles) %}
{% set has_email_settings_menu = ("Firm Admin" in ui_roles) or is_system_admin_user %}
{% set has_firm_admin_menu = has_firm_users_menu or has_firm_services_menu or has_firm_clients_menu or has_firm_consultants_menu or can_manage_local_storage_agent or has_email_settings_menu or ("Firm Admin" in ui_roles) or is_system_admin_user %}
{% if has_employee_self_menu %}
<details class="group rounded-xl" {% if current_path.startswith('/employee') or current_path.startswith('/alerts') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>My Workspace</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
{% if can_view_employee_portal(current_user, ui_perms, ui_roles) %}
<a href="/employee/dashboard" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/employee/dashboard' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Overview</a>
{% endif %}
{% if can_view_own_employee_work(current_user, ui_perms, ui_roles) %}
<a href="/employee/work" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employee/work') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Work Board</a>
{% endif %}
{% if can_view_own_employee_attendance(current_user, ui_perms, ui_roles) %}
<a href="/employee/attendance" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employee/attendance') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Attendance</a>
{% endif %}
{% if can_view_own_employee_leave(current_user, ui_perms, ui_roles) %}
<a href="/employee/leave" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employee/leave') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Leave</a>
{% endif %}
{% if can_view_own_employee_documents(current_user, ui_perms, ui_roles) %}
<a href="/employee/documents" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employee/documents') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Documents</a>
{% endif %}
{% if can_view_own_employee_payslips(current_user, ui_perms, ui_roles) %}
<a href="/employee/payslips" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employee/payslips') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Payslips</a>
{% endif %}
<a href="/alerts" class="flex items-center justify-between rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/alerts') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}"><span>My Alert</span>{% if unread_alert_count > 0 %}<span class="rounded-full bg-brand-600 px-2 py-0.5 text-[10px] font-semibold text-white">{{ unread_alert_count }}</span>{% endif %}</a>
{% if can_request_own_employee_offboarding(current_user, ui_perms, ui_roles) %}
<a href="/employee/offboarding" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employee/offboarding') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Offboarding</a>
{% endif %}
</div>
</details>
{% endif %}
{% if 'Partner' in ui_roles %}
<details class="group rounded-xl" {% if current_path.startswith('/partner') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>Partner Workspace</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
<a href="/partner/dashboard" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/partner/dashboard' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Overview</a>
<a href="/partner/reviews" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/partner/reviews') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Review Board</a>
<a href="/partner/clients" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/partner/clients') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Clients</a>
<a href="/alerts" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/alerts') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">My Alert</a>
</div>
</details>
{% endif %}
{% if has_team_workspace_menu %}
<details class="group rounded-xl" {% if current_path.startswith('/manager') or current_path.startswith('/employees/work') or current_path.startswith('/employees/progress') or current_path.startswith('/employees/attendance') or current_path.startswith('/employees/leave') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>Team Workspace</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
{% if can_manage_employee_work(current_user, ui_perms, ui_roles) %}
<a href="/manager/dashboard" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/manager/dashboard' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Manager Dashboard</a>
<a href="/manager/work" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/manager/work') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Team Work Board</a>
<a href="/employees/work" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employees/work') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Detailed Allocation</a>
{% endif %}
{% if can_view_employee_progress(current_user, ui_perms, ui_roles) %}
<a href="/employees/progress" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employees/progress') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Engagement Progress</a>
{% endif %}
{% if can_view_all_employee_attendance(current_user, ui_perms, ui_roles) %}
<a href="/employees/attendance" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/employees/attendance') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Team Attendance</a>
{% endif %}
{% if can_view_all_employee_leave(current_user, ui_perms, ui_roles) %}
<a href="/employees/leave" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/employees/leave' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Team Leave</a>
{% endif %}
</div>
</details>
{% endif %}
{% if can_view_billing(current_user, ui_perms, ui_roles) %}
<details class="group rounded-xl" {% if current_path.startswith('/billing') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>Billing</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
<a href="/billing" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/billing' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Invoices</a>
<a href="/billing/settings" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/billing/settings') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Billing Settings</a>
{% if can_create_billing(current_user, ui_perms, ui_roles) %}<a href="/billing/new" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/billing/new' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">New Invoice</a>{% endif %}
{% if can_generate_billing_invoices(current_user, ui_perms, ui_roles) %}<a href="/billing/generate" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Generate Bills</a>{% endif %}
{% if can_view_billing_fee_structure(current_user, ui_perms, ui_roles) %}<a href="/billing/fee-structures/list" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Fee Structure</a>{% endif %}
{% if can_import_billing_fee_structure(current_user, ui_perms, ui_roles) %}<a href="/billing/fee-structures/template" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Download Fee Template</a><a href="/billing/fee-structures/import" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Import Fee Structure</a>{% endif %}
</div>
</details>
{% endif %}
{% if has_team_admin_menu %}
<details class="group rounded-xl" {% if current_path.startswith('/employees') and not (current_path.startswith('/employees/work') or current_path.startswith('/employees/progress') or current_path.startswith('/employees/attendance') or current_path == '/employees/leave') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>Team Administration</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
{% if can_view_employee_dashboard(current_user, ui_perms, ui_roles) %}<a href="/employees/dashboard" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">HR Dashboard</a>{% endif %}
{% if can_view_employees(current_user, ui_perms, ui_roles) %}<a href="/employees" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Employee Master</a>{% endif %}
{% if can_approve_employee_registrations(current_user, ui_perms, ui_roles) %}<a href="/employees/registration-requests" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Employee Registrations</a>{% endif %}
{% if can_import_employee_hr(current_user, ui_perms, ui_roles) %}<a href="/employees/imports" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">HR Imports</a>{% endif %}
{% if can_manage_employee_leave_types(current_user, ui_perms, ui_roles) or can_manage_employee_leave_balances(current_user, ui_perms, ui_roles) %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Attendance &amp; Leave Setup</div>
{% if can_manage_employee_leave_types(current_user, ui_perms, ui_roles) %}<a href="/employees/leave-types" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Leave Types</a>{% endif %}
{% if can_manage_employee_leave_balances(current_user, ui_perms, ui_roles) %}<a href="/employees/leave-balances" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Leave Balances</a>{% endif %}
{% endif %}
{% if can_view_all_employee_documents(current_user, ui_perms, ui_roles) or can_manage_employee_document_types(current_user, ui_perms, ui_roles) %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Employee Documents</div>
{% if can_view_all_employee_documents(current_user, ui_perms, ui_roles) %}<a href="/employees/documents" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Employee Documents</a>{% endif %}
{% if can_manage_employee_document_types(current_user, ui_perms, ui_roles) %}<a href="/employees/document-types" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Document Types</a>{% endif %}
{% endif %}
{% if can_view_employee_onboarding(current_user, ui_perms, ui_roles) or can_manage_employee_onboarding(current_user, ui_perms, ui_roles) or can_view_employee_offboarding(current_user, ui_perms, ui_roles) %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Employee Lifecycle</div>
{% if can_view_employee_onboarding(current_user, ui_perms, ui_roles) %}<a href="/employees/onboarding" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Employee Onboarding</a>{% endif %}
{% if can_manage_employee_onboarding(current_user, ui_perms, ui_roles) %}<a href="/employees/onboarding-checklist" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Onboarding Checklist</a>{% endif %}
{% if can_view_employee_offboarding(current_user, ui_perms, ui_roles) %}<a href="/employees/offboarding" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Employee Offboarding</a>{% endif %}
{% endif %}
{% if can_manage_employee_payroll_structures(current_user, ui_perms, ui_roles) or can_run_employee_payroll(current_user, ui_perms, ui_roles) or can_view_employee_payroll(current_user, ui_perms, ui_roles) %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Payroll</div>
{% if can_manage_employee_payroll_structures(current_user, ui_perms, ui_roles) %}<a href="/employees/payroll/structures" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Salary Structures</a>{% endif %}
{% if can_run_employee_payroll(current_user, ui_perms, ui_roles) %}<a href="/employees/payroll/runs" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Payroll Runs</a>{% endif %}
{% if can_view_employee_payroll(current_user, ui_perms, ui_roles) %}<a href="/employees/payroll/payslips" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Employee Payslips</a>{% endif %}
{% endif %}
</div>
</details>
{% endif %}
{% if has_firm_admin_menu %}
<details class="group rounded-xl" {% if current_path.startswith('/system-settings/users') or current_path.startswith('/system-settings/rbac') or current_path.startswith('/services') or current_path.startswith('/clients') or current_path.startswith('/consultants') or current_path.startswith('/email') or current_path.startswith('/notice-cases') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>Firm Administration</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
{% if "Firm Admin" in ui_roles or is_system_admin_user %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Firm Setup</div>
<a href="/system-settings/branding" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/system-settings/branding') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Branding & Identity</a>
{% if has_email_settings_menu %}<a href="/email/settings" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/email') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Email Settings</a>{% endif %}
{% endif %}
{% if has_firm_users_menu %}
<div class="pt-2 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Users</div>
{% if can_view_users(current_user, ui_perms, ui_roles) %}<a href="/system-settings/users" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/system-settings/users' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">User List</a>{% endif %}
{% if can_manage_users(current_user, ui_perms, ui_roles) %}<a href="/system-settings/users/new" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/system-settings/users/new') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Create User</a>{% endif %}
{% if can_view_rbac(current_user, ui_perms, ui_roles) %}<a href="/system-settings/rbac/roles" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Roles &amp; Permissions</a>{% endif %}
{% endif %}
{% if can_view_notice_cases(current_user, ui_perms, ui_roles) %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Compliance</div>
<a href="/notice-cases" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/notice-cases') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Notices &amp; Cases</a>
{% if can_manage_notice_cases(current_user, ui_perms, ui_roles) %}<a href="/notice-cases/new" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">New Notice / Case</a>{% endif %}
{% endif %}
{% if has_firm_services_menu %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Services</div>
<a href="/services" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Firm Services</a>
<a href="/services/catalogue" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Service Catalogue</a>
{% if is_system_admin_user %}<a href="/services/catalogue/new" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Create Catalogue Service</a>{% endif %}
<a href="/services/categories" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Service Categories</a>
{% if is_system_admin_user %}<a href="/services/defaults" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">System Default Tasks</a>{% endif %}
<a href="/services/templates" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Firm Task Templates</a>
<a href="/services/bulk-imports" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Bulk Imports</a>
{% endif %}
{% if has_firm_clients_menu %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Clients</div>
{% if can_view_clients(current_user, ui_perms, ui_roles) %}<a href="/clients" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Client List</a>{% endif %}
{% if can_manage_clients(current_user, ui_perms, ui_roles) %}<a href="/clients/new" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Add Client</a>{% endif %}
{% if can_manage_clients(current_user, ui_perms, ui_roles) %}<a href="/clients/import" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Import Clients</a>{% endif %}
{% if can_export_clients(current_user, ui_perms, ui_roles) %}<a href="/clients/export" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Export Clients</a>{% endif %}
{% endif %}
{% if has_firm_consultants_menu %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Consultants</div>
{% if can_view_consultants(current_user, ui_perms, ui_roles) %}<a href="/consultants" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Consultant List</a>{% endif %}
{% if can_manage_consultants(current_user, ui_perms, ui_roles) %}<a href="/consultants/new" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Add Consultant</a>{% endif %}
{% if can_manage_consultant_service_requests(current_user, ui_perms, ui_roles) %}<a href="/consultants/service-requests" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Service Requests</a>{% endif %}
{% if can_manage_consultant_conversions(current_user, ui_perms, ui_roles) %}<a href="/consultants/conversion-requests" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Conversions</a>{% endif %}
{% endif %}
{% if can_manage_local_storage_agent %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Local Storage</div>
<a href="/documents/storage-nodes" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/documents/storage-nodes') or current_path.startswith('/documents/branch-storage-dashboard') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Local Storage Agent</a>
{% endif %}
</div>
</details>
{% endif %}
{% if is_system_admin_user %}
<details class="group rounded-xl" {% if current_path.startswith('/domains') or current_path.startswith('/platform-billing') or current_path.startswith('/marketplace') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>Platform</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Domain Configuration</div>
<a href="/domains" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/domains' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Domain Mappings</a>
<a href="/domains/tenant-subdomains" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/domains/tenant-subdomains') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Tenant Subdomains</a>
<a href="/domains/firm-domain" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/domains/firm-domain') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Firm Domains</a>
<a href="/domains/consultant-domains" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/domains/consultant-domains') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Consultant Domains</a>
<a href="/domains/verification" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/domains/verification') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">DNS Verification</a>
<a href="/domains/ssl" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/domains/ssl') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">SSL Status</a>
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Platform Operations</div>
<a href="/email/platform-smtp" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/email/platform-smtp') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Platform SMTP</a>
<a href="/wizards/system/firm/new" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/wizards/system/firm') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Create Firm Wizard</a>
{% if can_view_platform_billing(current_user, ui_perms, ui_roles) %}<a href="/platform-billing" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Platform Billing</a>{% endif %}
{% if can_view_marketplace_leads(current_user, ui_perms, ui_roles) %}<a href="/marketplace" class="block rounded-lg px-3 py-1.5 text-sm text-slate-300 transition hover:bg-slate-800 hover:text-white">Marketplace</a>{% endif %}
</div>
</details>
{% endif %}
{% if can_view_management_menus and (can_view_settings(current_user, ui_perms, ui_roles) or can_view_audit(current_user, ui_perms, ui_roles)) %}
<details class="group rounded-xl" {% if current_path.startswith('/system-settings') %}open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between rounded-xl px-3 py-2 text-sm font-semibold transition hover:bg-slate-800">
<span>Core Setup</span>
<span class="text-xs text-slate-400 transition group-open:rotate-90"></span>
</summary>
<div class="mt-1 space-y-1 border-l border-slate-700/70 pl-3">
{% if can_view_settings(current_user, ui_perms, ui_roles) %}
<a href="/system-settings" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path == '/system-settings' %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Dashboard</a>
{% endif %}
{% if can_view_tenants(current_user, ui_perms, ui_roles) %}
<a href="/system-settings/tenants" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/system-settings/tenants') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Audit Firms</a>
{% endif %}
{% if can_view_branches(current_user, ui_perms, ui_roles) %}
<a href="/system-settings/branches" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/system-settings/branches') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Branches</a>
{% endif %}
{% if can_view_settings(current_user, ui_perms, ui_roles) %}
<a href="/system-settings/financial-years" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/system-settings/financial-years') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Financial Years</a>
{% endif %}
{% if can_view_audit(current_user, ui_perms, ui_roles) %}
<a href="/system-settings/audit-logs" class="block rounded-lg px-3 py-1.5 text-sm transition {% if current_path.startswith('/system-settings/audit-logs') %}bg-brand-600 text-white{% else %}text-slate-300 hover:bg-slate-800 hover:text-white{% endif %}">Audit Logs</a>
{% endif %}
</div>
</details>
{% endif %}
</nav>
{% endif %}
</aside>
{% endif %}
<div class="min-h-screen">
{% if use_clean_top_nav %}
{% include "ui/templates/components/top_workspace_bar.html" %}
{% else %}
<header class="border-b border-slate-200 bg-white/90 backdrop-blur">
<div class="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
<div class="flex items-start justify-between gap-4">
<div>
<h1 class="text-lg font-semibold text-slate-900">{{ title or "Module Workspace" }}</h1>
</div>
<div class="text-right text-sm">
{% if full_auth %}
<div class="flex items-start justify-end gap-3">
<div class="min-w-0">
<div class="font-medium text-slate-800">{{ current_user.full_name or current_user.email }}</div>
<div class="text-slate-500">{{ current_user.email }}</div>
{% if current_user.qualification or current_user.designation %}
<div class="text-xs text-slate-500">{{ current_user.qualification or '' }}{% if current_user.qualification and current_user.designation %} • {% endif %}{{ current_user.designation or '' }}</div>
{% endif %}
<div class="text-xs text-slate-400">
Your Firm: {{ current_firm_name }}
• Branch: {{ current_branch_name }}{% if active_financial_year %} • FY: {{ active_financial_year }}{% endif %}
</div>
{% include "ui/templates/components/workspace_switcher.html" %}
<div class="mt-2 flex justify-end gap-2">
{% if "Consultant" in ui_roles %}
<a class="inline-flex rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50" href="/consultant/profile">My Profile</a>
{% elif "Client" in ui_roles %}
<a class="inline-flex rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50" href="/client/profile">My Profile</a>
{% else %}
<a class="inline-flex rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50" href="/employee/profile">My Profile</a>
{% endif %}
<a class="inline-flex rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50" href="/change-password">Change Password</a>
<a class="inline-flex rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50" href="/logout">Logout</a>
</div>
</div>
{% if current_user_photo_url %}
<img src="{{ current_user_photo_url }}" alt="Profile photo" class="h-12 w-12 shrink-0 rounded-2xl border border-slate-200 object-cover shadow-soft">
{% else %}
<div class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-brand-600 text-sm font-bold text-white shadow-soft">{{ current_user_initials }}</div>
{% endif %}
</div>
{% else %}
{% if domain_context.is_resolved %}
<div class="mb-2 text-xs text-slate-500">{{ current_firm_name }}{% if current_branch_name %} • {{ current_branch_name }}{% endif %}</div>
{% endif %}
<a class="inline-flex rounded-lg bg-brand-600 px-3 py-2 text-sm font-medium text-white hover:bg-brand-700" href="/login">Login</a>
{% endif %}
</div>
</div>
{% if full_auth and (can_switch_service_tenant(current_user, ui_perms, ui_roles) or can_switch_service_branch(current_user, ui_perms, ui_roles) or can_switch_employee_tenant(current_user, ui_perms, ui_roles) or can_switch_employee_branch(current_user, ui_perms, ui_roles) or can_view_settings(current_user, ui_perms, ui_roles)) %}
<div class="mt-4 flex flex-wrap items-end gap-4 border-t border-slate-200 pt-4">
{% if can_switch_service_tenant(current_user, ui_perms, ui_roles) or can_switch_employee_tenant(current_user, ui_perms, ui_roles) %}
{% set context_tenants = get_context_tenants(request, current_user, ui_perms, ui_roles) %}
<div>
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Active Audit Firm Context</label>
<select onchange="if(this.value){window.location.href=this.value;}" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
{% for tenant in context_tenants %}
<option value="/system-settings/context/tenant/{{ tenant.id }}" {% if tenant.id == active_tenant_id %}selected{% endif %}>{{ tenant.name }} ({{ tenant.code }})</option>
{% endfor %}
</select>
</div>
{% endif %}
{% if can_switch_service_branch(current_user, ui_perms, ui_roles) or can_switch_employee_branch(current_user, ui_perms, ui_roles) %}
{% set context_branches = get_context_branches(request, current_user, ui_perms, ui_roles) %}
<div>
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Active Branch Context</label>
<select onchange="if(this.value){window.location.href=this.value;}" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
<option value="/system-settings/context/branch/0" {% if not active_branch_id %}selected{% endif %}>All Branches</option>
{% for branch in context_branches %}
<option value="/system-settings/context/branch/{{ branch.id }}" {% if branch.id == active_branch_id %}selected{% endif %}>{{ branch.name }} ({{ branch.code }})</option>
{% endfor %}
</select>
</div>
{% endif %}
{% if can_view_settings(current_user, ui_perms, ui_roles) %}
{% set context_financial_years = get_context_financial_years(request, current_user, ui_perms, ui_roles) %}
<div>
<label for="active_financial_year" class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Active Financial Year</label>
<select id="active_financial_year" name="active_financial_year" data-testid="active-fy-select" aria-label="Active Financial Year" onchange="if(this.value){window.location.href=this.value;}" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
{% for fy in context_financial_years %}
<option value="/system-settings/context/financial-year/{{ fy.year_code }}" {% if fy.year_code == active_financial_year %}selected{% endif %}>FY {{ fy.year_code }}{% if fy.is_current %} (Current){% endif %}{% if fy.is_locked %} - Locked{% endif %}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="text-xs text-slate-500">
Active Scope:
{{ current_firm_name }}
• {{ current_branch_name if active_branch_id else "All Branches" }}{% if active_financial_year %} • FY {{ active_financial_year }}{% endif %}
</div>
</div>
{% endif %}
</div>
</header>
{% endif %}
<main class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
{% if flash %}
<div class="mb-6 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900 shadow-soft">
{{ flash }}
</div>
{% endif %}
{% block content %}{% endblock %}
</main>
</div>
</div>
{% if full_auth %}
<div id="af-alert-toast-container" class="fixed right-4 top-4 z-50 flex w-[min(24rem,calc(100vw-2rem))] flex-col gap-3"></div>
<script>
(function () {
const POLL_URL = "/alerts/poll";
const POLL_INTERVAL_MS = 45000;
const STORAGE_KEY = "af_popup_seen_alert_ids_v1";
const MAX_STORED_IDS = 100;
function readSeenIds() {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : [];
return Array.isArray(parsed) ? parsed.map(String) : [];
} catch (err) {
return [];
}
}
function writeSeenIds(ids) {
try {
const unique = Array.from(new Set(ids.map(String))).slice(-MAX_STORED_IDS);
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(unique));
} catch (err) {
// Ignore localStorage issues; alerts page remains available.
}
}
function updateHeaderCount(count) {
const badge = document.getElementById("af-alerts-header-count");
if (!badge) return;
const n = Number(count || 0);
badge.textContent = String(n);
if (n > 0) {
badge.classList.remove("hidden");
} else {
badge.classList.add("hidden");
}
}
function priorityClasses(priority) {
if (priority === "critical") return "border-red-200 bg-red-50 text-red-900";
if (priority === "high") return "border-amber-200 bg-amber-50 text-amber-900";
if (priority === "low") return "border-slate-200 bg-white text-slate-800";
return "border-blue-200 bg-blue-50 text-blue-900";
}
function showToast(alert) {
const container = document.getElementById("af-alert-toast-container");
if (!container || !alert) return;
const wrapper = document.createElement("div");
wrapper.className = "overflow-hidden rounded-2xl border shadow-soft " + priorityClasses(alert.priority);
wrapper.innerHTML = `
<div class="flex items-start gap-3 p-4">
<div class="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-white/80 text-base">🔔</div>
<div class="min-w-0 flex-1">
<div class="text-xs font-semibold uppercase tracking-wide opacity-75">New Alert</div>
<div class="mt-0.5 line-clamp-2 text-sm font-semibold"></div>
<div class="mt-1 line-clamp-3 text-xs opacity-80"></div>
<div class="mt-3 flex items-center gap-2">
<a class="inline-flex rounded-lg bg-white/90 px-3 py-1.5 text-xs font-semibold text-slate-800 hover:bg-white" href="#">View</a>
<button type="button" class="inline-flex rounded-lg px-3 py-1.5 text-xs font-semibold opacity-70 hover:bg-white/50">Dismiss</button>
</div>
</div>
</div>`;
wrapper.querySelector(".text-sm.font-semibold").textContent = alert.title || "Alert";
wrapper.querySelector(".text-xs.opacity-80").textContent = alert.message || "You have a new notification.";
const viewLink = wrapper.querySelector("a");
viewLink.href = alert.target_url || "/alerts";
wrapper.querySelector("button").addEventListener("click", function () { wrapper.remove(); });
container.prepend(wrapper);
window.setTimeout(function () {
if (wrapper && wrapper.parentNode) wrapper.remove();
}, 12000);
}
async function pollAlerts() {
try {
const response = await fetch(POLL_URL, {
method: "GET",
credentials: "same-origin",
headers: { "Accept": "application/json" }
});
if (!response.ok) return;
const data = await response.json();
updateHeaderCount(data.unread_count || 0);
const alerts = Array.isArray(data.alerts) ? data.alerts : [];
if (!alerts.length) return;
const seen = readSeenIds();
const newlySeen = seen.slice();
alerts.reverse().forEach(function (alert) {
const id = String(alert.id);
if (!seen.includes(id)) {
showToast(alert);
newlySeen.push(id);
}
});
writeSeenIds(newlySeen);
} catch (err) {
// Keep polling silent; the /alerts page remains the fallback.
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
pollAlerts();
window.setInterval(pollAlerts, POLL_INTERVAL_MS);
});
} else {
pollAlerts();
window.setInterval(pollAlerts, POLL_INTERVAL_MS);
}
})();
</script>
{% endif %}
</body>
</html>