UI Layout and Navigation¶
Status: Specified ยท Scope: All modules
Defines the application shell, navigation model, route structure, role-differentiated visibility, settings area, alert surfacing, and mobile behaviour. All module UIs are built within this shell.
App Shell¶
Three fixed regions. All module content renders in the main content area.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Top bar โ 48px fixed
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ โ
โ Sidebar โ Main content area โ
โ 240px โ flex-1, scrollable โ
โ fixed โ โ
โ โ โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Top bar¶
| Zone | Left | Centre | Right |
|---|---|---|---|
| Content | Tenant logo (white-label) | โ | Notification bell ยท User avatar + dropdown |
- Tenant logo โ renders the tenant's uploaded logo; falls back to the Construo wordmark.
Clicking it navigates to
/dashboard. - Notification bell โ shows unread count badge. Opens a dropdown of the 10 most recent
notifications, each linking directly to the relevant entity. "View all" link goes to
/notifications. - User avatar dropdown โ name, role label, "My preferences", "Sign out".
Sidebar¶
Fixed at 240px. Collapses to icon-only (48px) at โค1024px breakpoint via a toggle. On mobile (โค768px) collapses entirely behind a hamburger menu in the top bar.
Structure:
[Tenant name / logo mark]
โโโโโโโโโโโโโโโโโโโโโโโโ
Dashboard
Projects
โพ Canary Wharf Phase 2 โ expanded
Site Diary
Personnel
Attendance
Plant & Equipment
Incidents
Documents
Deliveries
Subcontractors
โธ Kings Cross Station โ collapsed
โธ A1 Upgrade โ Stevenage
+ New Project โ Project Manager and above only
โโโโโโโโโโโโโโโโโโโโโโโโ
Settings โ Tenant Admin only
โโโโโโโโโโโโโโโโโโโโโโโโ
[Avatar] Gari Smith
Technical Architect
Project expansion behaviour: - Clicking a collapsed project expands it in-place, revealing its module list. - Multiple projects can be expanded simultaneously. - The active route highlights the current module item. - The module list shown reflects the tenant's enabled modules โ disabled modules are hidden, not greyed out.
Main content area¶
Full height, scrollable. Contains:
- Page header โ page title, breadcrumb, primary action button (e.g. "New diary entry").
- Alert strip โ appears only when there are active alerts for the current context (see Alert Surfacing).
- Page content โ module-specific.
Navigation Model¶
Dashboard (default landing)¶
Route: /dashboard
Cross-project view. Content is scoped to what the authenticated user can see based on their role and project access. Project access is not embedded in the JWT โ the API checks it live (Redis-cached) on every request. The React app queries the project list and receives only the projects the user can access; it does not read or decode JWT claims for authorisation.
Page sections:
- Alert strip โ priority-ordered list of all items requiring action (see Alert Surfacing).
- Summary tiles โ four stat tiles: Active Sites ยท Open Incidents ยท Expiring This Week ยท Workers on Site Today. Values are aggregated across all accessible projects.
- Project cards โ one card per accessible project showing: project name, site count, active headcount, open incident count, and any overdue items. Clicking a card expands the project in the sidebar and navigates to the project overview.
- Recent activity feed โ chronological list of recent entries across modules (diary entries, incidents raised, new personnel added). Scoped to the user's accessible projects.
Project overview¶
Route: /projects/:projectId
Summary view for a single project.
- Site list with status and today's headcount per site
- Open incidents
- Items requiring attention (expiries, overdue inspections) scoped to this project
Module pages¶
Route: /projects/:projectId/[module] for project-level modules.
Route: /projects/:projectId/sites/:siteId/[module] for site-level modules.
See individual module specs for page structure.
Route Structure¶
/ โ redirect โ /dashboard
/dashboard โ cross-project dashboard
/projects โ project list (fallback if no projects)
/projects/new โ create project form
/projects/:projectId โ project overview
/projects/:projectId/edit โ edit project
/projects/:projectId/sites/:siteId โ site overview
/projects/:projectId/sites/:siteId/diary โ site diary
/projects/:projectId/sites/:siteId/attendance โ attendance / sign-in
/projects/:projectId/sites/:siteId/plant โ plant & equipment
/projects/:projectId/sites/:siteId/incidents โ incidents & near misses
/projects/:projectId/sites/:siteId/deliveries โ deliveries
/projects/:projectId/personnel โ personnel (project-level)
/projects/:projectId/documents โ document register
/projects/:projectId/subcontractors โ subcontractors
/projects/:projectId/inductions โ induction management
/notifications โ full notification history
/settings โ settings landing [Tenant Admin]
/settings/users โ user management
/settings/modules โ module enable/disable
/settings/fields โ field schema builder
/settings/notifications โ notification configuration
/settings/branding โ logo, colours, white-label
/profile โ user preferences, SMS opt-in/out
All routes under /settings redirect to /dashboard for non-admin roles. The Settings
nav item is hidden entirely for non-admin roles โ the redirect is a defence in depth measure.
Settings Area¶
Accessible only to Tenant Admin and Platform Admin roles. Reached via the "Settings" item at the bottom of the sidebar.
Settings has its own sub-navigation (left rail within the main content area):
| Section | Purpose |
|---|---|
| Users | Invite users, assign roles, deactivate accounts |
| Modules | Enable or disable platform modules for this tenant |
| Fields | Field schema builder โ add custom fields to any entity |
| Notifications | Configure event types: enabled, channels, recipients, thresholds |
| Branding | Upload logo, set primary colour, configure email from-name |
Settings pages do not have a project context โ they are tenant-wide.
Role-Differentiated Visibility¶
What each role sees¶
| Area | Platform Admin | Tenant Admin | Project Manager | Site Manager | Site Operative | Viewer |
|---|---|---|---|---|---|---|
| Dashboard | All tenants | All projects | Assigned projects | Assigned sites | Own sites | Assigned projects |
| Project list | All | All | Assigned | Assigned | Assigned | Assigned |
| Module pages | All | All | Full CRUD | Create/edit on assigned sites | Own entries only | Read-only |
| Settings | โ | Full access | Hidden | Hidden | Hidden | Hidden |
| New Project button | โ | โ | โ | Hidden | Hidden | Hidden |
| New Site button | โ | โ | โ | Hidden | Hidden | Hidden |
UI treatment of restricted actions¶
- Hidden โ element does not appear in the DOM. Used for entire nav sections and primary action buttons a role can never access.
- Disabled โ element is visible but non-interactive, with a tooltip explaining why. Used sparingly, only where awareness that the action exists is useful (e.g. a Site Manager seeing a greyed-out "Export PDF" that requires Project Manager role).
- Read-only form โ inputs render as static text. Used for Viewer role on all entity detail pages.
The API is always the authoritative enforcement point. UI visibility is UX only.
Alert Surfacing¶
Alerts appear in two places:
1. Notification bell (top bar)¶
- Badge shows count of unread notifications.
- Dropdown lists the 10 most recent, newest first.
- Each item shows: event type icon ยท description ยท entity name ยท time ago.
- Clicking an item marks it read and navigates to the entity.
- "Mark all read" action at the top of the dropdown.
- "View all" navigates to
/notifications.
2. Alert strip (dashboard and module pages)¶
The alert strip appears at the top of the main content area whenever there are active unresolved alerts in scope.
On the dashboard โ shows all alerts across all accessible projects, grouped by severity:
๐ด 2 overdue items LOLER cert expired (Site A crane) ยท RIDDOR deadline missed
๐ก 5 due this week 3 licences expiring ยท 1 plant inspection ยท 1 document
Clicking a grouped alert expands it to show individual items, each linking to the entity.
On a module page โ shows only alerts relevant to that module and project/site context. For example, on the Plant & Equipment page for Site A, the strip shows only plant-related alerts for Site A.
Severity colours:
| Colour | Meaning |
|---|---|
| ๐ด Red | Overdue or expired โ action required now |
| ๐ก Amber | Due within the configured threshold โ action required soon |
| ๐ต Blue | Informational โ e.g. induction blocked sign-in (resolved when induction completes) |
Mobile Responsive Behaviour¶
Breakpoints:
| Breakpoint | Behaviour |
|---|---|
| โฅ1280px | Full layout โ sidebar expanded at 240px |
| 1024pxโ1279px | Sidebar collapses to icon-only (48px) |
| โค768px | Sidebar hidden; hamburger menu in top bar opens a full-height overlay nav |
Mobile-specific layout rules:
- Tables โ render as stacked cards on โค768px. Each row becomes a card with label + value pairs. Sort and filter controls move to a slide-up drawer.
- Forms โ single column. Full-width inputs.
- Page header โ title and primary action button stack vertically if the title is long.
- Alert strip โ shows only the highest-severity alert; tap to expand all.
- Module pages โ no layout changes beyond the above. All modules are accessible on mobile.
High-frequency on-site flows (sign-in, diary entry, plant log, incident report) are tested on real devices in Sprint 18 and given priority treatment for touch targets and thumb reach.
Component Conventions¶
All UI is built with shadcn/ui components and Tailwind CSS utility classes. The following conventions apply across all module UIs:
| Pattern | Implementation |
|---|---|
| Data tables | TanStack Table v8 with shadcn Table. Sortable columns, column visibility toggle, CSV export. |
| Forms | React Hook Form + Zod. Inline field-level errors. Submit disabled until form is valid. |
| Modals / sheets | shadcn Dialog for confirmations and small forms. Sheet (slide-in panel) for larger create/edit forms. |
| Empty states | Centred illustration + heading + primary action button. Every list page must have an empty state. |
| Loading states | Skeleton loaders (shadcn Skeleton) for initial page load. Spinner on form submission buttons. |
| Confirmation dialogs | shadcn AlertDialog for destructive actions. Never use browser confirm(). |
| Toasts | shadcn Sonner for success/error feedback after mutations. Position: bottom-right. |
| Custom fields | Rendered dynamically from field_definitions. Field type determines component: text โ Input, select โ Select, date โ DatePicker, boolean โ Switch, file โ FileUpload. |