NDSS CRM Manual / Core Platform / Chapter 5: Dashboard & Navigation
V3.8 · 2024/2025

Chapter 5: Dashboard & Navigation

The NDSS CRM dashboard is the central command centre for managing every aspect of NDIS and disability care operations. Upon successful authentication, users are presented with a role-specific dashboard that surfaces the most relevant information, alerts, and quick actions for their position within the organisation. This chapter provides an exhaustive breakdown of every dashboard component, navigation mechanism, and interactive widget available within the platform. The dashboard is built using Next.js, React, and TypeScript on the front end, with Python and PHP microservices powering data aggregation and PostgreSQL storing all persistent state.

5.1 Dashboard Overview

NDSS CRM provides 20 distinct dashboard views, each tailored to a specific user role within the system. When a user logs in, the platform determines their primary role from the user_roles table in PostgreSQL and renders the corresponding dashboard layout. The dashboard framework is component-based, using React server components for initial data fetching and client components for interactive widgets.

5.1.1 Role-Specific Dashboard Matrix

The following table enumerates every dashboard variant, including the role it serves, the primary data focus, and the number of widgets rendered by default.

# Dashboard Name Role Primary Focus Default Widgets
1Administrator DashboardSuper AdminFull system overview, all metrics12
2Organisation Admin DashboardOrg AdminOrganisation-level KPIs10
3Operations Manager DashboardOps ManagerRostering, compliance, incidents9
4Finance DashboardFinance OfficerRevenue, invoices, claims8
5HR Manager DashboardHR ManagerStaff, onboarding, training8
6Support Coordinator DashboardCoordinatorClient plans, goals, progress7
7Team Leader DashboardTeam LeaderTeam shifts, performance7
8Rostering Officer DashboardRoster OfficerShift allocation, availability8
9Compliance Officer DashboardComplianceIncidents, audits, certifications7
10Clinical Lead DashboardClinical LeadCare plans, health records6
11Support Worker DashboardSupport WorkerMy shifts, my clients5
12Intake Officer DashboardIntake OfficerReferrals, assessments6
13Training Coordinator DashboardTraining CoordCourses, completions, overdue6
14SIL Coordinator DashboardSIL CoordGroup homes, SIL rosters7
15Client Portal DashboardClient/GuardianMy plan, bookings, progress5
16Plan Manager DashboardPlan ManagerFunding, claims, budgets7
17Front Desk DashboardReceptionistVisitors, calls, messages5
18Auditor DashboardExternal AuditorRead-only compliance view4
19Specialist Services DashboardSpecialistSpecialist appointments5
20Executive DashboardExecutiveHigh-level analytics only6

5.1.2 Dashboard Architecture

Each dashboard is composed of modular widget components that are registered in the dashboard_widgets configuration table. The widget registry is managed via a Python-based configuration service that determines which widgets are available for each role. The rendering pipeline works as follows:

  1. The Next.js server component fetches the user's role from the session JWT.
  2. A Python microservice is called to retrieve the widget configuration for that role from PostgreSQL.
  3. Each widget is rendered as an independent React component with its own data-fetching logic.
  4. PHP backend services handle legacy data aggregation for certain financial and compliance widgets.
  5. Widgets that require real-time updates subscribe to Supabase / Oracle Realtime channels.
Technical Note

Dashboard widget configurations are cached in Redis with a 5-minute TTL. Changes to widget layouts via the Admin Settings panel take effect within this window. The caching layer is implemented in Python using the redis-py library, while the PHP services use predis/predis.

5.1.3 Dashboard Data Flow

// Dashboard Data Flow (Simplified)
// Next.js Server Component → Python Aggregation Service → PostgreSQL
// Widget Rendering Pipeline

export async function DashboardPage() {
  const session = await getServerSession();
  const role = session.user.role;

  // Fetch widget config from Python service
  const widgetConfig = await fetch(
    `${PYTHON_SERVICE_URL}/api/dashboard/widgets?role=${role}`
  );

  // Fetch aggregated data
  const dashboardData = await fetch(
    `${PYTHON_SERVICE_URL}/api/dashboard/data?role=${role}&org_id=${session.user.org_id}`
  );

  return (
    <DashboardLayout>
      {widgetConfig.widgets.map(widget => (
        <DashboardWidget
          key={widget.id}
          type={widget.type}
          config={widget.config}
          data={dashboardData[widget.dataKey]}
        />
      ))}
    </DashboardLayout>
  );
}

