diff --git a/app/modules/clients/templates/clients/_client_tabs.html b/app/modules/clients/templates/clients/_client_tabs.html index bd12389..357add7 100644 --- a/app/modules/clients/templates/clients/_client_tabs.html +++ b/app/modules/clients/templates/clients/_client_tabs.html @@ -1,12 +1 @@ -
-
- {% set path = request.url.path %} - Overview - My Compliance - My Documents - My Messages - My Bills - My Profile - My Alert -
-
+{# Replaced by the permanent permission-aware navigation in base/layout.html. #} diff --git a/app/modules/consultants/templates/consultants/_consultant_tabs.html b/app/modules/consultants/templates/consultants/_consultant_tabs.html index b3abbaa..357add7 100644 --- a/app/modules/consultants/templates/consultants/_consultant_tabs.html +++ b/app/modules/consultants/templates/consultants/_consultant_tabs.html @@ -1,14 +1 @@ -{% set path = request.url.path %} -
- -
+{# Replaced by the permanent permission-aware navigation in base/layout.html. #} diff --git a/app/modules/employees/templates/employees/_my_workspace_tabs.html b/app/modules/employees/templates/employees/_my_workspace_tabs.html index bbdc683..357add7 100644 --- a/app/modules/employees/templates/employees/_my_workspace_tabs.html +++ b/app/modules/employees/templates/employees/_my_workspace_tabs.html @@ -1,38 +1 @@ -{% 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" %} -
-
My Workspace
-
-
- {% if can_view_employee_portal(current_user, current_user_permissions, current_user_roles) %} - Overview - {% endif %} - {% if can_view_own_employee_work(current_user, current_user_permissions, current_user_roles) %} - My Work Board - {% endif %} - {% if can_view_own_employee_attendance(current_user, current_user_permissions, current_user_roles) %} - My Attendance - {% endif %} - {% if can_view_employee_portal(current_user, current_user_permissions, current_user_roles) %} - My Profile - {% endif %} - {% if can_view_own_employee_leave(current_user, current_user_permissions, current_user_roles) %} - My Leave - {% endif %} - {% if can_view_own_employee_documents(current_user, current_user_permissions, current_user_roles) %} - My Documents - {% endif %} - {% if can_view_own_employee_payslips(current_user, current_user_permissions, current_user_roles) %} - My Payslips - {% endif %} - {% if can_request_own_employee_offboarding(current_user, current_user_permissions, current_user_roles) %} - My Offboarding - {% endif %} - - My Alert{% if unread_alert_count is defined and unread_alert_count > 0 %}{{ unread_alert_count }}{% endif %} - -
-
-
+{# Replaced by the permanent permission-aware navigation in base/layout.html. #} diff --git a/app/modules/managers/templates/managers/_manager_tabs.html b/app/modules/managers/templates/managers/_manager_tabs.html index 21fc760..357add7 100644 --- a/app/modules/managers/templates/managers/_manager_tabs.html +++ b/app/modules/managers/templates/managers/_manager_tabs.html @@ -1,12 +1 @@ -{% set manager_path = request.url.path %} -
-
- Overview - Team Work Board - Detailed Allocation - Engagement Progress - Team Attendance - Team Leave - My Alert -
-
+{# Replaced by the permanent permission-aware navigation in base/layout.html. #} diff --git a/app/modules/partners/templates/partners/_partner_tabs.html b/app/modules/partners/templates/partners/_partner_tabs.html index 5d9a71c..357add7 100644 --- a/app/modules/partners/templates/partners/_partner_tabs.html +++ b/app/modules/partners/templates/partners/_partner_tabs.html @@ -1,12 +1 @@ -{% set path = request.url.path %} -
-
- Overview - Review Board - My Clients - Engagements - Documents - Billing - My Alert -
-
+{# Replaced by the permanent permission-aware navigation in base/layout.html. #} diff --git a/app/static/js/navigation_shell.js b/app/static/js/navigation_shell.js new file mode 100644 index 0000000..6a9c10c --- /dev/null +++ b/app/static/js/navigation_shell.js @@ -0,0 +1,98 @@ +(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(); + }); +})(); diff --git a/app/ui/templates/base/layout.html b/app/ui/templates/base/layout.html index 8f2acb1..fa74283 100644 --- a/app/ui/templates/base/layout.html +++ b/app/ui/templates/base/layout.html @@ -83,6 +83,18 @@ --af-color-brand-900: {{ __firm_branding.accent_color or '#172554' }}; } + + {% set otp_ok = request.session.get("otp_verified", False) %} @@ -111,7 +123,7 @@ 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 and not is_system_admin_user %} + {% set use_clean_top_nav = full_auth %}
@@ -503,6 +515,7 @@ {% endif %}
+ {% if full_auth %}{% include "ui/templates/components/breadcrumbs.html" %}{% endif %} {% if flash %}
{{ flash }} @@ -676,5 +689,6 @@ })(); + diff --git a/app/ui/templates/components/breadcrumbs.html b/app/ui/templates/components/breadcrumbs.html new file mode 100644 index 0000000..d1d099b --- /dev/null +++ b/app/ui/templates/components/breadcrumbs.html @@ -0,0 +1,21 @@ +{% 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' +} %} +
+
+ +
+
+
diff --git a/app/ui/templates/components/role_navigation.html b/app/ui/templates/components/role_navigation.html new file mode 100644 index 0000000..b3a3cb4 --- /dev/null +++ b/app/ui/templates/components/role_navigation.html @@ -0,0 +1,182 @@ +{% 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 %} + {{ label }} +{%- 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 %} +
+ {{ label }} +
{{ caller() }}
+
+{%- endmacro %} + + + + diff --git a/app/ui/templates/components/top_workspace_bar.html b/app/ui/templates/components/top_workspace_bar.html index a4aa317..30d5c2a 100644 --- a/app/ui/templates/components/top_workspace_bar.html +++ b/app/ui/templates/components/top_workspace_bar.html @@ -1,85 +1,72 @@ -
-
-
-
- +
+
+
+
+ {% if firm_branding.logo_url %} - {{ current_firm_name }} logo + {{ current_firm_name }} logo {% else %} -
{{ (current_firm_name[:2] if current_firm_name else 'AF')|upper }}
+
{{ (current_firm_name[:2] if current_firm_name else 'AF')|upper }}
{% endif %} -
-
{{ current_firm_name }}
-
{{ current_branch_name if active_branch_id else 'All Branches' }}{% if active_financial_year %} • FY {{ active_financial_year }}{% endif %}
+
-
+
- - Alerts - {% if unread_alert_count %}{{ unread_alert_count }}{% endif %} + + + {% if unread_alert_count %}{{ unread_alert_count }}{% endif %} -
- - {% if current_user_photo_url %} - Profile photo - {% else %} -
{{ current_user_initials }}
- {% endif %} - + + +
+ +
+ {% include "ui/templates/components/role_navigation.html" %} +
diff --git a/app/ui/templates/components/workspace_switcher.html b/app/ui/templates/components/workspace_switcher.html index d172dc1..ea440e4 100644 --- a/app/ui/templates/components/workspace_switcher.html +++ b/app/ui/templates/components/workspace_switcher.html @@ -3,18 +3,15 @@ {% 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) %} -
- - - All workspaces -
+ + {% endif %}