From bfb1fca43a35c0f1a825c2c75176cb6f4845bf59 Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Mon, 6 Jul 2026 12:28:55 +0530 Subject: [PATCH] Add navigation workspace switcher v1 --- app/modules/workspace_navigation/__init__.py | 1 + app/modules/workspace_navigation/service.py | 139 ++++++++++++++++++ .../templates/workspace_navigation/index.html | 34 +++++ app/modules/workspace_navigation/ui.py | 43 ++++++ app/ui/app.py | 2 + .../components/workspace_switcher.html | 36 ++--- 6 files changed, 235 insertions(+), 20 deletions(-) create mode 100644 app/modules/workspace_navigation/__init__.py create mode 100644 app/modules/workspace_navigation/service.py create mode 100644 app/modules/workspace_navigation/templates/workspace_navigation/index.html create mode 100644 app/modules/workspace_navigation/ui.py diff --git a/app/modules/workspace_navigation/__init__.py b/app/modules/workspace_navigation/__init__.py new file mode 100644 index 0000000..4d6f851 --- /dev/null +++ b/app/modules/workspace_navigation/__init__.py @@ -0,0 +1 @@ +"""Workspace navigation module.""" diff --git a/app/modules/workspace_navigation/service.py b/app/modules/workspace_navigation/service.py new file mode 100644 index 0000000..627ca7a --- /dev/null +++ b/app/modules/workspace_navigation/service.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Iterable + + +@dataclass(frozen=True) +class WorkspaceCard: + code: str + title: str + description: str + url: str + badge: str + priority: int + + +def _role_set(roles: Iterable[str] | None) -> set[str]: + return {str(role).strip() for role in (roles or []) if str(role).strip()} + + +def _has_employee_portal_access(roles: set[str], permissions: set[str]) -> bool: + employee_permissions = { + "employees.ess.view", + "employees.work.view_self", + "employees.attendance.view_self", + "employees.leave.view_self", + "employees.documents.view_self", + "employees.payroll.view_self", + } + return bool({"Staff", "Employee"}.intersection(roles) or employee_permissions.intersection(permissions)) + + +def build_workspace_cards(roles: Iterable[str] | None, permissions: Iterable[str] | None) -> list[WorkspaceCard]: + role_set = _role_set(roles) + permission_set = {str(permission).strip() for permission in (permissions or []) if str(permission).strip()} + cards: list[WorkspaceCard] = [] + + if "System Admin" in role_set: + cards.append(WorkspaceCard( + code="system-admin", + title="System Admin", + description="Platform control, firms, SMTP, storage, reports and setup health.", + url="/system-admin/dashboard", + badge="Platform", + priority=10, + )) + + if "Firm Admin" in role_set: + cards.append(WorkspaceCard( + code="firm-admin", + title="Firm Administration", + description="Firm settings, branches, users, roles, services, FY and setup reports.", + url="/firm-admin/dashboard", + badge="Firm setup", + priority=20, + )) + + if "Partner" in role_set: + cards.append(WorkspaceCard( + code="partner", + title="Partner Operations", + description="Branch work, clients, staff workload, review, billing and partner reports.", + url="/partner/dashboard", + badge="Branch control", + priority=30, + )) + + if {"Manager", "Branch Manager"}.intersection(role_set): + cards.append(WorkspaceCard( + code="manager", + title="Manager Workspace", + description="Team work, review queue, client pending, documents, escalations and reports.", + url="/manager/dashboard", + badge="Execution control", + priority=40, + )) + + if _has_employee_portal_access(role_set, permission_set): + cards.append(WorkspaceCard( + code="employee", + title="Employee Portal", + description="Attendance, my work board, leave, documents, payslips, alerts and my reports.", + url="/employee/dashboard", + badge="My work", + priority=50, + )) + + if "Client" in role_set: + cards.append(WorkspaceCard( + code="client", + title="Client Portal", + description="Pending documents, services, billing, messages and client reports.", + url="/client/dashboard", + badge="Client view", + priority=60, + )) + + if "Consultant" in role_set: + cards.append(WorkspaceCard( + code="consultant", + title="Consultant Workspace", + description="Assigned work, clients, documents, clarifications, requests and consultant reports.", + url="/consultant/dashboard", + badge="External work", + priority=70, + )) + + # Reports centre is available as a role-aware workspace for every authenticated user. + cards.append(WorkspaceCard( + code="reports", + title="Reports Centre", + description="Role-aware reports grouped by work, clients, billing, HR, system and audit areas.", + url="/reports", + badge="Reports", + priority=90, + )) + + return sorted(cards, key=lambda card: card.priority) + + +def current_workspace_code(current_path: str | None) -> str: + path = current_path or "" + if path.startswith("/system-admin"): + return "system-admin" + if path.startswith("/firm-admin"): + return "firm-admin" + if path.startswith("/partner"): + return "partner" + if path.startswith("/manager"): + return "manager" + if path.startswith("/employee") or path.startswith("/employees"): + return "employee" + if path.startswith("/client"): + return "client" + if path.startswith("/consultant"): + return "consultant" + if path.startswith("/reports"): + return "reports" + return "" diff --git a/app/modules/workspace_navigation/templates/workspace_navigation/index.html b/app/modules/workspace_navigation/templates/workspace_navigation/index.html new file mode 100644 index 0000000..bdda384 --- /dev/null +++ b/app/modules/workspace_navigation/templates/workspace_navigation/index.html @@ -0,0 +1,34 @@ +{% extends "ui/templates/base/layout.html" %} + +{% block content %} +
+
+