5.2 Administrator Dashboard

The Administrator Dashboard is the most comprehensive view in NDSS CRM, providing a complete operational snapshot. It is available to users with the super_admin or org_admin role. This section details every element visible on the administrator's default dashboard.

5.2.1 Full Dashboard Wireframe

The following wireframe illustrates the complete Administrator Dashboard layout, including the top bar, stat cards, activity feed, and analytics panels.

Administrator Dashboard - Full Layout
N NDSS CRM
Dashboard
Clients
Staff
Rostering
Finance
Compliance
Reports
🔔 5 DA Demo Administrator
148
Active Clients +8%
64
Active Staff +2%
23
Shifts Today
7
Open Incidents -15%
$284,500
Revenue (AU$) +12%
34
NDIS Claims Pending
Recent Activity
2 min ago New shift created for James Thompson
15 min ago Invoice #INV-0042 approved by Finance
1 hr ago Staff member Sarah Chen completed CPR cert
2 hrs ago Incident #INC-019 resolved and closed
3 hrs ago New client referral received: Maria Garcia
Smart Alerts
3 Expired Certifications
Staff members with overdue First Aid renewals
2 Unallocated Shifts
Tomorrow: 8:00 AM - 12:00 PM (Parramatta)
5 Overdue Invoices
Total outstanding: AU$12,340
Compliance Review Due
Quarterly audit scheduled for 15 Apr 2024
Monthly Revenue (Recharts)
Shift Completion Rate

5.2.2 Stat Cards Specification

The stat cards at the top of the Administrator Dashboard provide an at-a-glance summary of key operational metrics. Each card is a self-contained React component that fetches its data independently, enabling partial page updates without full re-renders.

Stat Card Data Source Refresh Interval Trend Indicator Click Action
Active Clients clients table, status = 'active' 5 minutes +8% vs last month Navigate to Client List
Active Staff staff table, status = 'active' 5 minutes +2% vs last month Navigate to Staff Directory
Shifts Today shifts table, date = CURRENT_DATE 1 minute None (absolute count) Navigate to Today's Roster
Open Incidents incidents table, status IN ('open','investigating') 2 minutes -15% vs last month Navigate to Incidents List
Revenue (AU$) Python aggregation of invoices + payments tables 15 minutes +12% vs last month Navigate to Finance Overview
NDIS Claims Pending ndis_claims table, status = 'pending' 5 minutes None (absolute count) Navigate to Claims Queue

5.2.3 Preview Role Button

The Preview Role button allows administrators to temporarily view the dashboard as any other role in the system. This is invaluable for troubleshooting permission issues, verifying that role-specific widgets display correctly, and training staff on what their dashboard will look like. When activated, a prominent banner appears at the top of the page indicating the previewed role, along with an "Exit Preview" button to return to the admin view.

// Preview Role API Endpoint (Python Flask)
@app.route('/api/admin/preview-role', methods=['POST'])
def preview_role():
    admin_id = get_current_user_id()
    target_role = request.json.get('role')

    # Validate admin has permission to preview
    if not has_permission(admin_id, 'admin.preview_role'):
        return jsonify({'error': 'Insufficient permissions'}), 403

    # Create temporary session overlay
    session_token = create_preview_session(admin_id, target_role)

    return jsonify({
        'preview_token': session_token,
        'role': target_role,
        'expires_in': 3600  # 1 hour
    })

5.3 Smart Alerts Panel

The Smart Alerts Panel is a priority-ranked feed of actionable items that require immediate attention. Alerts are generated by a Python background worker that runs every 60 seconds, scanning multiple database tables for threshold violations, upcoming deadlines, and anomalous conditions.

5.3.1 Alert Categories

