Rollback unstable UIUX Phase 1 navigation changes

This commit is contained in:
A R R R Associates
2026-07-10 13:59:57 +05:30
parent 933a1e4a36
commit 18cfb0b8a7
12 changed files with 174 additions and 429 deletions
@@ -1 +1,12 @@
{# Replaced by the permanent permission-aware navigation in base/layout.html. #}
<div class="mb-6 overflow-x-auto rounded-2xl border border-slate-200 bg-white p-2 shadow-soft">
<div class="flex min-w-max items-center gap-2">
{% set path = request.url.path %}
<a href="/client/dashboard" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path == '/client/dashboard' else 'text-slate-700 hover:bg-slate-100' }}">Overview</a>
<a href="/client/compliance" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/compliance') or path.startswith('/client/engagements') else 'text-slate-700 hover:bg-slate-100' }}">My Compliance</a>
<a href="/client/documents" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/documents') else 'text-slate-700 hover:bg-slate-100' }}">My Documents</a>
<a href="/client/messages" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/messages') else 'text-slate-700 hover:bg-slate-100' }}">My Messages</a>
<a href="/client/billing" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/billing') else 'text-slate-700 hover:bg-slate-100' }}">My Bills</a>
<a href="/client/profile" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path.startswith('/client/profile') else 'text-slate-700 hover:bg-slate-100' }}">My Profile</a>
<a href="/alerts" class="rounded-xl px-4 py-2 text-sm font-semibold transition {{ 'bg-brand-600 text-white' if path == '/alerts' else 'text-slate-700 hover:bg-slate-100' }}">My Alert</a>
</div>
</div>
@@ -1 +1,14 @@
{# Replaced by the permanent permission-aware navigation in base/layout.html. #}
{% set path = request.url.path %}
<div class="mb-5 overflow-x-auto rounded-2xl border border-slate-200 bg-white p-2 shadow-soft">
<nav class="flex min-w-max gap-2 text-sm font-semibold">
<a href="/consultant/dashboard" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path == '/consultant/dashboard' else 'text-slate-700 hover:bg-slate-100' }}">Overview</a>
<a href="/consultant/work" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/consultant/work') or path.startswith('/consultant/assignments') else 'text-slate-700 hover:bg-slate-100' }}">My Work Board</a>
<a href="/consultant/communications" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/consultant/communications') else 'text-slate-700 hover:bg-slate-100' }}">My Messages</a>
<a href="/consultant/documents" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/consultant/documents') else 'text-slate-700 hover:bg-slate-100' }}">Shared Documents</a>
<a href="/consultant/service-requests" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/consultant/service-requests') else 'text-slate-700 hover:bg-slate-100' }}">Service Requests</a>
<a href="/consultant/managed-clients" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/consultant/managed-clients') else 'text-slate-700 hover:bg-slate-100' }}">Managed Clients</a>
<a href="/consultant/workspace" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/consultant/workspace') else 'text-slate-700 hover:bg-slate-100' }}">Workspace</a>
<a href="/consultant/profile" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/consultant/profile') else 'text-slate-700 hover:bg-slate-100' }}">My Profile</a>
<a href="/alerts" class="rounded-xl px-3 py-2 {{ 'bg-slate-900 text-white' if path.startswith('/alerts') else 'text-slate-700 hover:bg-slate-100' }}">My Alert</a>
</nav>
</div>
@@ -1 +1,38 @@
{# Replaced by the permanent permission-aware navigation in base/layout.html. #}
{% set current_path = request.url.path %}
{% set _tab_base = "inline-flex shrink-0 items-center rounded-xl px-3 py-2 text-sm font-semibold whitespace-nowrap transition" %}
{% set _tab_active = "bg-brand-600 text-white shadow-soft" %}
{% set _tab_idle = "border border-slate-300 bg-white text-slate-700 hover:bg-slate-50" %}
<div class="rounded-2xl border border-slate-200 bg-white p-3 shadow-soft">
<div class="mb-2 px-1 text-xs font-semibold uppercase tracking-wide text-slate-500">My Workspace</div>
<div class="overflow-x-auto pb-1">
<div class="flex min-w-max flex-nowrap gap-2">
{% if can_view_employee_portal(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/dashboard" class="{{ _tab_base }} {{ _tab_active if current_path == '/employee/dashboard' else _tab_idle }}">Overview</a>
{% endif %}
{% if can_view_own_employee_work(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/work" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/employee/work') else _tab_idle }}">My Work Board</a>
{% endif %}
{% if can_view_own_employee_attendance(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/attendance" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/employee/attendance') else _tab_idle }}">My Attendance</a>
{% endif %}
{% if can_view_employee_portal(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/profile" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/employee/profile') else _tab_idle }}">My Profile</a>
{% endif %}
{% if can_view_own_employee_leave(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/leave" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/employee/leave') else _tab_idle }}">My Leave</a>
{% endif %}
{% if can_view_own_employee_documents(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/documents" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/employee/documents') else _tab_idle }}">My Documents</a>
{% endif %}
{% if can_view_own_employee_payslips(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/payslips" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/employee/payslips') else _tab_idle }}">My Payslips</a>
{% endif %}
{% if can_request_own_employee_offboarding(current_user, current_user_permissions, current_user_roles) %}
<a href="/employee/offboarding" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/employee/offboarding') else _tab_idle }}">My Offboarding</a>
{% endif %}
<a href="/alerts" class="{{ _tab_base }} {{ _tab_active if current_path.startswith('/alerts') else _tab_idle }}">
My Alert{% if unread_alert_count is defined and unread_alert_count > 0 %}<span class="ml-2 rounded-full bg-white/20 px-2 py-0.5 text-[10px]">{{ unread_alert_count }}</span>{% endif %}
</a>
</div>
</div>
</div>
@@ -1 +1,12 @@
{# Replaced by the permanent permission-aware navigation in base/layout.html. #}
{% set manager_path = request.url.path %}
<div class="mb-6 overflow-x-auto rounded-2xl border border-slate-200 bg-white p-2 shadow-soft">
<div class="flex min-w-max items-center gap-2">
<a href="/manager/dashboard" class="rounded-xl px-4 py-2 text-sm font-semibold transition {% if manager_path == '/manager/dashboard' %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Overview</a>
<a href="/manager/work" class="rounded-xl px-4 py-2 text-sm font-semibold transition {% if manager_path.startswith('/manager/work') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Team Work Board</a>
<a href="/employees/work" class="rounded-xl px-4 py-2 text-sm font-semibold transition {% if manager_path.startswith('/employees/work') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Detailed Allocation</a>
<a href="/employees/progress" class="rounded-xl px-4 py-2 text-sm font-semibold transition {% if manager_path.startswith('/employees/progress') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Engagement Progress</a>
<a href="/employees/attendance" class="rounded-xl px-4 py-2 text-sm font-semibold transition {% if manager_path.startswith('/employees/attendance') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Team Attendance</a>
<a href="/employees/leave" class="rounded-xl px-4 py-2 text-sm font-semibold transition {% if manager_path.startswith('/employees/leave') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Team Leave</a>
<a href="/alerts" class="rounded-xl px-4 py-2 text-sm font-semibold transition {% if manager_path.startswith('/alerts') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">My Alert</a>
</div>
</div>
@@ -1 +1,12 @@
{# Replaced by the permanent permission-aware navigation in base/layout.html. #}
{% set path = request.url.path %}
<div class="mb-6 overflow-x-auto rounded-2xl border border-slate-200 bg-white p-2 shadow-soft">
<div class="flex min-w-max gap-2 text-sm font-medium">
<a href="/partner/dashboard" class="rounded-xl px-4 py-2 transition {% if path == '/partner/dashboard' %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Overview</a>
<a href="/partner/reviews" class="rounded-xl px-4 py-2 transition {% if path.startswith('/partner/reviews') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">Review Board</a>
<a href="/partner/clients" class="rounded-xl px-4 py-2 transition {% if path.startswith('/partner/clients') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">My Clients</a>
<a href="/services/engagements" class="rounded-xl px-4 py-2 text-slate-700 transition hover:bg-slate-100">Engagements</a>
<a href="/documents" class="rounded-xl px-4 py-2 text-slate-700 transition hover:bg-slate-100">Documents</a>
<a href="/billing" class="rounded-xl px-4 py-2 text-slate-700 transition hover:bg-slate-100">Billing</a>
<a href="/alerts" class="rounded-xl px-4 py-2 transition {% if path.startswith('/alerts') %}bg-brand-600 text-white{% else %}text-slate-700 hover:bg-slate-100{% endif %}">My Alert</a>
</div>
</div>
-98
View File
@@ -1,98 +0,0 @@
(function () {
'use strict';
function textOf(el) {
return (el.textContent || '').replace(/\s+/g, ' ').trim();
}
function prepareMobileNavigation() {
var desktop = document.querySelector('nav[aria-label="Primary navigation"]');
var drawer = document.getElementById('af-mobile-nav');
var target = drawer && drawer.querySelector('[data-af-mobile-links]');
var open = document.querySelector('[data-af-mobile-open]');
if (!desktop || !drawer || !target || !open) return;
var clone = desktop.cloneNode(true);
clone.className = 'block';
clone.removeAttribute('aria-label');
clone.querySelectorAll('.af-nav-menu').forEach(function (menu) {
menu.classList.remove('af-nav-menu');
menu.classList.add('ml-3', 'mt-1', 'space-y-1', 'border-l', 'border-slate-200', 'pl-3');
});
clone.querySelectorAll('.af-nav-dropdown').forEach(function (item) {
item.classList.add('rounded-xl', 'border', 'border-slate-200', 'p-1');
});
clone.querySelectorAll('.af-nav-link, .af-nav-summary').forEach(function (item) {
item.classList.add('block', 'w-full');
});
target.appendChild(clone);
function show() {
drawer.classList.remove('hidden');
drawer.setAttribute('aria-hidden', 'false');
document.documentElement.classList.add('overflow-hidden');
}
function hide() {
drawer.classList.add('hidden');
drawer.setAttribute('aria-hidden', 'true');
document.documentElement.classList.remove('overflow-hidden');
}
open.addEventListener('click', show);
drawer.querySelectorAll('[data-af-mobile-close]').forEach(function (button) { button.addEventListener('click', hide); });
drawer.querySelectorAll('a').forEach(function (link) { link.addEventListener('click', hide); });
document.addEventListener('keydown', function (event) { if (event.key === 'Escape') hide(); });
}
function normalizeBackAction() {
var slot = document.getElementById('af-shared-back-slot');
var main = document.querySelector('main');
if (!slot || !main) return;
var candidates = Array.prototype.slice.call(main.querySelectorAll('a, button'));
var existing = candidates.find(function (el) {
if (el.closest('#af-page-context')) return false;
var text = textOf(el).toLowerCase();
return text === 'back' || text.indexOf('back to ') === 0 || text.indexOf('← back') === 0;
});
if (existing) {
existing.className = 'inline-flex items-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-semibold text-slate-700 shadow-sm transition hover:border-brand-300 hover:text-brand-700';
if (textOf(existing).indexOf('←') !== 0) existing.textContent = '← ' + textOf(existing);
slot.appendChild(existing);
return;
}
var path = window.location.pathname.replace(/\/$/, '');
var segments = path.split('/').filter(Boolean);
var isRoot = segments.length <= 1 || /\/(dashboard|workspaces)$/.test(path);
if (isRoot) return;
var referrerIsLocal = false;
try { referrerIsLocal = !!document.referrer && new URL(document.referrer).origin === window.location.origin; } catch (e) {}
if (!referrerIsLocal && window.history.length <= 1) return;
var button = document.createElement('button');
button.type = 'button';
button.className = 'inline-flex items-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-semibold text-slate-700 shadow-sm transition hover:border-brand-300 hover:text-brand-700';
button.textContent = '← Back';
button.addEventListener('click', function () {
if (referrerIsLocal || window.history.length > 1) window.history.back();
else window.location.href = '/workspaces';
});
slot.appendChild(button);
}
function closeDesktopDropdowns() {
document.addEventListener('click', function (event) {
document.querySelectorAll('[data-af-nav-dropdown][open]').forEach(function (item) {
if (!item.contains(event.target)) item.removeAttribute('open');
});
});
}
document.addEventListener('DOMContentLoaded', function () {
prepareMobileNavigation();
normalizeBackAction();
closeDesktopDropdowns();
});
})();
+9 -46
View File
@@ -1,4 +1,4 @@
const CACHE_VERSION = 'arrr-erp-pwa-v2-navigation-fix';
const CACHE_VERSION = 'arrr-erp-pwa-v1';
const CORE_ASSETS = [
'/manifest.webmanifest',
'/static/css/theme_tokens.css',
@@ -8,20 +8,14 @@ const CORE_ASSETS = [
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_VERSION)
.then((cache) => cache.addAll(CORE_ASSETS))
.catch(() => undefined)
caches.open(CACHE_VERSION).then((cache) => cache.addAll(CORE_ASSETS)).catch(() => undefined)
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => Promise.all(
keys
.filter((key) => key !== CACHE_VERSION)
.map((key) => caches.delete(key))
))
caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE_VERSION).map((key) => caches.delete(key))))
);
self.clients.claim();
});
@@ -33,47 +27,16 @@ self.addEventListener('fetch', (event) => {
const url = new URL(request.url);
if (url.origin !== self.location.origin) return;
// Cache static assets only. Never substitute a CSS response for an HTML page.
if (url.pathname.startsWith('/static/')) {
event.respondWith(
caches.match(request).then((cached) => {
if (cached) return cached;
return fetch(request).then((response) => {
if (response && response.ok) {
const copy = response.clone();
caches.open(CACHE_VERSION)
.then((cache) => cache.put(request, copy))
.catch(() => undefined);
}
return response;
});
})
caches.match(request).then((cached) => cached || fetch(request).then((response) => {
const copy = response.clone();
caches.open(CACHE_VERSION).then((cache) => cache.put(request, copy)).catch(() => undefined);
return response;
}))
);
return;
}
// Application pages, login, API calls and downloads remain network-first.
// If the network is unavailable, return a valid HTML response instead of CSS.
event.respondWith(
fetch(request).catch(() => {
if (request.mode === 'navigate') {
return new Response(
'<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Offline | Audit Firm ERP</title></head><body style="font-family:system-ui,sans-serif;padding:2rem;background:#f8fafc;color:#0f172a"><main style="max-width:42rem;margin:auto;background:#fff;border:1px solid #e2e8f0;border-radius:1rem;padding:1.5rem"><h1 style="margin-top:0">You are offline</h1><p>The ERP could not reach the server. Check your internet connection and refresh this page.</p></main></body></html>',
{
status: 503,
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store'
}
}
);
}
return new Response('', {
status: 503,
headers: { 'Cache-Control': 'no-store' }
});
})
);
event.respondWith(fetch(request).catch(() => caches.match('/static/css/theme_tokens.css')));
});
+17 -33
View File
@@ -4,22 +4,22 @@
<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)|default(none, true) %}
<meta name="theme-color" content="{{ (__firm_branding.primary_color|default('', true)) if __firm_branding else '#1e3a8a' }}" />
{% set __firm_branding = get_current_firm_branding(request, __title_user) %}
<meta name="theme-color" content="{{ __firm_branding.primary_color or '#1e3a8a' }}" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="ARRR ERP" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<link rel="apple-touch-icon" href="/static/pwa/icons/icon-192.png" />
{% if __firm_branding and (__firm_branding.favicon_url|default("", true)) %}<link rel="icon" href="{{ __firm_branding.favicon_url }}" />{% endif %}
{% 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|default("", true)) if __firm_branding else "Audit Firm ERP" }}</title>
<title>{{ title or "Welcome" }} | {{ __firm_branding.firm_name or "Audit Firm ERP" }}</title>
{% endif %}
<script src="https://cdn.tailwindcss.com"></script>
<script>
@@ -76,25 +76,13 @@
<link rel="stylesheet" href="/static/css/theme_tokens.css?v=7q-dashboard-align-strong" />
<style>
:root {
--af-color-brand-500: {{ (__firm_branding.primary_color|default('', true)) if __firm_branding else '#2563eb' }};
--af-color-brand-600: {{ (__firm_branding.primary_color|default('', true)) if __firm_branding else '#1d4ed8' }};
--af-color-brand-700: {{ (__firm_branding.accent_color|default('', true)) if __firm_branding else '#1e40af' }};
--af-color-brand-800: {{ (__firm_branding.accent_color|default('', true)) if __firm_branding else '#1e3a8a' }};
--af-color-brand-900: {{ (__firm_branding.accent_color|default('', true)) if __firm_branding else '#172554' }};
--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>
<style>
.af-nav-link,.af-nav-summary{display:inline-flex;align-items:center;border-radius:.75rem;padding:.55rem .8rem;font-size:.78rem;font-weight:700;color:#475569;transition:.15s;white-space:nowrap}
.af-nav-link:hover,.af-nav-summary:hover{background:#f1f5f9;color:var(--af-color-brand-700)}
.af-nav-link-active{background:var(--af-color-brand-600)!important;color:#fff!important;box-shadow:var(--af-shadow-soft)}
.af-nav-dropdown{position:relative}
.af-nav-summary{cursor:pointer;list-style:none}
.af-nav-summary::-webkit-details-marker{display:none}
.af-nav-menu{position:absolute;left:0;top:calc(100% + .55rem);z-index:70;display:flex;min-width:15rem;flex-direction:column;gap:.15rem;border:1px solid #e2e8f0;border-radius:1rem;background:#fff;padding:.5rem;box-shadow:var(--af-shadow-card)}
.af-nav-menu .af-nav-link{display:flex;width:100%}
@media (max-width:1023px){#af-mobile-nav .af-nav-link,#af-mobile-nav .af-nav-summary{padding:.7rem .8rem}}
</style>
</head>
<body class="min-h-screen bg-slate-100 text-slate-900">
{% set otp_ok = request.session.get("otp_verified", False) %}
@@ -111,31 +99,29 @@
{% 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)|default(none, true) %}
{% 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|default("", true)) if firm_branding else "Audit Firm ERP" %}
{% if not current_firm_name %}{% set current_firm_name = "Audit Firm ERP" %}{% endif %}
{% set current_branch_name = (firm_branding.branch_name|default("", true)) if firm_branding else "" %}
{% set current_firm_logo_url = (firm_branding.logo_url|default("", true)) if firm_branding else "" %}
{% set domain_is_resolved = ((get_domain_context(request)|default({}, true)).get("is_resolved", false)) %}
{% 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) %}
{# Navigation UI Cleanup V2.1
Sidebar is retained only for System Admin users.
All other authenticated users use the floating top workspace bar on every page.
Existing routes and old sidebar code are kept intact. #}
{% set use_clean_top_nav = full_auth %}
{% set use_clean_top_nav = full_auth and not is_system_admin_user %}
<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 current_firm_logo_url %}<img src="{{ current_firm_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 %}
{% 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_is_resolved %}{{ current_branch_name or 'Domain Workspace' }}{% else %}Secure Practice Workspace{% endif %}</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>
@@ -458,7 +444,7 @@
{% endif %}
</div>
{% else %}
{% if domain_is_resolved %}
{% 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>
@@ -517,7 +503,6 @@
{% endif %}
<main class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
{% if full_auth %}{% include "ui/templates/components/breadcrumbs.html" %}{% endif %}
{% 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 }}
@@ -691,6 +676,5 @@
})();
</script>
<script src="/static/js/navigation_shell.js?v=uiux-navigation-v1"></script>
</body>
</html>
@@ -1,21 +0,0 @@
{% set crumb_path = request.url.path %}
{% set crumb_parts = crumb_path.strip('/').split('/') if crumb_path != '/' else [] %}
{% set crumb_labels = {
'system-admin':'System Administration','firm-admin':'Firm Administration','partner':'Partner','manager':'Manager','employee':'Employee','employees':'Team','consultant':'Consultant','consultants':'Consultants','client':'Client','clients':'Clients','services':'Services','engagements':'Engagements','documents':'Documents','billing':'Billing','system-settings':'Firm Setup','users':'Users','rbac':'Roles & Permissions','roles':'Roles','branches':'Branches','financial-years':'Financial Years','audit-logs':'Audit Logs','alerts':'Alerts','work':'Work','attendance':'Attendance','leave':'Leave','profile':'Profile','dashboard':'Dashboard','reviews':'Reviews','compliance':'Compliance','messages':'Messages','imports':'Imports','import':'Import','export':'Export','new':'New','edit':'Edit','settings':'Settings','domains':'Domains','email':'Email','aqmm':'AQMM','peer-review':'Peer Review','evidence':'Evidence'
} %}
<div id="af-page-context" class="mb-5 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-soft" data-af-page-context>
<div class="flex flex-wrap items-center justify-between gap-3">
<nav aria-label="Breadcrumb" class="min-w-0 overflow-x-auto">
<ol class="flex min-w-max items-center gap-2 text-xs text-slate-500">
<li><a href="/workspaces" class="font-semibold text-slate-600 hover:text-brand-700">Home</a></li>
{% for part in crumb_parts %}
{% set upto = '/' ~ '/'.join(crumb_parts[:loop.index]) %}
{% set label = crumb_labels.get(part, part.replace('-', ' ')|title) %}
<li aria-hidden="true" class="text-slate-300"></li>
<li>{% if not loop.last %}<a href="{{ upto }}" class="hover:text-brand-700">{{ label }}</a>{% else %}<span class="font-semibold text-slate-800" aria-current="page">{{ title or label }}</span>{% endif %}</li>
{% endfor %}
</ol>
</nav>
<div id="af-shared-back-slot" class="shrink-0"></div>
</div>
</div>
@@ -1,182 +0,0 @@
{% set nav_path = request.url.path %}
{% set nav_roles = ui_roles if ui_roles is defined else [] %}
{% set nav_perms = ui_perms if ui_perms is defined else [] %}
{% set nav_workspace = 'employee' %}
{% if nav_path.startswith('/system-admin') %}{% set nav_workspace = 'system' %}
{% elif nav_path.startswith('/firm-admin') %}{% set nav_workspace = 'firm' %}
{% elif nav_path.startswith('/partner') %}{% set nav_workspace = 'partner' %}
{% elif nav_path.startswith('/manager') or nav_path.startswith('/employees/work') or nav_path.startswith('/employees/progress') or nav_path.startswith('/employees/attendance') or nav_path.startswith('/employees/leave') %}{% set nav_workspace = 'manager' %}
{% elif nav_path.startswith('/consultant') %}{% set nav_workspace = 'consultant' %}
{% elif nav_path.startswith('/client') %}{% set nav_workspace = 'client' %}
{% elif nav_path.startswith('/employee') %}{% set nav_workspace = 'employee' %}
{% elif 'System Admin' in nav_roles %}{% set nav_workspace = 'system' %}
{% elif 'Firm Admin' in nav_roles %}{% set nav_workspace = 'firm' %}
{% elif 'Partner' in nav_roles %}{% set nav_workspace = 'partner' %}
{% elif 'Manager' in nav_roles or 'Branch Manager' in nav_roles %}{% set nav_workspace = 'manager' %}
{% elif 'Consultant' in nav_roles %}{% set nav_workspace = 'consultant' %}
{% elif 'Client' in nav_roles %}{% set nav_workspace = 'client' %}
{% endif %}
{% macro nav_link(url, label, prefixes=None) -%}
{% set checks = prefixes if prefixes else [url] %}
{% set ns = namespace(active=false) %}
{% for prefix in checks %}{% if nav_path == prefix or nav_path.startswith(prefix ~ '/') %}{% set ns.active = true %}{% endif %}{% endfor %}
<a href="{{ url }}" class="af-nav-link {{ 'af-nav-link-active' if ns.active else '' }}" {% if ns.active %}aria-current="page"{% endif %}>{{ label }}</a>
{%- endmacro %}
{% macro dropdown(label, prefixes) -%}
{% set ns = namespace(active=false) %}
{% for prefix in prefixes %}{% if nav_path == prefix or nav_path.startswith(prefix ~ '/') %}{% set ns.active = true %}{% endif %}{% endfor %}
<details class="af-nav-dropdown group" data-af-nav-dropdown>
<summary class="af-nav-summary {{ 'af-nav-link-active' if ns.active else '' }}">{{ label }}<span aria-hidden="true" class="ml-1 text-[10px]"></span></summary>
<div class="af-nav-menu">{{ caller() }}</div>
</details>
{%- endmacro %}
<nav aria-label="Primary navigation" class="hidden lg:block">
<div class="flex min-w-0 items-center gap-1 overflow-visible">
{% if nav_workspace == 'system' %}
{{ nav_link('/system-admin/dashboard', 'Platform', ['/system-admin','/workspaces']) }}
{% call dropdown('Firms', ['/system-settings/tenants','/system-settings/branches','/wizards/system/firm']) %}
{{ nav_link('/system-settings/tenants', 'Audit Firms') }}
{{ nav_link('/system-settings/branches', 'Branches') }}
{{ nav_link('/wizards/system/firm/new', 'Create Firm Wizard') }}
{% endcall %}
{% call dropdown('Catalogue', ['/services/catalogue','/services/categories','/services/defaults','/marketplace']) %}
{{ nav_link('/services/catalogue', 'Service Catalogue') }}
{{ nav_link('/services/categories', 'Service Categories') }}
{{ nav_link('/services/defaults', 'System Default Tasks') }}
{{ nav_link('/marketplace', 'Marketplace') }}
{% endcall %}
{% call dropdown('Infrastructure', ['/domains','/email/platform-smtp','/documents/storage-nodes']) %}
{{ nav_link('/domains', 'Domain Mappings') }}
{{ nav_link('/domains/verification', 'DNS Verification') }}
{{ nav_link('/domains/ssl', 'SSL Status') }}
{{ nav_link('/email/platform-smtp', 'Platform SMTP') }}
{{ nav_link('/documents/storage-nodes', 'Storage Agents') }}
{% endcall %}
{{ nav_link('/platform-billing', 'Billing') }}
{% call dropdown('Administration', ['/system-settings/users','/system-settings/rbac','/system-settings/financial-years','/system-settings/audit-logs']) %}
{{ nav_link('/system-settings/users', 'Users') }}
{{ nav_link('/system-settings/rbac/roles', 'Roles & Permissions') }}
{{ nav_link('/system-settings/financial-years', 'Financial Years') }}
{{ nav_link('/system-settings/audit-logs', 'Audit Logs') }}
{% endcall %}
{% elif nav_workspace == 'firm' %}
{{ nav_link('/firm-admin/dashboard', 'Dashboard') }}
{% if can_view_clients(current_user, nav_perms, nav_roles) %}{% call dropdown('Clients', ['/clients']) %}
{{ nav_link('/clients', 'Client List') }}
{{ nav_link('/clients/new', 'Add Client') }}
{{ nav_link('/clients/import', 'Import Clients') }}
{{ nav_link('/clients/export', 'Export Clients') }}
{% endcall %}{% endif %}
{% call dropdown('Services', ['/services']) %}
{{ nav_link('/services', 'Firm Services') }}
{{ nav_link('/services/templates', 'Task Templates') }}
{{ nav_link('/services/bulk-imports', 'Bulk Imports') }}
{{ nav_link('/services/engagements', 'Engagements') }}
{% endcall %}
{% call dropdown('Team', ['/employees','/consultants']) %}
{{ nav_link('/employees', 'Employees') }}
{{ nav_link('/employees/work', 'Work Allocation') }}
{{ nav_link('/employees/attendance', 'Attendance') }}
{{ nav_link('/employees/leave', 'Leave') }}
{{ nav_link('/consultants', 'Consultants') }}
{% endcall %}
{% if can_view_documents_menu %}{{ nav_link('/documents', 'Documents') }}{% endif %}
{{ nav_link('/billing', 'Billing') }}
{% call dropdown('Firm Setup', ['/system-settings','/email/settings']) %}
{{ nav_link('/system-settings', 'Settings Dashboard') }}
{{ nav_link('/system-settings/branding', 'Branding') }}
{{ nav_link('/system-settings/branches', 'Branches') }}
{{ nav_link('/system-settings/users', 'Users') }}
{{ nav_link('/system-settings/rbac/roles', 'Roles & Permissions') }}
{{ nav_link('/system-settings/financial-years', 'Financial Years') }}
{{ nav_link('/email/settings', 'Email Settings') }}
{{ nav_link('/system-settings/audit-logs', 'Audit Logs') }}
{% endcall %}
{% elif nav_workspace == 'partner' %}
{{ nav_link('/partner/dashboard', 'Dashboard') }}
{{ nav_link('/partner/reviews', 'Reviews') }}
{{ nav_link('/partner/clients', 'Clients') }}
{{ nav_link('/services/engagements', 'Engagements') }}
{% if can_view_documents_menu %}{{ nav_link('/documents', 'Documents') }}{% endif %}
{{ nav_link('/billing', 'Billing') }}
{{ nav_link('/aqmm/dashboard', 'AQMM') }}
{{ nav_link('/peer-review/evidence', 'Peer Review') }}
{% elif nav_workspace == 'manager' %}
{{ nav_link('/manager/dashboard', 'Dashboard') }}
{% if can_manage_employee_work(current_user, nav_perms, nav_roles) %}{{ nav_link('/manager/work', 'Work Board') }}{{ nav_link('/employees/work', 'Allocation') }}{% endif %}
{% if can_view_employee_progress(current_user, nav_perms, nav_roles) %}{{ nav_link('/employees/progress', 'Progress') }}{% endif %}
{% if can_view_all_employee_attendance(current_user, nav_perms, nav_roles) %}{{ nav_link('/employees/attendance', 'Attendance') }}{% endif %}
{% if can_view_all_employee_leave(current_user, nav_perms, nav_roles) %}{{ nav_link('/employees/leave', 'Leave') }}{% endif %}
{% elif nav_workspace == 'employee' %}
{% if can_view_employee_portal(current_user, nav_perms, nav_roles) %}{{ nav_link('/employee/dashboard', 'Dashboard') }}{% endif %}
{% if can_view_own_employee_work(current_user, nav_perms, nav_roles) %}{{ nav_link('/employee/work', 'Work') }}{% endif %}
{% if can_view_own_employee_attendance(current_user, nav_perms, nav_roles) %}{{ nav_link('/employee/attendance', 'Attendance') }}{% endif %}
{% if can_view_own_employee_leave(current_user, nav_perms, nav_roles) %}{{ nav_link('/employee/leave', 'Leave') }}{% endif %}
{% if can_view_own_employee_documents(current_user, nav_perms, nav_roles) %}{{ nav_link('/employee/documents', 'Documents') }}{% endif %}
{% if can_view_own_employee_payslips(current_user, nav_perms, nav_roles) %}{{ nav_link('/employee/payslips', 'Payslips') }}{% endif %}
{% if can_request_own_employee_offboarding(current_user, nav_perms, nav_roles) %}{{ nav_link('/employee/offboarding', 'Offboarding') }}{% endif %}
{% elif nav_workspace == 'consultant' %}
{{ nav_link('/consultant/dashboard', 'Dashboard') }}
{{ nav_link('/consultant/work', 'Work', ['/consultant/work','/consultant/assignments']) }}
{{ nav_link('/consultant/managed-clients', 'Clients') }}
{{ nav_link('/consultant/service-requests', 'Requests') }}
{{ nav_link('/consultant/documents', 'Documents') }}
{{ nav_link('/consultant/communications', 'Messages') }}
{{ nav_link('/consultant/workspace', 'Workspace') }}
{% elif nav_workspace == 'client' %}
{{ nav_link('/client/dashboard', 'Dashboard') }}
{{ nav_link('/client/compliance', 'Compliance', ['/client/compliance','/client/engagements']) }}
{{ nav_link('/client/documents', 'Documents') }}
{{ nav_link('/client/messages', 'Messages') }}
{{ nav_link('/client/billing', 'Bills & Payments') }}
{% endif %}
</div>
</nav>
<div id="af-mobile-nav" class="fixed inset-0 z-[80] hidden lg:hidden" aria-hidden="true">
<button type="button" class="absolute inset-0 bg-slate-950/45" data-af-mobile-close aria-label="Close navigation"></button>
<div class="absolute inset-y-0 left-0 w-[86vw] max-w-sm overflow-y-auto bg-white p-4 shadow-2xl">
<div class="mb-4 flex items-center justify-between border-b border-slate-200 pb-3">
<div><div class="text-sm font-bold text-slate-900">Navigation</div><div class="text-xs text-slate-500">{{ current_firm_name }}</div></div>
<button type="button" data-af-mobile-close class="rounded-xl border border-slate-200 px-3 py-2 text-sm font-semibold text-slate-700">Close</button>
</div>
<div class="space-y-1" data-af-mobile-links></div>
<div class="mt-5 space-y-3 border-t border-slate-200 pt-4">
<div class="text-[11px] font-bold uppercase tracking-wider text-slate-400">Workspace context</div>
<a href="/workspaces" class="block rounded-xl border border-slate-200 px-3 py-2 text-sm font-semibold text-slate-700">Change workspace</a>
{% if can_switch_service_tenant(current_user, nav_perms, nav_roles) or can_switch_employee_tenant(current_user, nav_perms, nav_roles) %}
{% set mobile_tenants = get_context_tenants(request, current_user, nav_perms, nav_roles) %}
<select aria-label="Active Audit Firm" onchange="if(this.value){window.location.href=this.value;}" class="w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
{% for tenant in mobile_tenants %}<option value="/system-settings/context/tenant/{{ tenant.id }}" {% if tenant.id == active_tenant_id %}selected{% endif %}>{{ tenant.name }}</option>{% endfor %}
</select>
{% endif %}
{% if can_switch_service_branch(current_user, nav_perms, nav_roles) or can_switch_employee_branch(current_user, nav_perms, nav_roles) %}
{% set mobile_branches = get_context_branches(request, current_user, nav_perms, nav_roles) %}
<select aria-label="Active Branch" onchange="if(this.value){window.location.href=this.value;}" class="w-full rounded-xl border border-slate-200 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 mobile_branches %}<option value="/system-settings/context/branch/{{ branch.id }}" {% if branch.id == active_branch_id %}selected{% endif %}>{{ branch.name }}</option>{% endfor %}
</select>
{% endif %}
{% if can_view_settings(current_user, nav_perms, nav_roles) %}
{% set mobile_fys = get_context_financial_years(request, current_user, nav_perms, nav_roles) %}
<select aria-label="Active Financial Year" onchange="if(this.value){window.location.href=this.value;}" class="w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
{% for fy in mobile_fys %}<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 %}</option>{% endfor %}
</select>
{% endif %}
</div>
<div class="mt-5 space-y-1 border-t border-slate-200 pt-4">
{% if "Consultant" in nav_roles %}<a class="block rounded-xl px-3 py-2 text-sm text-slate-700 hover:bg-slate-50" href="/consultant/profile">My Profile</a>{% elif "Client" in nav_roles %}<a class="block rounded-xl px-3 py-2 text-sm text-slate-700 hover:bg-slate-50" href="/client/profile">My Profile</a>{% else %}<a class="block rounded-xl px-3 py-2 text-sm text-slate-700 hover:bg-slate-50" href="/employee/profile">My Profile</a>{% endif %}
<a class="block rounded-xl px-3 py-2 text-sm text-slate-700 hover:bg-slate-50" href="/change-password">Change Password</a>
<a class="block rounded-xl px-3 py-2 text-sm font-semibold text-red-600 hover:bg-red-50" href="/logout">Logout</a>
</div>
</div>
</div>
@@ -1,72 +1,85 @@
<header class="sticky top-0 z-50 border-b border-slate-200/80 bg-slate-100/95 backdrop-blur-xl">
<div class="mx-auto max-w-[1600px] px-3 py-2 sm:px-5 lg:px-8">
<div class="rounded-2xl border border-slate-200 bg-white/95 shadow-soft">
<div class="flex items-center gap-3 px-3 py-2.5 sm:px-4">
<a href="/workspaces" class="flex min-w-0 items-center gap-3 rounded-xl px-1 py-1 transition hover:bg-slate-50 lg:min-w-[220px]">
{% if current_firm_logo_url|default("", true) %}
<img src="{{ current_firm_logo_url }}" alt="{{ current_firm_name }} logo" class="h-10 w-10 shrink-0 rounded-xl border border-slate-100 bg-white object-contain p-1 shadow-sm" />
<header class="sticky top-0 z-40 border-b border-slate-200/70 bg-slate-100/85 backdrop-blur-xl">
<div class="mx-auto max-w-[1500px] px-4 py-3 sm:px-6 lg:px-8">
<div class="rounded-3xl border border-slate-200 bg-white/95 px-4 py-3 shadow-soft">
<div class="flex flex-wrap items-center gap-3">
<a href="/workspaces" class="flex min-w-[220px] items-center gap-3 rounded-2xl px-2 py-1 transition hover:bg-slate-50">
{% if firm_branding.logo_url %}
<img src="{{ firm_branding.logo_url }}" alt="{{ current_firm_name }} logo" class="h-11 w-11 rounded-2xl border border-slate-100 bg-white object-contain p-1 shadow-soft" />
{% else %}
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-600 text-sm font-bold text-white shadow-sm">{{ (current_firm_name[:2] if current_firm_name else 'AF')|upper }}</div>
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-brand-600 text-sm font-bold text-white shadow-soft">{{ (current_firm_name[:2] if current_firm_name else 'AF')|upper }}</div>
{% endif %}
<div class="hidden min-w-0 sm:block">
<div class="max-w-[220px] truncate text-sm font-bold text-slate-900">{{ current_firm_name }}</div>
<div class="max-w-[240px] truncate text-[11px] text-slate-500">{{ current_branch_name if active_branch_id else 'All Branches' }}{% if active_financial_year %} • FY {{ active_financial_year }}{% endif %}</div>
<div class="min-w-0">
<div class="truncate text-sm font-bold text-slate-900">{{ current_firm_name }}</div>
<div class="truncate text-xs text-slate-500">{{ current_branch_name if active_branch_id else 'All Branches' }}{% if active_financial_year %} • FY {{ active_financial_year }}{% endif %}</div>
</div>
</a>
<div class="hidden min-w-0 flex-1 items-center gap-2 md:flex">
<div class="flex min-w-0 flex-1 flex-wrap items-center gap-2">
{% include "ui/templates/components/workspace_switcher.html" %}
{% 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)) %}
{% 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) %}
<select title="Active Audit Firm" aria-label="Active Audit Firm" onchange="if(this.value){window.location.href=this.value;}" class="max-w-[180px] rounded-xl border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-700 outline-none hover:border-brand-200 focus:ring-2 focus:ring-brand-100">
{% for tenant in context_tenants %}<option value="/system-settings/context/tenant/{{ tenant.id }}" {% if tenant.id == active_tenant_id %}selected{% endif %}>{{ tenant.name }}</option>{% endfor %}
<select title="Active Audit Firm" onchange="if(this.value){window.location.href=this.value;}" class="max-w-[210px] rounded-2xl border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-700 shadow-sm outline-none hover:border-brand-200 focus:ring-2 focus:ring-brand-100">
{% for tenant in context_tenants %}
<option value="/system-settings/context/tenant/{{ tenant.id }}" {% if tenant.id == active_tenant_id %}selected{% endif %}>{{ tenant.name }}</option>
{% endfor %}
</select>
{% 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) %}
<select title="Active Branch" aria-label="Active Branch" onchange="if(this.value){window.location.href=this.value;}" class="max-w-[160px] rounded-xl border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-700 outline-none hover:border-brand-200 focus:ring-2 focus:ring-brand-100">
<select title="Active Branch" onchange="if(this.value){window.location.href=this.value;}" class="max-w-[190px] rounded-2xl border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-700 shadow-sm outline-none hover:border-brand-200 focus:ring-2 focus:ring-brand-100">
<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 }}</option>{% endfor %}
{% for branch in context_branches %}
<option value="/system-settings/context/branch/{{ branch.id }}" {% if branch.id == active_branch_id %}selected{% endif %}>{{ branch.name }}</option>
{% endfor %}
</select>
{% 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) %}
<select title="Active Financial Year" data-testid="active-fy-select" aria-label="Active Financial Year" onchange="if(this.value){window.location.href=this.value;}" class="max-w-[145px] rounded-xl border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-700 outline-none hover:border-brand-200 focus:ring-2 focus:ring-brand-100">
{% 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 title="Active Financial Year" data-testid="active-fy-select" aria-label="Active Financial Year" onchange="if(this.value){window.location.href=this.value;}" class="max-w-[170px] rounded-2xl border border-slate-200 bg-white px-3 py-2 text-xs font-semibold text-slate-700 shadow-sm outline-none hover:border-brand-200 focus:ring-2 focus:ring-brand-100">
{% 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>
{% endif %}
{% endif %}
</div>
<div class="ml-auto flex items-center gap-2">
<a href="/alerts" aria-label="Alerts" class="relative inline-flex h-10 items-center justify-center rounded-xl border border-slate-200 bg-white px-3 text-xs font-semibold text-slate-700 hover:border-brand-200 hover:text-brand-700">
<span class="hidden sm:inline">Alerts</span><span class="sm:hidden" aria-hidden="true">🔔</span>
{% if unread_alert_count %}<span class="ml-1.5 rounded-full bg-brand-600 px-1.5 py-0.5 text-[10px] font-bold text-white">{{ unread_alert_count }}</span>{% endif %}
<a href="/alerts" class="relative inline-flex h-10 items-center justify-center rounded-2xl border border-slate-200 bg-white px-3 text-xs font-semibold text-slate-700 shadow-sm hover:border-brand-200 hover:text-brand-700">
Alerts
{% if unread_alert_count %}<span class="ml-2 rounded-full bg-brand-600 px-2 py-0.5 text-[10px] font-bold text-white">{{ unread_alert_count }}</span>{% endif %}
</a>
<details class="group relative hidden sm:block">
<summary class="flex cursor-pointer list-none items-center gap-2 rounded-xl border border-slate-200 bg-white px-2 py-1.5 hover:border-brand-200">
{% if current_user_photo_url %}<img src="{{ current_user_photo_url }}" alt="Profile photo" class="h-8 w-8 rounded-lg object-cover" />{% else %}<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-brand-600 text-xs font-bold text-white">{{ current_user_initials }}</div>{% endif %}
<div class="hidden text-left xl:block"><div class="max-w-[140px] truncate text-xs font-bold text-slate-900">{{ current_user.full_name or current_user.email }}</div><div class="max-w-[140px] truncate text-[10px] text-slate-500">{{ current_user.email }}</div></div>
<details class="group relative">
<summary class="flex cursor-pointer list-none items-center gap-2 rounded-2xl border border-slate-200 bg-white px-2 py-2 shadow-sm hover:border-brand-200">
{% if current_user_photo_url %}
<img src="{{ current_user_photo_url }}" alt="Profile photo" class="h-8 w-8 rounded-xl object-cover" />
{% else %}
<div class="flex h-8 w-8 items-center justify-center rounded-xl bg-brand-600 text-xs font-bold text-white">{{ current_user_initials }}</div>
{% endif %}
<div class="hidden text-left sm:block">
<div class="max-w-[150px] truncate text-xs font-bold text-slate-900">{{ current_user.full_name or current_user.email }}</div>
<div class="max-w-[150px] truncate text-[11px] text-slate-500">{{ current_user.email }}</div>
</div>
</summary>
<div class="absolute right-0 z-[70] mt-2 w-56 rounded-2xl border border-slate-200 bg-white p-2 text-sm shadow-card">
{% if "Consultant" in ui_roles %}<a class="block rounded-xl px-3 py-2 text-slate-700 hover:bg-slate-50" href="/consultant/profile">My Profile</a>{% elif "Client" in ui_roles %}<a class="block rounded-xl px-3 py-2 text-slate-700 hover:bg-slate-50" href="/client/profile">My Profile</a>{% else %}<a class="block rounded-xl px-3 py-2 text-slate-700 hover:bg-slate-50" href="/employee/profile">My Profile</a>{% endif %}
<div class="absolute right-0 z-50 mt-2 w-56 rounded-2xl border border-slate-200 bg-white p-2 text-sm shadow-card">
{% if "Consultant" in ui_roles %}
<a class="block rounded-xl px-3 py-2 text-slate-700 hover:bg-slate-50" href="/consultant/profile">My Profile</a>
{% elif "Client" in ui_roles %}
<a class="block rounded-xl px-3 py-2 text-slate-700 hover:bg-slate-50" href="/client/profile">My Profile</a>
{% else %}
<a class="block rounded-xl px-3 py-2 text-slate-700 hover:bg-slate-50" href="/employee/profile">My Profile</a>
{% endif %}
<a class="block rounded-xl px-3 py-2 text-slate-700 hover:bg-slate-50" href="/change-password">Change Password</a>
<a class="block rounded-xl px-3 py-2 text-red-600 hover:bg-red-50" href="/logout">Logout</a>
</div>
</details>
<button type="button" data-af-mobile-open aria-label="Open navigation" aria-controls="af-mobile-nav" class="inline-flex h-10 w-10 items-center justify-center rounded-xl border border-slate-200 bg-white text-xl font-bold text-slate-700 hover:border-brand-200 hover:text-brand-700 lg:hidden"></button>
</div>
</div>
<div class="lg:border-t lg:border-slate-200 lg:px-4 lg:py-2">
{% include "ui/templates/components/role_navigation.html" %}
</div>
</div>
</div>
</header>
@@ -3,15 +3,18 @@
{% set perms = ui_perms if ui_perms is defined else [] %}
{% set current = current_path if current_path is defined else request.url.path %}
{% set can_employee = ('Staff' in roles) or ('Employee' in roles) or ('employees.ess.view' in perms) or ('employees.work.view_self' in perms) or ('employees.attendance.view_self' in perms) or ('employees.leave.view_self' in perms) or ('employees.documents.view_self' in perms) or ('employees.payroll.view_self' in perms) %}
<label class="sr-only" for="workspace_switcher">Workspace</label>
<select id="workspace_switcher" title="Active Workspace" onchange="if(this.value){window.location.href=this.value;}" class="min-w-[190px] max-w-[220px] rounded-xl border border-brand-200 bg-brand-50 px-3 py-2 text-xs font-semibold text-brand-900 outline-none transition hover:bg-brand-100 focus:border-brand-400 focus:ring-2 focus:ring-brand-200">
<option value="/workspaces" {% if current.startswith('/workspaces') %}selected{% endif %}>Workspace Centre</option>
{% if 'System Admin' in roles %}<option value="/system-admin/dashboard" {% if current.startswith('/system-admin') %}selected{% endif %}>System Administration</option>{% endif %}
{% if 'Partner' in roles %}<option value="/partner/dashboard" {% if current.startswith('/partner') %}selected{% endif %}>Partner Workspace</option>{% endif %}
{% if 'Firm Admin' in roles %}<option value="/firm-admin/dashboard" {% if current.startswith('/firm-admin') %}selected{% endif %}>Firm Administration</option>{% endif %}
{% if 'Manager' in roles or 'Branch Manager' in roles %}<option value="/manager/dashboard" {% if current.startswith('/manager') %}selected{% endif %}>Manager Workspace</option>{% endif %}
{% if can_employee %}<option value="/employee/dashboard" {% if current.startswith('/employee') or current.startswith('/employees') %}selected{% endif %}>Employee Workspace</option>{% endif %}
{% if 'Client' in roles %}<option value="/client/dashboard" {% if current.startswith('/client') %}selected{% endif %}>Client Portal</option>{% endif %}
{% if 'Consultant' in roles %}<option value="/consultant/dashboard" {% if current.startswith('/consultant') %}selected{% endif %}>Consultant Workspace</option>{% endif %}
</select>
<div class="mt-2 flex flex-wrap justify-end gap-2">
<label class="sr-only" for="workspace_switcher">Workspace</label>
<select id="workspace_switcher" onchange="if(this.value){window.location.href=this.value;}" class="min-w-[230px] max-w-xs rounded-xl border border-brand-200 bg-brand-50 px-3 py-1.5 text-xs font-semibold text-brand-900 shadow-sm outline-none transition hover:bg-brand-100 focus:border-brand-400 focus:ring-2 focus:ring-brand-200">
<option value="/workspaces" {% if current.startswith('/workspaces') %}selected{% endif %}>Workspace Centre</option>
{% if 'System Admin' in roles %}<option value="/system-admin/dashboard" {% if current.startswith('/system-admin') %}selected{% endif %}>System Admin - Platform Control</option>{% endif %}
{% if 'Partner' in roles %}<option value="/partner/dashboard" {% if current.startswith('/partner') %}selected{% endif %}>Partner Operations</option>{% endif %}
{% if 'Firm Admin' in roles %}<option value="/firm-admin/dashboard" {% if current.startswith('/firm-admin') %}selected{% endif %}>Firm Administration</option>{% endif %}
{% if 'Manager' in roles or 'Branch Manager' in roles %}<option value="/manager/dashboard" {% if current.startswith('/manager') %}selected{% endif %}>Manager Workspace</option>{% endif %}
{% if can_employee %}<option value="/employee/dashboard" {% if current.startswith('/employee') or current.startswith('/employees') %}selected{% endif %}>Employee Portal</option>{% endif %}
{% if 'Client' in roles %}<option value="/client/dashboard" {% if current.startswith('/client') %}selected{% endif %}>Client Portal</option>{% endif %}
{% if 'Consultant' in roles %}<option value="/consultant/dashboard" {% if current.startswith('/consultant') %}selected{% endif %}>Consultant Workspace</option>{% endif %}
</select>
<a href="/workspaces" class="inline-flex items-center rounded-xl border border-slate-200 bg-white px-3 py-1.5 text-xs font-semibold text-slate-600 shadow-sm hover:border-brand-200 hover:text-brand-700">All workspaces</a>
</div>
{% endif %}