Rollback unstable UIUX Phase 1 navigation changes
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
})();
|
||||
+7
-44
@@ -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) {
|
||||
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);
|
||||
}
|
||||
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')));
|
||||
});
|
||||
|
||||
@@ -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) %}
|
||||
<div class="mt-2 flex flex-wrap justify-end gap-2">
|
||||
<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">
|
||||
<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 Administration</option>{% endif %}
|
||||
{% if 'Partner' in roles %}<option value="/partner/dashboard" {% if current.startswith('/partner') %}selected{% endif %}>Partner Workspace</option>{% endif %}
|
||||
{% 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 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 %}
|
||||
|
||||
Reference in New Issue
Block a user