Category Severity Trigger Condition Example Alert Action Link
Expired Certifications Critical Staff certification expiry_date < CURRENT_DATE "3 staff members have expired First Aid certificates" Staff → Qualifications
Expiring Certifications Warning Certification expires within 30 days "Sarah Chen's NDIS Worker Screening expires in 12 days" Staff Profile → Qualifications
Unallocated Shifts Warning Shift staff_id IS NULL and date <= CURRENT_DATE + 7 "2 shifts in next 24 hours have no staff assigned" Rostering → Unallocated
Overdue Invoices Warning Invoice due_date < CURRENT_DATE and status = 'unpaid' "5 invoices totalling AU$12,340 are overdue" Finance → Invoices
Compliance Deadlines Info Audit/review due within 14 days "Quarterly compliance audit due on 15 Apr 2024" Compliance → Audits
NDIS Plan Expiry Critical Client NDIS plan expires within 30 days "James Thompson's NDIS plan expires in 8 days" Client Profile → Funding
Incomplete Timesheets Warning Shift completed but no timesheet submitted within 48 hours "7 timesheets pending submission from last week" Rostering → Timesheets
Funding Budget Alert Critical Client funding utilisation exceeds 85% "Maria Garcia: Core Supports at 92% utilisation" Client Profile → Funding

5.3.2 Alert Priority Engine

Alerts are scored using a weighted priority algorithm implemented in Python. The scoring factors include:

# Alert Priority Scoring (Python)
def calculate_alert_priority(alert):
    base_score = SEVERITY_WEIGHTS[alert.severity]  # Critical=100, Warning=60, Info=20

    # Time decay: alerts become more urgent as deadlines approach
    if alert.deadline:
        days_remaining = (alert.deadline - datetime.now()).days
        time_factor = max(0, 1 - (days_remaining / 30))
        base_score += time_factor * 40

    # Impact factor: number of affected entities
    impact_factor = min(alert.affected_count / 10, 1.0)
    base_score += impact_factor * 20

    # Financial impact (if applicable)
    if alert.financial_amount:
        financial_factor = min(alert.financial_amount / 50000, 1.0)
        base_score += financial_factor * 30

    return min(base_score, 200)  # Cap at 200

5.3.3 Alert Wireframe

Smart Alerts Panel - Expanded View
Smart Alerts 8 Active
All Critical (3) Warning (4) Info (1)
CRITICAL - 3 Expired Certifications
Staff: Mark Wilson, Lisa Park, Tom Nguyen
View Details →
CRITICAL - NDIS Plan Expiry: James Thompson
Plan expires in 8 days (12 Apr 2024)
View Client →
CRITICAL - Funding Alert: Maria Garcia
Core Supports at 92% utilisation
View Funding →
WARNING - 2 Unallocated Shifts
Tomorrow, 8:00 AM – 12:00 PM (Parramatta)
Assign Staff →
WARNING - 5 Overdue Invoices
Total: AU$12,340 outstanding
View Invoices →

5.4 Activity Feed

The Activity Feed provides a chronological timeline of all system events relevant to the current user's scope. Events are streamed via Supabase / Oracle Realtime and stored in the activity_log table in PostgreSQL. The feed supports infinite scroll pagination, loading 25 events per batch.

5.4.1 Event Types

Event Type Icon Description Example
client.created👤New client record created"New client Maria Garcia added by Jane Smith"
client.updatedClient record modified"James Thompson NDIS plan updated"
shift.created📅New shift scheduled"Shift created: J. Thompson, Mon 8 Apr 9:00-13:00"
shift.completedShift marked as completed"Shift #SH-0234 completed by Mark Wilson"
shift.cancelledShift was cancelled"Shift #SH-0235 cancelled (client request)"
invoice.created💰New invoice generated"Invoice #INV-0042 created: AU$1,250"
invoice.approvedInvoice approved for payment"Invoice #INV-0042 approved by Finance"
incident.reportedNew incident report filed"Incident #INC-020 reported at SIL House Alpha"
incident.resolvedIncident closed and resolved"Incident #INC-019 resolved and closed"
staff.certified📜Staff certification updated"Sarah Chen completed CPR certification"
referral.received📥New client referral received"New referral: Maria Garcia from GP Dr. Patel"
system.backup💾System backup completed"Nightly backup completed successfully"

5.4.2 Activity Feed Filtering

Users can filter the activity feed by event category using the tab bar above the timeline. Available filter groups:

  • All - Every event type (default)
  • Clients - client.* events only
  • Shifts - shift.* events only
  • Finance - invoice.*, payment.* events
  • Compliance - incident.*, audit.*, certification.* events
  • System - system.* events
// Activity Feed Filter Component (TypeScript/React)
interface ActivityFilter {
  label: string;
  eventTypes: string[];
  icon: React.ReactNode;
}

