Prepare ERP source for Gitea deployment

This commit is contained in:
A R R R Associates
2026-06-20 15:01:44 +05:30
commit 5c75eb6bd9
450 changed files with 67698 additions and 0 deletions
+617
View File
@@ -0,0 +1,617 @@
<!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) %}
<div class="min-h-screen lg:grid lg:grid-cols-[260px_minmax(0,1fr)]">
<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>
{% if can_view_platform_billing(current_user, ui_perms, ui_roles) or can_view_marketplace_leads(current_user, ui_perms, ui_roles) %}
<div class="pt-3 text-[10px] font-semibold uppercase tracking-[0.16em] text-slate-500">Platform Operations</div>
{% 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 %}
{% 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>
<div class="min-h-screen">
<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>
<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 class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Active Financial Year</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 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>
<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>