Navigation Centre

+
+
+

My Workspaces

+

Open the workspace allowed for your role. This page does not replace any existing module; it only gives a clean entry point to dashboards and reports.

+
+ Open Reports Centre +
+
+ + +
+{% endblock %} diff --git a/app/modules/workspace_navigation/ui.py b/app/modules/workspace_navigation/ui.py new file mode 100644 index 0000000..e3be2e8 --- /dev/null +++ b/app/modules/workspace_navigation/ui.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from fastapi import APIRouter, Request +from fastapi.responses import RedirectResponse + +from app.core.db.common import CommonSessionLocal +from app.core.security.csrf import get_or_create_csrf_token +from app.core.security.session_auth import get_current_user +from app.core.templating import templates +from app.modules.core.rbac.deps import get_user_permissions, get_user_roles +from app.modules.workspace_navigation.service import build_workspace_cards, current_workspace_code + +router = APIRouter(tags=["workspace-navigation-ui"]) + + +@router.get("/workspaces") +def workspaces(request: Request): + db = CommonSessionLocal() + try: + current_user = get_current_user(request, db=db) + if not current_user: + return RedirectResponse(url="/login", status_code=303) + + roles = get_user_roles(db, current_user.id) + permissions = get_user_permissions(db, current_user.id) + cards = build_workspace_cards(roles, permissions) + current_path = request.url.path + + return templates.TemplateResponse( + "modules/workspace_navigation/templates/workspace_navigation/index.html", + { + "request": request, + "current_user": current_user, + "current_user_roles": roles, + "current_user_permissions": permissions, + "csrf_token": get_or_create_csrf_token(request), + "title": "My Workspaces", + "workspace_cards": cards, + "current_workspace_code": current_workspace_code(current_path), + }, + ) + finally: + db.close() diff --git a/app/ui/app.py b/app/ui/app.py index 795e892..4c39eef 100644 --- a/app/ui/app.py +++ b/app/ui/app.py @@ -28,6 +28,7 @@ from app.modules.wizards.ui import router as wizards_ui_router from app.modules.system_admin_dashboard.ui import router as system_admin_dashboard_router from app.ui.routes.auth import router as auth_router from app.modules.unified_reports.ui import router as unified_reports_router +from app.modules.workspace_navigation.ui import router as workspace_navigation_router from app.modules.firm_admin_dashboard.ui import router as firm_admin_dashboard_router @@ -51,6 +52,7 @@ def mount_ui(app: FastAPI) -> None: app.include_router(documents_ui_router) app.include_router(alerts_ui_router) app.include_router(unified_reports_router) + app.include_router(workspace_navigation_router) app.include_router(notice_cases_router) app.include_router(wizards_ui_router) app.include_router(system_admin_dashboard_router) diff --git a/app/ui/templates/components/workspace_switcher.html b/app/ui/templates/components/workspace_switcher.html index 147132d..67bb5aa 100644 --- a/app/ui/templates/components/workspace_switcher.html +++ b/app/ui/templates/components/workspace_switcher.html @@ -1,25 +1,21 @@ {% if full_auth %} - {% set ws = namespace(count=0) %} - {% if 'System Admin' in ui_roles %}{% set ws.count = ws.count + 1 %}{% endif %} - {% if 'Firm Admin' in ui_roles %}{% set ws.count = ws.count + 1 %}{% endif %} - {% if 'Partner' in ui_roles %}{% set ws.count = ws.count + 1 %}{% endif %} - {% if 'Manager' in ui_roles or 'Branch Manager' in ui_roles %}{% set ws.count = ws.count + 1 %}{% endif %} - {% if 'Staff' in ui_roles or can_view_employee_portal(current_user, ui_perms, ui_roles) %}{% set ws.count = ws.count + 1 %}{% endif %} - {% if 'Client' in ui_roles %}{% set ws.count = ws.count + 1 %}{% endif %} - {% if 'Consultant' in ui_roles %}{% set ws.count = ws.count + 1 %}{% endif %} - {% if ws.count > 1 %} -
+ {% set roles = ui_roles if ui_roles is defined else [] %} + {% 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) %} +
- + + {% if 'System Admin' in roles %}{% endif %} + {% if 'Partner' in roles %}{% endif %} + {% if 'Firm Admin' in roles %}{% endif %} + {% if 'Manager' in roles or 'Branch Manager' in roles %}{% endif %} + {% if can_employee %}{% endif %} + {% if 'Client' in roles %}{% endif %} + {% if 'Consultant' in roles %}{% endif %} + + All workspaces
- {% endif %} {% endif %}