const ACTIVITY_FILTERS: ActivityFilter[] = [
  { label: 'All', eventTypes: ['*'], icon: <ListIcon /> },
  { label: 'Clients', eventTypes: ['client.*'], icon: <UserIcon /> },
  { label: 'Shifts', eventTypes: ['shift.*'], icon: <CalendarIcon /> },
  { label: 'Finance', eventTypes: ['invoice.*', 'payment.*'], icon: <DollarIcon /> },
  { label: 'Compliance', eventTypes: ['incident.*', 'audit.*'], icon: <ShieldIcon /> },
  { label: 'System', eventTypes: ['system.*'], icon: <ServerIcon /> },
];

5.4.3 Activity Feed Database Schema

-- PostgreSQL: activity_log table
CREATE TABLE activity_log (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  org_id        UUID NOT NULL REFERENCES organisations(id),
  event_type    VARCHAR(100) NOT NULL,
  actor_id      UUID REFERENCES users(id),
  actor_name    VARCHAR(255),
  entity_type   VARCHAR(50),    -- 'client', 'shift', 'invoice', etc.
  entity_id     UUID,
  summary       TEXT NOT NULL,
  metadata      JSONB DEFAULT '{}',
  created_at    TIMESTAMPTZ DEFAULT NOW(),

  INDEX idx_activity_org_created (org_id, created_at DESC),
  INDEX idx_activity_event_type (event_type)
);

5.5 Analytics Widgets

NDSS CRM uses Recharts (a React charting library) to render interactive data visualisations within dashboard widgets. All chart data is aggregated by a Python data pipeline that runs scheduled queries against PostgreSQL and caches results in Redis. PHP services supplement this by providing legacy financial report data through a RESTful interface.

5.5.1 Available Chart Types

Chart Widget Chart Type Data Source Default Period Available To Roles
Monthly RevenueBar ChartInvoices + PaymentsLast 12 monthsAdmin, Finance, Executive
Shift Completion RateLine ChartShifts tableLast 8 weeksAdmin, Ops Manager, Roster Officer
Client GrowthArea ChartClients tableLast 12 monthsAdmin, Executive
Funding UtilisationStacked BarNDIS Claims + BudgetsCurrent quarterAdmin, Finance, Plan Manager
Incident TrendsLine ChartIncidents tableLast 6 monthsAdmin, Compliance, Ops Manager
Staff Availability HeatmapHeatmapAvailability tableCurrent weekAdmin, Roster Officer
Service Category BreakdownPie/Donut ChartShifts + Service typesCurrent monthAdmin, Ops Manager
Timesheet Approval RateGauge ChartTimesheets tableCurrent pay periodAdmin, Finance, HR

5.5.2 Recharts Integration

// Revenue Bar Chart Component (TypeScript + Recharts)
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';

interface RevenueData {
  month: string;
  revenue: number;
  claims: number;
}

export function RevenueChart({ data }: { data: RevenueData[] }) {
  return (
    <ResponsiveContainer width="100%" height={300}>
      <BarChart data={data}>
        <CartesianGrid strokeDasharray="3 3" />
        <XAxis dataKey="month" />
        <YAxis tickFormatter={(v) => `$${(v/1000).toFixed(0)}k`} />
        <Tooltip
          formatter={(value: number) => [`AU$${value.toLocaleString()}`, 'Revenue']}
        />
        <Bar dataKey="revenue" fill="#E8672A" radius={[4,4,0,0]} />
        <Bar dataKey="claims" fill="#1a1f2e" radius={[4,4,0,0]} />
      </BarChart>
    </ResponsiveContainer>
  );
}

5.5.3 Analytics Widget Wireframe

Analytics Widgets - Chart Row
Monthly Revenue Last 12 months
AprMayJunJulAugSepOctNovDecJanFebMar
Service Category Breakdown Current month
Core 58% Capacity 28% Capital 14%

5.6 Navigation System

The NDSS CRM navigation system is a collapsible sidebar that organises all platform modules into logical groups. The sidebar is persistent on desktop viewports (greater than 1024px) and collapses into a hamburger menu on tablet and mobile devices. Navigation items are rendered conditionally based on the user's role permissions.

5.6.1 Sidebar Navigation Structure

The sidebar is divided into the following section groups. Each section contains navigation items that link to specific platform modules.

