Upgrade unified reports centre to v2
This commit is contained in:
@@ -1 +1 @@
|
|||||||
"""Unified role-aware reports centre."""
|
"""Unified role-aware reports centre UI."""
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ MANAGER = {"Manager", "Branch Manager"}
|
|||||||
STAFF = {"Staff", "Employee"}
|
STAFF = {"Staff", "Employee"}
|
||||||
CLIENT = {"Client"}
|
CLIENT = {"Client"}
|
||||||
CONSULTANT = {"Consultant"}
|
CONSULTANT = {"Consultant"}
|
||||||
|
ALL_DASHBOARD_ROLES = SYSTEM_ADMIN | FIRM_ADMIN | PARTNER | MANAGER | STAFF | CLIENT | CONSULTANT
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -20,10 +21,12 @@ class ReportCard:
|
|||||||
group: str
|
group: str
|
||||||
roles: tuple[str, ...]
|
roles: tuple[str, ...]
|
||||||
badge: str = "Open"
|
badge: str = "Open"
|
||||||
|
area: str = "General"
|
||||||
|
importance: int = 50
|
||||||
|
|
||||||
|
|
||||||
def _role_set(roles: Iterable[str] | None) -> set[str]:
|
def _role_set(roles: Iterable[str] | None) -> set[str]:
|
||||||
return {str(r).strip() for r in (roles or []) if str(r).strip()}
|
return {str(role).strip() for role in (roles or []) if str(role).strip()}
|
||||||
|
|
||||||
|
|
||||||
def _has_any(user_roles: set[str], allowed: set[str]) -> bool:
|
def _has_any(user_roles: set[str], allowed: set[str]) -> bool:
|
||||||
@@ -33,7 +36,7 @@ def _has_any(user_roles: set[str], allowed: set[str]) -> bool:
|
|||||||
def can_access_reports(roles: Iterable[str] | None, permissions: Iterable[str] | None = None) -> bool:
|
def can_access_reports(roles: Iterable[str] | None, permissions: Iterable[str] | None = None) -> bool:
|
||||||
role_set = _role_set(roles)
|
role_set = _role_set(roles)
|
||||||
permission_set = set(permissions or [])
|
permission_set = set(permissions or [])
|
||||||
if role_set.intersection(SYSTEM_ADMIN | FIRM_ADMIN | PARTNER | MANAGER | STAFF | CLIENT | CONSULTANT):
|
if role_set.intersection(ALL_DASHBOARD_ROLES):
|
||||||
return True
|
return True
|
||||||
return bool(permission_set.intersection({
|
return bool(permission_set.intersection({
|
||||||
"system.settings.view",
|
"system.settings.view",
|
||||||
@@ -44,77 +47,86 @@ def can_access_reports(roles: Iterable[str] | None, permissions: Iterable[str] |
|
|||||||
"employees.ess.view",
|
"employees.ess.view",
|
||||||
"employees.work.view_self",
|
"employees.work.view_self",
|
||||||
"consultants.view",
|
"consultants.view",
|
||||||
|
"alerts.view",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
def _all_cards() -> list[ReportCard]:
|
def _all_cards() -> list[ReportCard]:
|
||||||
return [
|
return [
|
||||||
# System Admin
|
# System Admin
|
||||||
ReportCard("Firm Setup Completeness", "Review firm, branch, FY, admin invite, services and branding readiness.", "/system-admin/dashboard?tab=firm-setup-health", "system", ("System Admin",), "Dashboard"),
|
ReportCard("Platform Overview", "Open platform KPIs, firm status, SMTP, storage and setup indicators.", "/system-admin/dashboard", "system", ("System Admin",), "Dashboard", "Platform", 5),
|
||||||
ReportCard("Firm List / Tenant Report", "Open platform firm list with branch, user and setup summary.", "/system-admin/dashboard?tab=firms", "system", ("System Admin",), "Dashboard"),
|
ReportCard("Firm Setup Completeness", "Review firm, branch, FY, admin invite, services and branding readiness.", "/system-admin/dashboard?tab=firm-setup-health", "system", ("System Admin",), "Dashboard", "Setup", 10),
|
||||||
ReportCard("Service Catalogue Readiness", "Check service categories, catalogue items and default task template readiness.", "/system-admin/dashboard?tab=catalogue", "system", ("System Admin",), "Dashboard"),
|
ReportCard("Firm List / Tenant Report", "Open platform firm list with branch, user and setup summary.", "/system-admin/dashboard?tab=firms", "system", ("System Admin",), "Dashboard", "Platform", 20),
|
||||||
ReportCard("Platform SMTP Status", "Review platform SMTP configuration used for firm invites and system emails.", "/system-admin/dashboard?tab=smtp", "system", ("System Admin",), "Dashboard"),
|
ReportCard("Service Catalogue Readiness", "Check service categories, catalogue items and default task template readiness.", "/system-admin/dashboard?tab=catalogue", "system", ("System Admin",), "Dashboard", "Services", 30),
|
||||||
ReportCard("Storage Status", "Review persistent storage, branding storage and backup storage readiness.", "/system-admin/dashboard?tab=storage", "system", ("System Admin",), "Dashboard"),
|
ReportCard("Platform SMTP Status", "Review platform SMTP configuration used for firm invites and system emails.", "/system-admin/dashboard?tab=smtp", "system", ("System Admin",), "Dashboard", "Email", 35),
|
||||||
ReportCard("Platform Audit Logs", "Open audit log trail for platform-level actions.", "/system-admin/dashboard?tab=audit-logs", "audit", ("System Admin",), "Dashboard"),
|
ReportCard("Storage Status", "Review persistent storage, branding storage and backup storage readiness.", "/system-admin/dashboard?tab=storage", "system", ("System Admin",), "Dashboard", "Storage", 40),
|
||||||
ReportCard("Platform Billing Readiness", "Review platform billing and subscription readiness indicators.", "/system-admin/dashboard?tab=billing", "billing", ("System Admin",), "Dashboard"),
|
ReportCard("Platform Billing Readiness", "Review platform billing and subscription readiness indicators.", "/system-admin/dashboard?tab=billing", "billing", ("System Admin",), "Dashboard", "Billing", 45),
|
||||||
|
ReportCard("Platform Audit Logs", "Open audit log trail for platform-level actions.", "/system-admin/dashboard?tab=audit-logs", "audit", ("System Admin",), "Dashboard", "Audit", 50),
|
||||||
|
|
||||||
# Firm Admin
|
# Firm Admin
|
||||||
ReportCard("Branch Setup Report", "Review branches, primary branch readiness and branch setup shortcuts.", "/firm-admin/dashboard?tab=branches", "system", ("Firm Admin",), "Dashboard"),
|
ReportCard("Firm Administration Overview", "Open firm setup health, branches, users, services, FY and settings.", "/firm-admin/dashboard", "system", ("Firm Admin",), "Dashboard", "Firm Setup", 5),
|
||||||
ReportCard("Users & Roles Report", "Review user access, roles and invite/admin readiness for the firm.", "/firm-admin/dashboard?tab=users", "system", ("Firm Admin",), "Dashboard"),
|
ReportCard("Branch Setup Report", "Review branches, primary branch readiness and branch setup shortcuts.", "/firm-admin/dashboard?tab=branches", "system", ("Firm Admin",), "Dashboard", "Branches", 10),
|
||||||
ReportCard("Firm Settings Completion", "Open firm profile, branding, SMTP and setup status.", "/firm-admin/dashboard?tab=firm-settings", "system", ("Firm Admin",), "Dashboard"),
|
ReportCard("Users & Roles Report", "Review user access, roles and invite/admin readiness for the firm.", "/firm-admin/dashboard?tab=users", "system", ("Firm Admin",), "Dashboard", "Users", 15),
|
||||||
ReportCard("Services Setup Readiness", "Review selected services and task-template setup readiness.", "/firm-admin/dashboard?tab=services", "work", ("Firm Admin",), "Dashboard"),
|
ReportCard("Firm Settings Completion", "Open firm profile, branding, SMTP and setup status.", "/firm-admin/dashboard?tab=firm-settings", "system", ("Firm Admin",), "Dashboard", "Settings", 20),
|
||||||
ReportCard("Financial Year Report", "Review active FY, FY backup/lock and year setup readiness.", "/firm-admin/dashboard?tab=financial-years", "system", ("Firm Admin",), "Dashboard"),
|
ReportCard("Services Setup Readiness", "Review selected services and task-template setup readiness.", "/firm-admin/dashboard?tab=services", "work", ("Firm Admin",), "Dashboard", "Services", 25),
|
||||||
ReportCard("Firm Audit Logs", "Open firm administration audit activity.", "/firm-admin/dashboard?tab=audit-logs", "audit", ("Firm Admin",), "Dashboard"),
|
ReportCard("Financial Year Report", "Review active FY, FY backup/lock and year setup readiness.", "/firm-admin/dashboard?tab=financial-years", "system", ("Firm Admin",), "Dashboard", "FY", 30),
|
||||||
|
ReportCard("Firm Administration Audit Logs", "Open firm administration audit activity.", "/firm-admin/dashboard?tab=audit-logs", "audit", ("Firm Admin",), "Dashboard", "Audit", 35),
|
||||||
|
|
||||||
# Partner
|
# Partner
|
||||||
ReportCard("Branch Work Report", "Review branch work status, due work and service-wise operational load.", "/partner/dashboard?tab=branch-work", "work", ("Partner",), "Dashboard"),
|
ReportCard("Partner Operations Overview", "Open branch operations, client work, staff load, review and billing status.", "/partner/dashboard", "work", ("Partner",), "Dashboard", "Operations", 5),
|
||||||
ReportCard("Partner Client Report", "Review branch clients, service mapping, pending work and status.", "/partner/dashboard?tab=clients", "client", ("Partner",), "Dashboard"),
|
ReportCard("Branch Work Report", "Review branch work status, due work and service-wise operational load.", "/partner/dashboard?tab=branch-work", "work", ("Partner",), "Dashboard", "Work", 10),
|
||||||
ReportCard("Partner Staff Workload", "Review staff workload, open tasks, overdue and review pending work.", "/partner/dashboard?tab=staff", "work", ("Partner",), "Dashboard"),
|
ReportCard("Partner Client Report", "Review branch clients, service mapping, pending work and status.", "/partner/dashboard?tab=clients", "client", ("Partner",), "Dashboard", "Clients", 15),
|
||||||
ReportCard("Partner Review Pending", "Open partner-level review queue and pending approvals.", "/partner/dashboard?tab=review", "work", ("Partner",), "Dashboard"),
|
ReportCard("Partner Staff Workload", "Review staff workload, open tasks, overdue and review pending work.", "/partner/dashboard?tab=staff", "work", ("Partner",), "Dashboard", "Staff", 20),
|
||||||
ReportCard("Branch Billing Report", "Review branch billing, unbilled work and collection indicators.", "/partner/dashboard?tab=billing", "billing", ("Partner",), "Dashboard"),
|
ReportCard("Partner Review Pending", "Open partner-level review queue and pending approvals.", "/partner/dashboard?tab=review", "work", ("Partner",), "Dashboard", "Review", 25),
|
||||||
|
ReportCard("Branch Billing Report", "Review branch billing, unbilled work and collection indicators.", "/partner/dashboard?tab=billing", "billing", ("Partner",), "Dashboard", "Billing", 30),
|
||||||
|
|
||||||
# Manager
|
# Manager
|
||||||
ReportCard("Team Work Report", "Review team work, due tasks, workload and current execution status.", "/manager/dashboard?tab=team-work", "work", ("Manager", "Branch Manager"), "Dashboard"),
|
ReportCard("Manager Execution Overview", "Open team work, due calendar, review queue and escalations.", "/manager/dashboard", "work", ("Manager", "Branch Manager"), "Dashboard", "Execution", 5),
|
||||||
ReportCard("Manager Due Calendar", "Review due calendar, upcoming work and ageing buckets.", "/manager/dashboard?tab=due-calendar", "work", ("Manager", "Branch Manager"), "Dashboard"),
|
ReportCard("Team Work Report", "Review team work, due tasks, workload and current execution status.", "/manager/dashboard?tab=team-work", "work", ("Manager", "Branch Manager"), "Dashboard", "Team", 10),
|
||||||
ReportCard("Manager Review Queue", "Open tasks waiting for manager review or correction.", "/manager/dashboard?tab=review-queue", "work", ("Manager", "Branch Manager"), "Dashboard"),
|
ReportCard("Manager Due Calendar", "Review due calendar, upcoming work and ageing buckets.", "/manager/dashboard?tab=due-calendar", "work", ("Manager", "Branch Manager"), "Dashboard", "Due Dates", 15),
|
||||||
ReportCard("Client Pending Under Manager", "Review tasks blocked because client data or documents are pending.", "/manager/dashboard?tab=client-pending", "client", ("Manager", "Branch Manager"), "Dashboard"),
|
ReportCard("Manager Review Queue", "Open tasks waiting for manager review or correction.", "/manager/dashboard?tab=review-queue", "work", ("Manager", "Branch Manager"), "Dashboard", "Review", 20),
|
||||||
ReportCard("Document Pending Report", "Review document requirements, uploads and pending work papers.", "/manager/dashboard?tab=documents", "work", ("Manager", "Branch Manager"), "Dashboard"),
|
ReportCard("Client Pending Under Manager", "Review tasks blocked because client data or documents are pending.", "/manager/dashboard?tab=client-pending", "client", ("Manager", "Branch Manager"), "Dashboard", "Client Pending", 25),
|
||||||
ReportCard("Escalation Register", "Review overdue, ageing and escalation items under manager control.", "/manager/dashboard?tab=escalations", "audit", ("Manager", "Branch Manager"), "Dashboard"),
|
ReportCard("Document Pending Report", "Review document requirements, uploads and pending work papers.", "/manager/dashboard?tab=documents", "work", ("Manager", "Branch Manager"), "Dashboard", "Documents", 30),
|
||||||
|
ReportCard("Escalation Register", "Review overdue, ageing and escalation items under manager control.", "/manager/dashboard?tab=escalations", "audit", ("Manager", "Branch Manager"), "Dashboard", "Escalations", 35),
|
||||||
|
|
||||||
# Employee / Staff
|
# Employee / Staff
|
||||||
ReportCard("My Pending Tasks", "Open my work board and pending assigned tasks.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal"),
|
ReportCard("Employee Portal Overview", "Open staff dashboard with attendance, work, HR, documents and reports.", "/employee/dashboard", "my", ("Staff", "Employee"), "Portal", "Personal", 5),
|
||||||
ReportCard("My Due Today", "Open work due today from the employee portal.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal"),
|
ReportCard("My Pending Tasks", "Open my work board and pending assigned tasks.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal", "Work", 10),
|
||||||
ReportCard("My Overdue Tasks", "Open overdue and returned work from the employee portal.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal"),
|
ReportCard("My Due Today", "Open due today work from the employee portal work section.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal", "Work", 15),
|
||||||
ReportCard("My Attendance Report", "Open attendance punch details and attendance history.", "/employee/dashboard?tab=attendance", "hr", ("Staff", "Employee"), "Portal"),
|
ReportCard("My Overdue Tasks", "Open overdue and returned work from the employee portal.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal", "Work", 20),
|
||||||
ReportCard("My Leave Report", "Open leave, balances and leave activity from HR profile.", "/employee/dashboard?tab=hr", "hr", ("Staff", "Employee"), "Portal"),
|
ReportCard("My Attendance Report", "Open attendance punch details and attendance history.", "/employee/dashboard?tab=attendance", "hr", ("Staff", "Employee"), "Portal", "Attendance", 25),
|
||||||
ReportCard("My Documents Report", "Open my documents and uploaded work papers.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal"),
|
ReportCard("My Leave Report", "Open leave, balances and leave activity from HR profile.", "/employee/dashboard?tab=hr", "hr", ("Staff", "Employee"), "Portal", "Leave", 30),
|
||||||
|
ReportCard("My Documents Report", "Open my documents and uploaded work papers.", "/employee/dashboard?tab=work", "my", ("Staff", "Employee"), "Portal", "Documents", 35),
|
||||||
|
|
||||||
# Client
|
# Client
|
||||||
ReportCard("My Compliance Status", "Review service-wise compliance and current work status.", "/client/dashboard?tab=services-documents", "client", ("Client",), "Portal"),
|
ReportCard("Client Portal Overview", "Open client portal with pending actions, services, documents, billing and messages.", "/client/dashboard", "client", ("Client",), "Portal", "Client Portal", 5),
|
||||||
ReportCard("Pending From Me", "Open documents, data and replies pending from the client side.", "/client/dashboard?tab=action-centre", "client", ("Client",), "Portal"),
|
ReportCard("Pending From Me", "Open documents, data and replies pending from the client side.", "/client/dashboard?tab=action-centre", "client", ("Client",), "Portal", "Action Centre", 10),
|
||||||
ReportCard("Document History", "Open uploaded documents, firm shared documents and acknowledgements.", "/client/dashboard?tab=services-documents", "client", ("Client",), "Portal"),
|
ReportCard("My Compliance Status", "Review service-wise compliance and current work status.", "/client/dashboard?tab=services-documents", "client", ("Client",), "Portal", "Services", 15),
|
||||||
ReportCard("Invoice Outstanding", "Open invoices, outstanding amounts and payment status.", "/client/dashboard?tab=billing-messages", "billing", ("Client",), "Portal"),
|
ReportCard("Document History", "Open uploaded documents, firm shared documents and acknowledgements.", "/client/dashboard?tab=services-documents", "client", ("Client",), "Portal", "Documents", 20),
|
||||||
ReportCard("Payment History", "Open payment and receipt information in the client portal.", "/client/dashboard?tab=billing-messages", "billing", ("Client",), "Portal"),
|
ReportCard("Invoice Outstanding", "Open invoices, outstanding amounts and payment status.", "/client/dashboard?tab=billing-messages", "billing", ("Client",), "Portal", "Billing", 25),
|
||||||
ReportCard("Messages / Queries", "Open firm messages and query responses.", "/client/dashboard?tab=billing-messages", "client", ("Client",), "Portal"),
|
ReportCard("Payment History", "Open payment and receipt information in the client portal.", "/client/dashboard?tab=billing-messages", "billing", ("Client",), "Portal", "Payments", 30),
|
||||||
|
ReportCard("Messages / Queries", "Open firm messages and query responses.", "/client/dashboard?tab=billing-messages", "client", ("Client",), "Portal", "Messages", 35),
|
||||||
|
|
||||||
# Consultant
|
# Consultant
|
||||||
ReportCard("Assigned Work Report", "Review consultant work centre and assigned tasks.", "/consultant/dashboard?tab=work-centre", "my", ("Consultant",), "Portal"),
|
ReportCard("Consultant Portal Overview", "Open consultant portal with work centre, clients, requests, documents and reports.", "/consultant/dashboard", "my", ("Consultant",), "Portal", "Consultant Portal", 5),
|
||||||
ReportCard("Consultant Pending Work", "Review pending, due and overdue consultant assignments.", "/consultant/dashboard?tab=work-centre", "my", ("Consultant",), "Portal"),
|
ReportCard("Assigned Work Report", "Review consultant work centre and assigned tasks.", "/consultant/dashboard?tab=work-centre", "my", ("Consultant",), "Portal", "Work", 10),
|
||||||
ReportCard("Consultant Client Report", "Review consultant-linked clients and service requests.", "/consultant/dashboard?tab=clients-requests", "client", ("Consultant",), "Portal"),
|
ReportCard("Consultant Pending Work", "Review pending, due and overdue consultant assignments.", "/consultant/dashboard?tab=work-centre", "my", ("Consultant",), "Portal", "Work", 15),
|
||||||
ReportCard("Document Pending for Consultant", "Review consultant document and working paper requirements.", "/consultant/dashboard?tab=documents-clarifications", "my", ("Consultant",), "Portal"),
|
ReportCard("Consultant Client Report", "Review consultant-linked clients and service requests.", "/consultant/dashboard?tab=clients-requests", "client", ("Consultant",), "Portal", "Clients", 20),
|
||||||
ReportCard("Clarification Pending", "Review clarifications between firm and consultant.", "/consultant/dashboard?tab=documents-clarifications", "my", ("Consultant",), "Portal"),
|
ReportCard("Document Pending for Consultant", "Review consultant document and working paper requirements.", "/consultant/dashboard?tab=documents-clarifications", "my", ("Consultant",), "Portal", "Documents", 25),
|
||||||
ReportCard("Consultant Service Requests", "Review consultant service requests and status.", "/consultant/dashboard?tab=clients-requests", "client", ("Consultant",), "Portal"),
|
ReportCard("Clarification Pending", "Review clarifications between firm and consultant.", "/consultant/dashboard?tab=documents-clarifications", "my", ("Consultant",), "Portal", "Clarifications", 30),
|
||||||
|
ReportCard("Consultant Service Requests", "Review consultant service requests and status.", "/consultant/dashboard?tab=clients-requests", "client", ("Consultant",), "Portal", "Requests", 35),
|
||||||
|
|
||||||
# Existing module shortcuts visible to operational users
|
# Existing module shortcuts visible to operational users
|
||||||
ReportCard("Client Master", "Open the existing client master list and filters.", "/clients", "client", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module"),
|
ReportCard("Client Master", "Open the existing client master list and filters.", "/clients", "client", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module", "Clients", 40),
|
||||||
ReportCard("Work Tracker", "Open the existing work tracker for operational task follow-up.", "/services/work-tracker", "work", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module"),
|
ReportCard("Documents Module", "Open document management and client/task document records.", "/documents", "client", ("System Admin", "Firm Admin", "Partner", "Manager", "Staff", "Employee"), "Module", "Documents", 45),
|
||||||
ReportCard("Subscriptions", "Open client service subscriptions and task generation readiness.", "/services/work-tracker/subscriptions", "work", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module"),
|
ReportCard("Work Tracker", "Open the existing work tracker for operational task follow-up.", "/services/work-tracker", "work", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module", "Tasks", 40),
|
||||||
ReportCard("Billing Register", "Open existing billing invoices and payments area.", "/billing", "billing", ("System Admin", "Firm Admin", "Partner"), "Module"),
|
ReportCard("Subscriptions", "Open client service subscriptions and task generation readiness.", "/services/work-tracker/subscriptions", "work", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module", "Services", 45),
|
||||||
ReportCard("Billing Payments", "Open existing payment receipts and collections.", "/billing/payments", "billing", ("System Admin", "Firm Admin", "Partner"), "Module"),
|
ReportCard("Billing Register", "Open existing billing invoices and payments area.", "/billing", "billing", ("System Admin", "Firm Admin", "Partner"), "Module", "Invoices", 40),
|
||||||
ReportCard("Employee Attendance", "Open employee attendance administration page.", "/employees/attendance", "hr", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module"),
|
ReportCard("Billing Payments", "Open existing payment receipts and collections.", "/billing/payments", "billing", ("System Admin", "Firm Admin", "Partner"), "Module", "Payments", 45),
|
||||||
ReportCard("Employee Progress", "Open employee progress and performance page.", "/employees/progress", "hr", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module"),
|
ReportCard("Employee Attendance", "Open employee attendance administration page.", "/employees/attendance", "hr", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module", "Attendance", 40),
|
||||||
ReportCard("Alerts", "Open alerts and notification centre.", "/alerts", "audit", ("System Admin", "Firm Admin", "Partner", "Manager", "Staff", "Employee", "Client", "Consultant"), "Module"),
|
ReportCard("Employee Progress", "Open employee progress and performance page.", "/employees/progress", "hr", ("System Admin", "Firm Admin", "Partner", "Manager"), "Module", "Progress", 45),
|
||||||
ReportCard("Audit Logs", "Open existing audit log page.", "/system-settings/audit-logs", "audit", ("System Admin", "Firm Admin"), "Module"),
|
ReportCard("Alerts", "Open alerts and notification centre.", "/alerts", "audit", ("System Admin", "Firm Admin", "Partner", "Manager", "Staff", "Employee", "Client", "Consultant"), "Module", "Alerts", 40),
|
||||||
|
ReportCard("Audit Logs", "Open existing audit log page.", "/system-settings/audit-logs", "audit", ("System Admin", "Firm Admin"), "Module", "Audit", 45),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -126,33 +138,45 @@ def _visible(card: ReportCard, roles: set[str]) -> bool:
|
|||||||
|
|
||||||
def get_visible_report_cards(roles: Iterable[str] | None, permissions: Iterable[str] | None = None) -> list[ReportCard]:
|
def get_visible_report_cards(roles: Iterable[str] | None, permissions: Iterable[str] | None = None) -> list[ReportCard]:
|
||||||
roles_set = _role_set(roles)
|
roles_set = _role_set(roles)
|
||||||
cards = [card for card in _all_cards() if _visible(card, roles_set)]
|
cards = sorted([card for card in _all_cards() if _visible(card, roles_set)], key=lambda c: (c.importance, c.title))
|
||||||
|
|
||||||
# Permission fallback for older accounts where explicit role names may be absent.
|
# Permission fallback for older accounts where explicit role names may be absent.
|
||||||
permission_set = set(permissions or [])
|
permission_set = set(permissions or [])
|
||||||
if not cards:
|
if not cards:
|
||||||
if "clients.view" in permission_set:
|
if "clients.view" in permission_set:
|
||||||
cards.append(ReportCard("Client Master", "Open the existing client master list and filters.", "/clients", "client", tuple(), "Module"))
|
cards.append(ReportCard("Client Master", "Open the existing client master list and filters.", "/clients", "client", tuple(), "Module", "Clients", 10))
|
||||||
if "services.view" in permission_set:
|
if "services.view" in permission_set:
|
||||||
cards.append(ReportCard("Work Tracker", "Open the existing work tracker for operational task follow-up.", "/services/work-tracker", "work", tuple(), "Module"))
|
cards.append(ReportCard("Work Tracker", "Open the existing work tracker for operational task follow-up.", "/services/work-tracker", "work", tuple(), "Module", "Tasks", 10))
|
||||||
if "billing.view" in permission_set:
|
if "billing.view" in permission_set:
|
||||||
cards.append(ReportCard("Billing Register", "Open existing billing invoices and payments area.", "/billing", "billing", tuple(), "Module"))
|
cards.append(ReportCard("Billing Register", "Open existing billing invoices and payments area.", "/billing", "billing", tuple(), "Module", "Invoices", 10))
|
||||||
if "employees.ess.view" in permission_set or "employees.work.view_self" in permission_set:
|
if "employees.ess.view" in permission_set or "employees.work.view_self" in permission_set:
|
||||||
cards.append(ReportCard("My Employee Portal", "Open your employee dashboard, attendance and work board.", "/employee/dashboard", "my", tuple(), "Portal"))
|
cards.append(ReportCard("My Employee Portal", "Open your employee dashboard, attendance and work board.", "/employee/dashboard", "my", tuple(), "Portal", "Personal", 10))
|
||||||
return cards
|
return cards
|
||||||
|
|
||||||
|
|
||||||
def cards_by_group(cards: Iterable[ReportCard]) -> dict[str, list[ReportCard]]:
|
def cards_by_group(cards: Iterable[ReportCard]) -> dict[str, list[ReportCard]]:
|
||||||
groups = {"my": [], "work": [], "client": [], "billing": [], "hr": [], "system": [], "audit": []}
|
groups = {"overview": [], "my": [], "work": [], "client": [], "billing": [], "hr": [], "system": [], "audit": []}
|
||||||
for card in cards:
|
for card in cards:
|
||||||
groups.setdefault(card.group, []).append(card)
|
groups.setdefault(card.group, []).append(card)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
|
||||||
|
def cards_by_area(cards: Iterable[ReportCard]) -> dict[str, list[ReportCard]]:
|
||||||
|
area_groups: dict[str, list[ReportCard]] = {}
|
||||||
|
for card in cards:
|
||||||
|
area_groups.setdefault(card.area, []).append(card)
|
||||||
|
return dict(sorted(area_groups.items(), key=lambda item: item[0]))
|
||||||
|
|
||||||
|
|
||||||
|
def recommended_cards(cards: Iterable[ReportCard], limit: int = 8) -> list[ReportCard]:
|
||||||
|
return sorted(list(cards), key=lambda c: (c.importance, c.title))[:limit]
|
||||||
|
|
||||||
|
|
||||||
def get_report_summary(cards: Iterable[ReportCard]) -> dict[str, int]:
|
def get_report_summary(cards: Iterable[ReportCard]) -> dict[str, int]:
|
||||||
groups = cards_by_group(cards)
|
groups = cards_by_group(cards)
|
||||||
|
all_cards = list(cards)
|
||||||
return {
|
return {
|
||||||
"total": sum(len(v) for v in groups.values()),
|
"total": len(all_cards),
|
||||||
"my": len(groups.get("my", [])),
|
"my": len(groups.get("my", [])),
|
||||||
"work": len(groups.get("work", [])),
|
"work": len(groups.get("work", [])),
|
||||||
"client": len(groups.get("client", [])),
|
"client": len(groups.get("client", [])),
|
||||||
@@ -160,17 +184,21 @@ def get_report_summary(cards: Iterable[ReportCard]) -> dict[str, int]:
|
|||||||
"hr": len(groups.get("hr", [])),
|
"hr": len(groups.get("hr", [])),
|
||||||
"system": len(groups.get("system", [])),
|
"system": len(groups.get("system", [])),
|
||||||
"audit": len(groups.get("audit", [])),
|
"audit": len(groups.get("audit", [])),
|
||||||
|
"dashboards": len([card for card in all_cards if card.badge == "Dashboard"]),
|
||||||
|
"portals": len([card for card in all_cards if card.badge == "Portal"]),
|
||||||
|
"modules": len([card for card in all_cards if card.badge == "Module"]),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_report_tabs(cards: Iterable[ReportCard]) -> list[dict[str, str | int]]:
|
def get_report_tabs(cards: Iterable[ReportCard]) -> list[dict[str, str | int]]:
|
||||||
summary = get_report_summary(cards)
|
summary = get_report_summary(cards)
|
||||||
return [
|
return [
|
||||||
|
{"key": "overview", "label": "Overview", "count": summary["total"]},
|
||||||
{"key": "my-reports", "label": "My Reports", "count": summary["my"]},
|
{"key": "my-reports", "label": "My Reports", "count": summary["my"]},
|
||||||
{"key": "work", "label": "Work Reports", "count": summary["work"]},
|
{"key": "work", "label": "Work", "count": summary["work"]},
|
||||||
{"key": "client", "label": "Client Reports", "count": summary["client"]},
|
{"key": "client", "label": "Client & Documents", "count": summary["client"]},
|
||||||
{"key": "billing", "label": "Billing Reports", "count": summary["billing"]},
|
{"key": "billing", "label": "Billing", "count": summary["billing"]},
|
||||||
{"key": "hr", "label": "HR / Attendance", "count": summary["hr"]},
|
{"key": "hr", "label": "HR / Attendance", "count": summary["hr"]},
|
||||||
{"key": "system", "label": "System Reports", "count": summary["system"]},
|
{"key": "system", "label": "System & Setup", "count": summary["system"]},
|
||||||
{"key": "audit", "label": "Audit / Alerts", "count": summary["audit"]},
|
{"key": "audit", "label": "Audit / Alerts", "count": summary["audit"]},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-brand-600">Role-wise Reports Centre</p>
|
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-brand-600">Role-wise Reports Centre V2</p>
|
||||||
<h1 class="mt-2 text-2xl font-bold text-slate-900">Unified Reports Centre</h1>
|
<h1 class="mt-2 text-2xl font-bold text-slate-900">Unified Reports Centre</h1>
|
||||||
<p class="mt-1 text-sm text-slate-500">Open the reports and existing report modules relevant to your role and current workspace.</p>
|
<p class="mt-1 text-sm text-slate-500">Use one role-aware place to open your dashboards, existing reports, portals and audit views.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-2 text-center sm:grid-cols-4">
|
<div class="grid grid-cols-2 gap-2 text-center sm:grid-cols-4">
|
||||||
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
||||||
@@ -14,30 +14,36 @@
|
|||||||
<div class="text-xs text-slate-500">Available</div>
|
<div class="text-xs text-slate-500">Available</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
||||||
<div class="text-lg font-bold text-slate-900">{{ report_summary.work }}</div>
|
<div class="text-lg font-bold text-slate-900">{{ report_summary.dashboards }}</div>
|
||||||
<div class="text-xs text-slate-500">Work</div>
|
<div class="text-xs text-slate-500">Dashboards</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
||||||
<div class="text-lg font-bold text-slate-900">{{ report_summary.client }}</div>
|
<div class="text-lg font-bold text-slate-900">{{ report_summary.portals }}</div>
|
||||||
<div class="text-xs text-slate-500">Client</div>
|
<div class="text-xs text-slate-500">Portals</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
<div class="rounded-2xl bg-slate-50 px-4 py-3">
|
||||||
<div class="text-lg font-bold text-slate-900">{{ report_summary.billing }}</div>
|
<div class="text-lg font-bold text-slate-900">{{ report_summary.modules }}</div>
|
||||||
<div class="text-xs text-slate-500">Billing</div>
|
<div class="text-xs text-slate-500">Modules</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 overflow-x-auto">
|
<div class="mt-6 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||||
<div class="flex min-w-max gap-2 rounded-2xl bg-slate-100 p-1">
|
<div class="overflow-x-auto">
|
||||||
{% for tab in report_tabs %}
|
<div class="flex min-w-max gap-2 rounded-2xl bg-slate-100 p-1">
|
||||||
<a href="/reports?tab={{ tab.key }}"
|
{% for tab in report_tabs %}
|
||||||
data-reports-tab="{{ tab.key }}"
|
<a href="/reports?tab={{ tab.key }}"
|
||||||
class="reports-tab rounded-xl px-4 py-2 text-sm font-semibold transition {% if active_tab == tab.key %}bg-white text-brand-700 shadow-soft{% else %}text-slate-600 hover:bg-white hover:text-slate-900{% endif %}">
|
data-reports-tab="{{ tab.key }}"
|
||||||
{{ tab.label }}
|
class="reports-tab rounded-xl px-4 py-2 text-sm font-semibold transition {% if active_tab == tab.key %}bg-white text-brand-700 shadow-soft{% else %}text-slate-600 hover:bg-white hover:text-slate-900{% endif %}">
|
||||||
<span class="ml-1 rounded-full bg-white/70 px-2 py-0.5 text-xs text-slate-500">{{ tab.count }}</span>
|
{{ tab.label }}
|
||||||
</a>
|
<span class="ml-1 rounded-full bg-white/70 px-2 py-0.5 text-xs text-slate-500">{{ tab.count }}</span>
|
||||||
{% endfor %}
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full lg:w-80">
|
||||||
|
<label for="reports-search" class="sr-only">Search reports</label>
|
||||||
|
<input id="reports-search" type="search" placeholder="Search report cards..." class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-700 outline-none focus:border-brand-400 focus:ring-2 focus:ring-brand-100">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,6 +57,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const panel = document.getElementById('reports-centre-panel');
|
const panel = document.getElementById('reports-centre-panel');
|
||||||
const tabs = Array.from(document.querySelectorAll('[data-reports-tab]'));
|
const tabs = Array.from(document.querySelectorAll('[data-reports-tab]'));
|
||||||
|
const searchInput = document.getElementById('reports-search');
|
||||||
if (!panel || !tabs.length) return;
|
if (!panel || !tabs.length) return;
|
||||||
|
|
||||||
function setActive(tabName) {
|
function setActive(tabName) {
|
||||||
@@ -63,6 +70,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applySearch() {
|
||||||
|
const query = (searchInput && searchInput.value || '').trim().toLowerCase();
|
||||||
|
const cards = Array.from(panel.querySelectorAll('[data-report-card]'));
|
||||||
|
let shown = 0;
|
||||||
|
cards.forEach((card) => {
|
||||||
|
const haystack = (card.dataset.searchText || card.textContent || '').toLowerCase();
|
||||||
|
const visible = !query || haystack.includes(query);
|
||||||
|
card.classList.toggle('hidden', !visible);
|
||||||
|
if (visible) shown += 1;
|
||||||
|
});
|
||||||
|
const empty = panel.querySelector('[data-report-search-empty]');
|
||||||
|
if (empty) empty.classList.toggle('hidden', !query || shown > 0);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadTab(tabName, pushState) {
|
async function loadTab(tabName, pushState) {
|
||||||
setActive(tabName);
|
setActive(tabName);
|
||||||
panel.innerHTML = '<div class="rounded-3xl border border-slate-200 bg-white p-8 text-center text-sm text-slate-500 shadow-soft">Loading...</div>';
|
panel.innerHTML = '<div class="rounded-3xl border border-slate-200 bg-white p-8 text-center text-sm text-slate-500 shadow-soft">Loading...</div>';
|
||||||
@@ -72,6 +93,7 @@
|
|||||||
});
|
});
|
||||||
if (!response.ok) throw new Error('Tab load failed');
|
if (!response.ok) throw new Error('Tab load failed');
|
||||||
panel.innerHTML = await response.text();
|
panel.innerHTML = await response.text();
|
||||||
|
applySearch();
|
||||||
if (pushState) {
|
if (pushState) {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set('tab', tabName);
|
url.searchParams.set('tab', tabName);
|
||||||
@@ -89,12 +111,16 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.addEventListener('input', applySearch);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('popstate', function () {
|
window.addEventListener('popstate', function () {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
loadTab(url.searchParams.get('tab') || 'my-reports', false);
|
loadTab(url.searchParams.get('tab') || 'overview', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
loadTab(panel.dataset.initialTab || 'my-reports', false);
|
loadTab(panel.dataset.initialTab || 'overview', false);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -8,16 +8,23 @@
|
|||||||
<div class="text-sm text-slate-500">{{ active_cards|length }} available</div>
|
<div class="text-sm text-slate-500">{{ active_cards|length }} available</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div data-report-search-empty class="mt-6 hidden rounded-2xl border border-dashed border-slate-300 bg-slate-50 p-6 text-sm text-slate-600">
|
||||||
|
No report cards match your search.
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if active_cards %}
|
{% if active_cards %}
|
||||||
<div class="mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
<div class="mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||||
{% for card in active_cards %}
|
{% for card in active_cards %}
|
||||||
<a href="{{ card.href }}" class="group rounded-2xl border border-slate-200 bg-slate-50 p-5 transition hover:border-brand-200 hover:bg-white hover:shadow-soft">
|
<a href="{{ card.href }}" data-report-card data-search-text="{{ card.title }} {{ card.description }} {{ card.badge }} {{ card.area }}" class="group rounded-2xl border border-slate-200 bg-slate-50 p-5 transition hover:border-brand-200 hover:bg-white hover:shadow-soft">
|
||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between gap-3">
|
||||||
<div class="font-semibold text-slate-900 group-hover:text-brand-700">{{ card.title }}</div>
|
<div>
|
||||||
|
<div class="font-semibold text-slate-900 group-hover:text-brand-700">{{ card.title }}</div>
|
||||||
|
<div class="mt-1 text-xs font-semibold uppercase tracking-wide text-slate-400">{{ card.area }}</div>
|
||||||
|
</div>
|
||||||
<span class="shrink-0 rounded-full bg-white px-2 py-1 text-[11px] font-semibold text-slate-500">{{ card.badge }}</span>
|
<span class="shrink-0 rounded-full bg-white px-2 py-1 text-[11px] font-semibold text-slate-500">{{ card.badge }}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm leading-6 text-slate-600">{{ card.description }}</p>
|
<p class="mt-2 text-sm leading-6 text-slate-600">{{ card.description }}</p>
|
||||||
<div class="mt-4 text-sm font-semibold text-brand-700">Open report →</div>
|
<div class="mt-4 text-sm font-semibold text-brand-700">Open →</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% set section_kicker = "Clients" %}
|
{% set section_kicker = "Clients & Documents" %}
|
||||||
{% set section_title = "Client Reports" %}
|
{% set section_title = "Client & Document Reports" %}
|
||||||
{% set section_description = "Client master, client portal, pending-from-client, documents and compliance status reports." %}
|
{% set section_description = "Client master, client portal, pending-from-client, documents and compliance status report links." %}
|
||||||
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% set section_kicker = "HR & Attendance" %}
|
{% set section_kicker = "HR & Attendance" %}
|
||||||
{% set section_title = "HR / Attendance Reports" %}
|
{% set section_title = "HR / Attendance Reports" %}
|
||||||
{% set section_description = "Employee attendance, leave, progress and personal HR reports." %}
|
{% set section_description = "Employee attendance, leave, progress and personal HR report links." %}
|
||||||
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% set section_kicker = "Personal Reports" %}
|
{% set section_kicker = "Personal" %}
|
||||||
{% set section_title = "My Reports" %}
|
{% set section_title = "My Reports" %}
|
||||||
{% set section_description = "Personal work, attendance, client or consultant portal reports available to your login." %}
|
{% set section_description = "Personal work, attendance, client or consultant portal reports available to your login." %}
|
||||||
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<div class="space-y-6">
|
||||||
|
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
|
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||||
|
<div class="text-sm font-semibold text-slate-500">Total Available</div>
|
||||||
|
<div class="mt-2 text-3xl font-bold text-slate-900">{{ report_summary.total }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-400">Role-aware report cards</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||||
|
<div class="text-sm font-semibold text-slate-500">Work / Execution</div>
|
||||||
|
<div class="mt-2 text-3xl font-bold text-slate-900">{{ report_summary.work }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-400">Task, review and due reports</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||||
|
<div class="text-sm font-semibold text-slate-500">Client / Documents</div>
|
||||||
|
<div class="mt-2 text-3xl font-bold text-slate-900">{{ report_summary.client }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-400">Client, portal and document views</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
|
||||||
|
<div class="text-sm font-semibold text-slate-500">Admin / Audit</div>
|
||||||
|
<div class="mt-2 text-3xl font-bold text-slate-900">{{ report_summary.system + report_summary.audit }}</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-400">Setup and audit shortcuts</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||||
|
<div class="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
|
||||||
|
<div>
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-brand-600">Recommended</p>
|
||||||
|
<h2 class="mt-1 text-xl font-bold text-slate-900">Start here</h2>
|
||||||
|
<p class="mt-1 text-sm text-slate-500">Most useful reports and dashboard links for your current role.</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-slate-500">{{ recommended_cards|length }} recommended</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-report-search-empty class="mt-6 hidden rounded-2xl border border-dashed border-slate-300 bg-slate-50 p-6 text-sm text-slate-600">
|
||||||
|
No report cards match your search.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
|
{% for card in recommended_cards %}
|
||||||
|
<a href="{{ card.href }}" data-report-card data-search-text="{{ card.title }} {{ card.description }} {{ card.badge }} {{ card.area }}" class="group rounded-2xl border border-slate-200 bg-slate-50 p-5 transition hover:border-brand-200 hover:bg-white hover:shadow-soft">
|
||||||
|
<div class="flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div class="font-semibold text-slate-900 group-hover:text-brand-700">{{ card.title }}</div>
|
||||||
|
<div class="mt-1 text-xs font-semibold uppercase tracking-wide text-slate-400">{{ card.area }}</div>
|
||||||
|
</div>
|
||||||
|
<span class="shrink-0 rounded-full bg-white px-2 py-1 text-[11px] font-semibold text-slate-500">{{ card.badge }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-slate-600">{{ card.description }}</p>
|
||||||
|
<div class="mt-4 text-sm font-semibold text-brand-700">Open →</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||||
|
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-brand-600">Grouped View</p>
|
||||||
|
<h2 class="mt-1 text-xl font-bold text-slate-900">Report areas</h2>
|
||||||
|
<div class="mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||||
|
{% for area, cards in report_area_groups.items() %}
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||||||
|
<div class="font-semibold text-slate-900">{{ area }}</div>
|
||||||
|
<div class="mt-1 text-sm text-slate-500">{{ cards|length }} report link{{ '' if cards|length == 1 else 's' }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% set section_kicker = "Administration" %}
|
{% set section_kicker = "Administration" %}
|
||||||
{% set section_title = "System Reports" %}
|
{% set section_title = "System & Setup Reports" %}
|
||||||
{% set section_description = "Firm setup, branch setup, users, roles, SMTP, storage, FY and platform readiness reports." %}
|
{% set section_description = "Firm setup, branch setup, users, roles, SMTP, storage, FY and platform readiness report links." %}
|
||||||
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
{% include "modules/unified_reports/templates/unified_reports/partials/_cards.html" %}
|
||||||
|
|||||||
@@ -11,15 +11,18 @@ from app.core.templating import templates
|
|||||||
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
||||||
from app.modules.unified_reports.service import (
|
from app.modules.unified_reports.service import (
|
||||||
can_access_reports,
|
can_access_reports,
|
||||||
|
cards_by_area,
|
||||||
cards_by_group,
|
cards_by_group,
|
||||||
get_report_summary,
|
get_report_summary,
|
||||||
get_report_tabs,
|
get_report_tabs,
|
||||||
get_visible_report_cards,
|
get_visible_report_cards,
|
||||||
|
recommended_cards,
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/reports", tags=["unified-reports-ui"])
|
router = APIRouter(prefix="/reports", tags=["unified-reports-ui"])
|
||||||
|
|
||||||
_ALLOWED_TABS = {
|
_ALLOWED_TABS = {
|
||||||
|
"overview": "overview",
|
||||||
"my-reports": "my",
|
"my-reports": "my",
|
||||||
"work": "work",
|
"work": "work",
|
||||||
"client": "client",
|
"client": "client",
|
||||||
@@ -27,6 +30,20 @@ _ALLOWED_TABS = {
|
|||||||
"hr": "hr",
|
"hr": "hr",
|
||||||
"system": "system",
|
"system": "system",
|
||||||
"audit": "audit",
|
"audit": "audit",
|
||||||
|
# Backward-compatible aliases from V1 direct links.
|
||||||
|
"my_reports": "my",
|
||||||
|
}
|
||||||
|
|
||||||
|
_PARTIALS = {
|
||||||
|
"overview": "modules/unified_reports/templates/unified_reports/partials/overview.html",
|
||||||
|
"my-reports": "modules/unified_reports/templates/unified_reports/partials/my_reports.html",
|
||||||
|
"my_reports": "modules/unified_reports/templates/unified_reports/partials/my_reports.html",
|
||||||
|
"work": "modules/unified_reports/templates/unified_reports/partials/work.html",
|
||||||
|
"client": "modules/unified_reports/templates/unified_reports/partials/client.html",
|
||||||
|
"billing": "modules/unified_reports/templates/unified_reports/partials/billing.html",
|
||||||
|
"hr": "modules/unified_reports/templates/unified_reports/partials/hr.html",
|
||||||
|
"system": "modules/unified_reports/templates/unified_reports/partials/system.html",
|
||||||
|
"audit": "modules/unified_reports/templates/unified_reports/partials/audit.html",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -55,6 +72,8 @@ def _payload(db, current_user):
|
|||||||
"current_user_permissions": permissions,
|
"current_user_permissions": permissions,
|
||||||
"report_cards": cards,
|
"report_cards": cards,
|
||||||
"report_groups": groups,
|
"report_groups": groups,
|
||||||
|
"report_area_groups": cards_by_area(cards),
|
||||||
|
"recommended_cards": recommended_cards(cards),
|
||||||
"report_tabs": get_report_tabs(cards),
|
"report_tabs": get_report_tabs(cards),
|
||||||
"report_summary": get_report_summary(cards),
|
"report_summary": get_report_summary(cards),
|
||||||
}
|
}
|
||||||
@@ -72,13 +91,13 @@ def _base_context(request: Request, db, current_user, **extra):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
def reports_home(request: Request, tab: str = "my-reports"):
|
def reports_home(request: Request, tab: str = "overview"):
|
||||||
db = CommonSessionLocal()
|
db = CommonSessionLocal()
|
||||||
try:
|
try:
|
||||||
current_user, response = _current_user_or_response(request, db)
|
current_user, response = _current_user_or_response(request, db)
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
active_tab = tab if tab in _ALLOWED_TABS else "my-reports"
|
active_tab = tab if tab in _ALLOWED_TABS else "overview"
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"modules/unified_reports/templates/unified_reports/dashboard.html",
|
"modules/unified_reports/templates/unified_reports/dashboard.html",
|
||||||
_base_context(request, db, current_user, title="Reports Centre", active_tab=active_tab),
|
_base_context(request, db, current_user, title="Reports Centre", active_tab=active_tab),
|
||||||
@@ -94,10 +113,12 @@ def reports_tab(request: Request, tab_name: str):
|
|||||||
current_user, response = _current_user_or_response(request, db)
|
current_user, response = _current_user_or_response(request, db)
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
active_tab = tab_name if tab_name in _ALLOWED_TABS else "my-reports"
|
active_tab = tab_name if tab_name in _ALLOWED_TABS else "overview"
|
||||||
group_key = _ALLOWED_TABS[active_tab]
|
group_key = _ALLOWED_TABS[active_tab]
|
||||||
|
cards = get_visible_report_cards(get_user_roles(db, current_user.id), get_user_permissions(db, current_user.id))
|
||||||
|
group_cards = cards_by_group(cards)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
f"modules/unified_reports/templates/unified_reports/partials/{active_tab.replace('-', '_')}.html",
|
_PARTIALS[active_tab],
|
||||||
_base_context(
|
_base_context(
|
||||||
request,
|
request,
|
||||||
db,
|
db,
|
||||||
@@ -105,7 +126,7 @@ def reports_tab(request: Request, tab_name: str):
|
|||||||
title="Reports Centre",
|
title="Reports Centre",
|
||||||
active_tab=active_tab,
|
active_tab=active_tab,
|
||||||
active_group=group_key,
|
active_group=group_key,
|
||||||
active_cards=cards_by_group(get_visible_report_cards(get_user_roles(db, current_user.id), get_user_permissions(db, current_user.id))).get(group_key, []),
|
active_cards=cards if group_key == "overview" else group_cards.get(group_key, []),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Reference in New Issue
Block a user