Sidebar Navigation - Full Structure
N NDSS CRM
People
Clients
Staff
Intake & Referrals
Front Desk
Operations
Rostering
Finance
Compliance
Services Ops
Human Resources
HR Hub
Onboarding
Learning & Dev
Training Records
Communication
Messaging
Reports
Administration
Settings
SIL & Specialist

5.6.2 Role-Based Menu Visibility

Not all sidebar items are visible to every role. The following matrix shows which navigation groups are accessible by role:

Navigation Group Admin Ops Manager Coordinator Support Worker Finance HR Compliance
PeopleAllAllClients onlyMy ClientsNoneStaff onlyNone
OperationsAllAllRosteringMy ShiftsFinanceNoneCompliance
Human ResourcesAllHR HubNoneMy TrainingNoneAllTraining Records
CommunicationAllAllMessagingMessagingReportsNoneReports
AdministrationAllSettingsNoneNoneNoneNoneNone

5.6.3 Sidebar Collapse Behaviour

The sidebar supports three states: expanded (full width with labels, 260px), collapsed (icon-only, 64px), and hidden (off-screen on mobile). Users can toggle between expanded and collapsed using the hamburger menu icon or the keyboard shortcut Ctrl + B (Windows/Linux) or Cmd + B (macOS). The collapse state is persisted to localStorage so it survives page refreshes.

// Sidebar State Management (TypeScript)
type SidebarState = 'expanded' | 'collapsed' | 'hidden';

interface SidebarContext {
  state: SidebarState;
  toggle: () => void;
  expand: () => void;
  collapse: () => void;
}

// Keyboard shortcut handler
useEffect(() => {
  const handler = (e: KeyboardEvent) => {
    if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
      e.preventDefault();
      toggleSidebar();
    }
  };
  window.addEventListener('keydown', handler);
  return () => window.removeEventListener('keydown', handler);
}, []);

5.7 Global Search (Cmd+K)

The Global Search modal provides a universal search experience across all NDSS CRM entities. It is triggered by pressing Cmd+K (macOS) or Ctrl+K (Windows/Linux), or by clicking the search bar in the top navigation. The search uses a full-text search index in PostgreSQL combined with a Python search service that handles ranking and result aggregation.

5.7.1 Search Modal Wireframe

Global Search Modal - Cmd+K
Clients
JT
James Thompson
ND-00001 · NDIS: 430123456789 · Parramatta NSW
JA
James Anderson
ND-00005 · NDIS: 430987654321 · Blacktown NSW
Shifts
📅
Shift #SH-0234 - James Thompson
Mon 8 Apr, 9:00 AM – 1:00 PM · Completed
Staff
JR
James Rodriguez
Support Worker · Full-time · Active

5.7.2 Search Scope & Indexing

Entity Searchable Fields Index Type Result Display
ClientsName, NDIS number, phone, email, suburbGIN (tsvector)Name, ID, NDIS number, location
StaffName, email, phone, roleGIN (tsvector)Name, role, employment type, status
ShiftsShift ID, client name, staff name, dateB-tree + GINShift ID, client, date/time, status
InvoicesInvoice number, client name, amountB-tree + GINInvoice number, client, amount, status
IncidentsIncident ID, description, locationGIN (tsvector)Incident ID, summary, severity
DocumentsFile name, tags, associated entityGIN (tsvector)File name, type, upload date

5.7.3 Search Implementation

# Python Search Service (Flask)
from flask import Flask, request, jsonify
import psycopg2
from psycopg2.extras import RealDictCursor

@app.route('/api/search', methods=['GET'])
def global_search():
    query = request.args.get('q', '')
    org_id = request.args.get('org_id')
    limit = int(request.args.get('limit', 20))

    if len(query) < 2:
        return jsonify({'results': []})

    results = {}

    # Search clients
    clients = db.execute("""
        SELECT id, first_name, last_name, ndis_number, suburb, state
        FROM clients
        WHERE org_id = %s
          AND search_vector @@ plainto_tsquery('english', %s)
        ORDER BY ts_rank(search_vector, plainto_tsquery('english', %s)) DESC
        LIMIT %s
    """, (org_id, query, query, limit))

    results['clients'] = clients

    # Search staff
    staff = db.execute("""
        SELECT id, first_name, last_name, role, employment_type, status
        FROM staff
        WHERE org_id = %s
          AND search_vector @@ plainto_tsquery('english', %s)
        ORDER BY ts_rank(search_vector, plainto_tsquery('english', %s)) DESC
        LIMIT %s
    """, (org_id, query, query, limit))

    results['staff'] = staff

    # Search shifts
    shifts = db.execute("""
        SELECT s.id, s.date, s.start_time, s.end_time, s.status,
               c.first_name || ' ' || c.last_name AS client_name
        FROM shifts s
        JOIN clients c ON s.client_id = c.id
        WHERE s.org_id = %s
          AND (s.id::text ILIKE %s OR c.first_name ILIKE %s OR c.last_name ILIKE %s)
        ORDER BY s.date DESC
        LIMIT %s
    """, (org_id, f'%{query}%', f'%{query}%', f'%{query}%', limit))

    results['shifts'] = shifts

    return jsonify({'results': results, 'total': sum(len(v) for v in results.values())})

5.8 Quick Actions FAB

The Quick Actions Floating Action Button (FAB) is a persistent circular button positioned in the bottom-right corner of the viewport. When clicked, it expands to reveal a set of shortcut actions for common tasks. The FAB is role-aware, showing only the actions that the current user has permission to perform.

5.8.1 Quick Action Items

Action Icon Keyboard Shortcut Available To Navigates To
New Client👤+Alt+CAdmin, Coordinator, IntakeClient creation form
New Shift📅+Alt+SAdmin, Roster Officer, CoordinatorShift creation form
New Invoice💰+Alt+IAdmin, FinanceInvoice creation form
New Incident⚠+Alt+NAll staff rolesIncident report form
New Staff👥+Alt+TAdmin, HRStaff onboarding form
New Referral📥+Alt+RAdmin, IntakeReferral intake form

5.8.2 FAB Wireframe

Quick Actions FAB - Expanded State
New Client 👤
New Shift 📅
New Invoice 💰
New Incident
+

5.8.3 FAB Component Implementation

// Quick Actions FAB Component (TypeScript/React)
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { usePermissions } from '@/hooks/usePermissions';

interface QuickAction {
  id: string;
  label: string;
  icon: string;
  href: string;
  permission: string;
}

const QUICK_ACTIONS: QuickAction[] = [
  { id: 'new-client', label: 'New Client', icon: 'UserPlus', href: '/clients/new', permission: 'clients.create' },
  { id: 'new-shift', label: 'New Shift', icon: 'CalendarPlus', href: '/rostering/new', permission: 'shifts.create' },
  { id: 'new-invoice', label: 'New Invoice', icon: 'Receipt', href: '/finance/invoices/new', permission: 'invoices.create' },
  { id: 'new-incident', label: 'New Incident', icon: 'AlertTriangle', href: '/compliance/incidents/new', permission: 'incidents.create' },
];

export function QuickActionsFAB() {
  const [isOpen, setIsOpen] = useState(false);
  const router = useRouter();
  const { hasPermission } = usePermissions();

  const availableActions = QUICK_ACTIONS.filter(a => hasPermission(a.permission));

  return (
    <div className="fixed bottom-6 right-6 z-50">
      {isOpen && availableActions.map((action, i) => (
        <button
          key={action.id}
          onClick={() => router.push(action.href)}
          className="fab-action-item"
          style={{ animationDelay: `${i * 50}ms` }}
        >
          <span className="fab-label">{action.label}</span>
          <Icon name={action.icon} />
        </button>
      ))}
      <button
        className="fab-main"
        onClick={() => setIsOpen(!isOpen)}
        style={{ background: '#E8672A' }}
      >
        {isOpen ? '×' : '+'}
      </button>
    </div>
  );
}

5.9 Notification Center

The Notification Center is accessed by clicking the bell icon in the top navigation bar. It displays a badge count of unread notifications and opens a dropdown panel with categorised notification items. Notifications are delivered in real-time via Supabase / Oracle Realtime and also stored in PostgreSQL for persistence.

5.9.1 Notification Categories

Category Example Notification Priority Auto-dismiss
Shifts"You have been assigned to a new shift on 10 Apr, 9:00 AM"HighNo
Shifts"Shift #SH-0240 has been cancelled by the coordinator"HighNo
Compliance"Your First Aid certificate expires in 14 days"CriticalNo
Compliance"Incident #INC-021 requires your review"HighNo
Finance"Invoice #INV-0050 has been approved for payment"MediumAfter 7 days
Finance"NDIS claim batch #CB-012 submitted successfully"MediumAfter 7 days
System"Scheduled maintenance window: Sun 12 Apr, 2:00-4:00 AM"LowAfter event
System"New platform version 1.1.0 available"LowAfter 14 days

5.9.2 Notification Panel Wireframe

Notification Center - Dropdown Panel
Notifications Mark all as read
All (5) Shifts (2) Compliance (1) Finance (1) System (1)
New Shift Assigned 2m ago
You have been assigned to James Thompson, Mon 8 Apr 9:00 AM
Certification Expiry Warning 1h ago
Your First Aid certificate expires in 14 days. Please renew.
Invoice Approved 3h ago
Invoice #INV-0050 for AU$2,480 has been approved.
Shift Cancelled 5h ago
Shift #SH-0240 on Wed 10 Apr has been cancelled.
System Update 1d ago
Scheduled maintenance: Sun 12 Apr, 2:00-4:00 AM AEST.

5.9.3 Notification Database Schema

-- PostgreSQL: notifications table
CREATE TABLE notifications (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  org_id          UUID NOT NULL REFERENCES organisations(id),
  user_id         UUID NOT NULL REFERENCES users(id),
  category        VARCHAR(50) NOT NULL,  -- 'shifts', 'compliance', 'finance', 'system'
  title           VARCHAR(255) NOT NULL,
  body            TEXT,
  priority        VARCHAR(20) DEFAULT 'medium',  -- 'critical', 'high', 'medium', 'low'
  is_read         BOOLEAN DEFAULT FALSE,
  read_at         TIMESTAMPTZ,
  action_url      TEXT,
  auto_dismiss_at TIMESTAMPTZ,
  metadata        JSONB DEFAULT '{}',
  created_at      TIMESTAMPTZ DEFAULT NOW(),

  INDEX idx_notif_user_unread (user_id, is_read, created_at DESC),
  INDEX idx_notif_category (user_id, category, created_at DESC)
);

5.9.4 Mark as Read Behaviour

Individual notifications can be marked as read by clicking them (which also navigates to the relevant action URL) or by clicking the "x" dismiss button. The "Mark all as read" link at the top of the panel performs a bulk update. The read state is synchronised in real-time across all open browser tabs via a Supabase / Oracle Realtime subscription on the notifications table.

5.10 Role Switcher

The Role Switcher is an administrative tool that allows users with the super_admin role to temporarily adopt the perspective of any other role in the system. Unlike the "Preview Role" button on the dashboard (which only changes the dashboard view), the Role Switcher affects the entire application experience, including navigation, permissions, and data visibility.

5.10.1 Role Switcher Wireframe

Role Switcher - Preview Role Dropdown
Previewing as: Support Worker
Preview Role
Select Role to Preview

5.10.2 Role Switcher Constraints

Constraint Description
Access ControlOnly super_admin users can access the Role Switcher
Session DurationPreview sessions expire after 1 hour
Write ProtectionAll write operations are blocked during preview mode. Attempting to create, update, or delete records will show a "Preview Mode: Read Only" message.
Visual IndicatorAn orange banner is displayed at the top of every page during preview mode
Audit TrailAll preview sessions are logged in the admin_audit_log table with the admin user ID, target role, start time, and end time
Concurrent SessionsOnly one preview session can be active per admin user at a time

5.10.3 Implementation Details

// Role Switcher Middleware (Next.js)
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const previewRole = request.cookies.get('newdawnss_preview_role')?.value;
  const previewExpiry = request.cookies.get('newdawnss_preview_expiry')?.value;

  if (previewRole && previewExpiry) {
    // Check if preview session has expired
    if (new Date(previewExpiry) < new Date()) {
      const response = NextResponse.next();
      response.cookies.delete('newdawnss_preview_role');
      response.cookies.delete('newdawnss_preview_expiry');
      return response;
    }

    // Inject preview role into request headers for downstream use
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-preview-role', previewRole);
    requestHeaders.set('x-preview-mode', 'true');

    return NextResponse.next({
      request: { headers: requestHeaders },
    });
  }

  return NextResponse.next();
}
Important Security Note

The Role Switcher is a powerful administrative tool. All preview sessions are recorded in the audit log. Administrators should use this feature responsibly and only for legitimate purposes such as troubleshooting, training, or verifying role configurations. The preview session is strictly read-only to prevent accidental data modifications.

← Chapter 4: Authentication Chapter 6: Client Management →