A comprehensive technical reference covering the full-stack architecture, database schema, state management patterns, component library, and directory structure of the NDSS CRM platform built by Newdawn Support Services.
NDSS CRM follows a modern, layered full-stack architecture designed for performance, scalability, and maintainability. The platform is composed of four primary tiers: a Next.js frontend served via server-side rendering and static generation, a Supabase / Oracle backend-as-a-service layer providing authentication and real-time database access, Python microservices for data processing and analytics workloads, and PHP integration modules for legacy system compatibility with existing Newdawn Support Services infrastructure.
The architecture is designed to support the demanding requirements of NDIS disability care organisations, where data integrity, audit compliance, role-based security, and real-time collaboration are critical operational needs.
The following wireframe diagram illustrates the four-tier architecture of the NDSS CRM platform and the data flow between each layer.
| Tier | Technologies | Responsibilities |
|---|---|---|
| Client Layer | Next.js 16, React 19, TypeScript, TailwindCSS | Server-side rendering (SSR), static site generation (SSG), client-side routing, UI rendering, form validation, state management, and real-time UI updates via subscriptions. |
| BaaS Layer | Supabase / Oracle, PostgreSQL 15 | Authentication, authorization (RLS), database CRUD operations, real-time data subscriptions, file storage, and edge function execution for lightweight serverless logic. |
| Microservices | Python 3.12, PHP 8.3 | Data processing pipelines, NDIS claim generation, report compilation, PDF generation, legacy system integrations, payroll data synchronisation, and batch data transformations. |
| Data & Storage | PostgreSQL, Supabase / Oracle Storage, Redis | Persistent data storage, file/document storage, session caching, query result caching, and automated backup management. |
NDSS CRM uses multiple programming languages (TypeScript, Python, PHP) because each language excels in its domain. TypeScript powers the interactive frontend with type safety. Python handles data-intensive processing, analytics, and report generation with its rich ecosystem of libraries (Pandas, ReportLab). PHP provides robust integration with existing Newdawn Support Services legacy systems and third-party payroll providers that expose PHP-compatible APIs.
The NDSS CRM frontend is built on Next.js 16 using the App Router paradigm introduced in Next.js 13 and matured through subsequent releases. The App Router provides file-system-based routing, React Server Components (RSC), streaming server-side rendering, and fine-grained layout nesting - all of which are leveraged extensively throughout the platform.
The App Router organises routes as directories within the src/app/ folder. Each directory can contain a page.tsx file (the rendered page), a layout.tsx file (shared layout wrapper), a loading.tsx file (suspense fallback), and an error.tsx file (error boundary). This convention-based approach eliminates the need for manual route configuration.
| File Convention | Purpose | Example |
|---|---|---|
page.tsx |
Page Component | src/app/(dashboard)/clients/page.tsx renders at /clients |
layout.tsx |
Shared Layout | src/app/(dashboard)/layout.tsx wraps all dashboard pages with sidebar and topbar |
loading.tsx |
Suspense Fallback | Displays skeleton loaders while server components stream data |
error.tsx |
Error Boundary | Catches and displays runtime errors with recovery options |
not-found.tsx |
404 Handler | Custom "Page Not Found" display per route segment |
template.tsx |
Re-rendered Layout | Similar to layout but creates a new instance on navigation (used for animations) |
NDSS CRM leverages several React 19 features throughout the codebase:
The frontend component tree follows a strict hierarchy with clearly separated concerns:
NDSS CRM uses Next.js route groups (parenthesised folders) to organise routes without affecting the URL path. The three primary route groups are:
| Route Group | Layout | Contains |
|---|---|---|
(auth) |
Minimal layout (no sidebar) | Login, Register, Forgot Password, Reset Password, Verify Email |
(dashboard) |
Full layout (sidebar + topbar) | All authenticated platform pages: Dashboard, Clients, Staff, Rostering, Finance, Compliance, Intake, Clinical, Learning, Messaging, Reports, Admin, SIL |
(client-portal) |
Portal layout (simplified sidebar) | Client-facing portal: My Services, My Plan, Messages, Documents, Profile |
(public) |
Public layout (no auth required) | Landing page, Terms of Service, Privacy Policy, Demo Request |
The backend of NDSS CRM is built on a hybrid architecture combining Supabase / Oracle (PostgreSQL-based Backend-as-a-Service), Python microservices for data-intensive operations, and PHP integration modules for legacy system compatibility. This approach provides the rapid development benefits of a BaaS while retaining the flexibility to implement complex business logic in purpose-built services.
Supabase / Oracle serves as the primary backend layer and provides the following capabilities:
Python 3.12 microservices handle data processing workloads that require computational libraries not available in the JavaScript/TypeScript ecosystem:
| Service | Key Libraries | Responsibilities |
|---|---|---|
ndss-report-engine |
Pandas, ReportLab, Matplotlib | Generates PDF and Excel reports for financial summaries, compliance audits, client progress reviews, and organisational KPI dashboards. |
ndss-claim-processor |
Pandas, psycopg2, httpx | Processes NDIS claims by validating service records against the NDIS Price Guide, calculating line item amounts, and preparing bulk claim submissions. |
ndss-data-pipeline |
Pandas, SQLAlchemy, Celery | Runs scheduled data transformations including shift aggregation, budget utilisation calculations, staff compliance status updates, and historical data archival. |
ndss-notification-worker |
httpx, Jinja2, smtplib | Dispatches email and SMS notifications based on system events: shift reminders, qualification expiry warnings, incident escalations, and billing alerts. |
PHP 8.3 modules provide compatibility bridges with legacy systems and third-party services that use PHP-based APIs:
| Module | Key Libraries | Responsibilities |
|---|---|---|
ndss-payroll-bridge |
Guzzle, Carbon, League\Csv | Synchronises timesheet data with external payroll systems (MYOB, Xero, KeyPay) that provide PHP SDK clients. Handles award interpretation, leave accrual calculations, and superannuation reporting. |
ndss-legacy-api |
Laravel Components, Doctrine DBAL | Provides a REST API bridge to legacy Newdawn databases that store historical client records, pre-migration financial data, and archived compliance documents. |
ndss-doc-converter |
PhpSpreadsheet, TCPDF, PHPWord | Converts documents between formats (DOCX to PDF, CSV to XLSX), processes bulk document uploads, and generates templated letters, service agreements, and compliance certificates. |
Python and PHP microservices communicate with the main Supabase / Oracle database using direct PostgreSQL connections (via connection pooling with PgBouncer). They also expose REST endpoints that the Next.js frontend can call via internal API routes. All inter-service communication uses HTTPS with bearer token authentication.
The NDSS CRM database is a relational PostgreSQL schema containing over 40 tables. This section documents the 10 primary entities that form the core of the data model. Every table uses UUID primary keys, includes created_at and updated_at timestamp columns, and is protected by Row Level Security policies.
The profiles table extends the Supabase / Oracle auth.users table with application-specific user data. Every authenticated user has exactly one profile record.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | References auth.users.id |
email | TEXT | No | User email address (unique) |
full_name | TEXT | No | Full display name |
role | TEXT | No | One of 24 role identifiers (e.g., master_admin, support_worker) |
avatar_url | TEXT | Yes | URL to profile photo in Supabase / Oracle Storage |
phone | TEXT | Yes | Contact phone number |
is_active | BOOLEAN | No | Whether the account is active (default: true) |
last_login_at | TIMESTAMPTZ | Yes | Timestamp of most recent login |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The clients table stores all NDIS participant records, including personal details, NDIS identifiers, and service status information.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
ndis_number | TEXT | No | NDIS participant number (unique, 9 digits) |
first_name | TEXT | No | Participant first name |
last_name | TEXT | No | Participant last name |
date_of_birth | DATE | No | Date of birth |
gender | TEXT | Yes | Gender identity |
email | TEXT | Yes | Email address |
phone | TEXT | Yes | Contact phone number |
address | JSONB | Yes | Structured address object (street, suburb, state, postcode) |
status | TEXT | No | One of: active, inactive, waitlist, exited |
primary_disability | TEXT | Yes | Primary disability type |
emergency_contact | JSONB | Yes | Emergency contact details (name, phone, relationship) |
assigned_coordinator_id | UUID (FK) | Yes | References profiles.id for the assigned service coordinator |
plan_start_date | DATE | Yes | Current NDIS plan start date |
plan_end_date | DATE | Yes | Current NDIS plan end date |
notes | TEXT | Yes | General notes about the participant |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The goals table tracks NDIS participant goals as defined in their NDIS plan and service agreements.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
client_id | UUID (FK) | No | References clients.id |
title | TEXT | No | Short goal title |
description | TEXT | Yes | Detailed goal description |
category | TEXT | No | Goal category (e.g., daily_living, social_participation, employment) |
status | TEXT | No | One of: not_started, in_progress, achieved, discontinued |
target_date | DATE | Yes | Target completion date |
progress_percentage | INTEGER | No | Current progress (0-100) |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The progress_notes table stores shift-level notes recorded by support workers after each service delivery session.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
client_id | UUID (FK) | No | References clients.id |
shift_id | UUID (FK) | Yes | References shifts.id (optional if not linked to a specific shift) |
author_id | UUID (FK) | No | References profiles.id for the note author |
note_date | DATE | No | Date of the service/note |
content | TEXT | No | Progress note content (rich text) |
goals_addressed | UUID[] | Yes | Array of goal IDs addressed during this session |
mood_rating | INTEGER | Yes | Client mood rating (1-5 scale) |
is_signed | BOOLEAN | No | Whether the note has been signed/approved |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The client_funding table tracks NDIS plan funding allocations and budget utilisation for each participant.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
client_id | UUID (FK) | No | References clients.id |
support_category | TEXT | No | NDIS support category code (e.g., core_daily_activities) |
total_budget | NUMERIC(12,2) | No | Total allocated budget amount in AUD |
used_budget | NUMERIC(12,2) | No | Amount of budget consumed to date |
plan_start_date | DATE | No | Funding period start date |
plan_end_date | DATE | No | Funding period end date |
management_type | TEXT | No | One of: ndia_managed, plan_managed, self_managed |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The staff_profiles table extends the profiles table with employment-specific fields for all staff members.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
profile_id | UUID (FK) | No | References profiles.id |
employee_id | TEXT | No | Internal employee identifier (e.g., NDS-001) |
department | TEXT | Yes | Department assignment |
position_title | TEXT | No | Job title |
employment_type | TEXT | No | One of: full_time, part_time, casual, contractor |
start_date | DATE | No | Employment start date |
ndis_worker_screening | JSONB | Yes | Screening status object (number, expiry_date, status) |
working_with_children_check | JSONB | Yes | WWC check object (number, expiry_date, status) |
hourly_rate | NUMERIC(8,2) | Yes | Base hourly pay rate |
availability | JSONB | Yes | Weekly availability schedule (day-by-day time ranges) |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The qualifications table records certifications, qualifications, and training completions for staff members.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
staff_profile_id | UUID (FK) | No | References staff_profiles.id |
name | TEXT | No | Qualification or certification name |
issuing_body | TEXT | Yes | Organisation that issued the qualification |
issue_date | DATE | No | Date the qualification was issued |
expiry_date | DATE | Yes | Expiration date (null if no expiry) |
document_url | TEXT | Yes | URL to the uploaded certificate document |
status | TEXT | No | One of: current, expired, pending_renewal |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The shifts table is the core scheduling entity, representing individual service delivery appointments between staff and clients.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
client_id | UUID (FK) | No | References clients.id |
staff_id | UUID (FK) | Yes | References staff_profiles.id (null if unallocated) |
shift_date | DATE | No | Date of the shift |
start_time | TIME | No | Shift start time |
end_time | TIME | No | Shift end time |
service_type | TEXT | No | NDIS support item category |
status | TEXT | No | One of: scheduled, confirmed, in_progress, completed, cancelled, no_show |
location | TEXT | Yes | Service delivery location |
notes | TEXT | Yes | Shift-specific notes |
clock_in_time | TIMESTAMPTZ | Yes | Actual clock-in timestamp |
clock_out_time | TIMESTAMPTZ | Yes | Actual clock-out timestamp |
is_recurring | BOOLEAN | No | Whether this shift is part of a recurring series |
recurrence_id | UUID (FK) | Yes | References parent recurrence record |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The invoices table tracks all financial documents generated for NDIS claims and client billing.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
invoice_number | TEXT | No | Auto-generated invoice number (e.g., INV-2024-0001) |
client_id | UUID (FK) | No | References clients.id |
billing_period_start | DATE | No | Start of billing period |
billing_period_end | DATE | No | End of billing period |
total_amount | NUMERIC(12,2) | No | Total invoice amount in AUD |
gst_amount | NUMERIC(12,2) | No | GST component (typically $0 for NDIS services) |
status | TEXT | No | One of: draft, submitted, paid, overdue, cancelled |
line_items | JSONB | No | Array of line item objects (service_type, quantity, unit_price, total) |
submitted_at | TIMESTAMPTZ | Yes | Timestamp when invoice was submitted for payment |
paid_at | TIMESTAMPTZ | Yes | Timestamp when payment was received |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The incidents table records all safety incidents, near-misses, and complaints as required by NDIS Practice Standards.
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID (PK) | No | Primary key |
incident_number | TEXT | No | Auto-generated reference (e.g., INC-2024-0042) |
client_id | UUID (FK) | Yes | References clients.id (null if not client-related) |
reported_by | UUID (FK) | No | References profiles.id for the reporter |
incident_date | TIMESTAMPTZ | No | Date and time the incident occurred |
category | TEXT | No | Incident category (e.g., injury, medication_error, behaviour, property_damage) |
severity | TEXT | No | One of: low, medium, high, critical |
description | TEXT | No | Detailed description of the incident |
immediate_actions | TEXT | Yes | Actions taken immediately following the incident |
status | TEXT | No | One of: open, investigating, resolved, closed |
is_reportable | BOOLEAN | No | Whether this incident must be reported to the NDIS Commission |
attachments | TEXT[] | Yes | Array of file URLs for supporting documents/photos |
created_at | TIMESTAMPTZ | No | Record creation timestamp |
updated_at | TIMESTAMPTZ | No | Last modification timestamp |
The following diagram summarises the primary relationships between core database entities:
id, email, roleprofile_id (FK)staff_profile_id (FK)id, ndis_numberclient_id (FK)client_id (FK)client_id, staff_id (FK)client_id, shift_id (FK)client_id (FK)client_id, reported_by (FK)NDSS CRM uses Supabase / Oracle Auth for all authentication operations. The authentication flow is protected by Next.js middleware that intercepts every request and verifies the user session before allowing access to protected routes. For a complete guide to authentication features and user management, refer to Chapter 4.
The following describes the step-by-step authentication flow when a user accesses NDSS CRM:
src/middleware.ts) intercepts all incoming HTTP requests.supabase.auth.getSession() to check for a valid session cookie./login.auth.users table and returns a JWT access token and refresh token.profiles table to determine the user role./dashboard for admins, /portal for clients).The middleware configuration defines which routes are public (accessible without authentication) and which are protected:
// src/middleware.ts - Route Protection Configuration
const publicRoutes = [
'/login',
'/register',
'/forgot-password',
'/reset-password',
'/verify-email',
'/terms',
'/privacy',
'/demo'
];
const protectedRouteGroups = [
'/(dashboard)', // All internal platform pages
'/(client-portal)' // Client self-service portal
];
// Middleware function
export async function middleware(request: NextRequest) {
const session = await getSession(request);
if (!session && isProtectedRoute(request.pathname)) {
return NextResponse.redirect(new URL('/login', request.url));
}
if (session && isPublicOnlyRoute(request.pathname)) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
Sessions are managed using Supabase / Oracle's cookie-based approach with the @supabase/ssr library:
| Parameter | Value | Description |
|---|---|---|
| Session Duration | 1 hour (access token) | Access tokens expire after 1 hour and are automatically refreshed |
| Refresh Token Lifetime | 7 days | Refresh tokens allow silent token renewal for up to 7 days |
| Cookie Type | HTTP-only, Secure, SameSite=Lax | Prevents JavaScript access to session tokens and mitigates CSRF |
| Auto Refresh | Enabled | The Supabase / Oracle client automatically refreshes tokens before expiry |
| Idle Timeout | 30 minutes | Users are logged out after 30 minutes of inactivity |
NDSS CRM employs a deliberate separation between server state (data from the database) and client state (UI state, form state, user preferences). This separation prevents the common pitfalls of mixing remote data with local UI state and ensures predictable data flow throughout the application.
React Query manages all data fetching, caching, synchronisation, and background updates for data originating from the Supabase / Oracle database or microservice APIs:
// Example: Fetching client list with React Query
const { data: clients, isLoading, error } = useQuery({
queryKey: ['clients', { status: 'active', page: 1 }],
queryFn: () => supabase
.from('clients')
.select('*')
.eq('status', 'active')
.range(0, 24)
.order('last_name', { ascending: true }),
staleTime: 5 * 60 * 1000, // 5 minutes
});
Zustand manages lightweight client-side state that does not originate from the database:
// Example: Zustand store for sidebar state
import { create } from 'zustand';
interface SidebarStore {
isCollapsed: boolean;
isMobileOpen: boolean;
toggleCollapse: () => void;
setMobileOpen: (open: boolean) => void;
}
export const useSidebarStore = create<SidebarStore>((set) => ({
isCollapsed: false,
isMobileOpen: false,
toggleCollapse: () => set((s) => ({ isCollapsed: !s.isCollapsed })),
setMobileOpen: (open) => set({ isMobileOpen: open }),
}));
All forms across the platform use React Hook Form with Zod schema validation:
// Example: Client creation form with Zod validation
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const clientSchema = z.object({
ndis_number: z.string().regex(/^\d{9}$/, 'Must be 9 digits'),
first_name: z.string().min(1, 'Required'),
last_name: z.string().min(1, 'Required'),
date_of_birth: z.string().min(1, 'Required'),
status: z.enum(['active', 'inactive', 'waitlist']),
email: z.string().email().optional().or(z.literal('')),
phone: z.string().optional(),
});
type ClientFormData = z.infer<typeof clientSchema>;
const { register, handleSubmit, formState: { errors } } = useForm<ClientFormData>({
resolver: zodResolver(clientSchema),
});
The NDSS CRM API layer consists of three complementary interfaces: Supabase / Oracle Client SDK for direct database operations, Next.js API Routes for custom server-side logic, and Supabase / Oracle Realtime for live data subscriptions.
Custom API routes are defined in src/app/api/ and handle operations that require server-side processing beyond simple CRUD:
| Method | Endpoint | Description |
|---|---|---|
POST | /api/claims/generate | Triggers the Python claim processor to generate NDIS claims for a billing period |
POST | /api/reports/build | Triggers the Python report engine to compile a specified report |
POST | /api/payroll/sync | Triggers the PHP payroll bridge to synchronise timesheet data |
GET | /api/dashboard/stats | Returns aggregated dashboard statistics for the authenticated user role |
POST | /api/shifts/bulk-create | Creates multiple shifts at once (used for recurring shift generation) |
POST | /api/documents/convert | Triggers the PHP document converter for format transformations |
GET | /api/ndis/price-guide | Returns NDIS price guide data for a given support category and region |
POST | /api/notifications/send | Dispatches email/SMS notifications via the Python notification worker |
The majority of CRUD operations are performed directly via the Supabase / Oracle JavaScript client, which translates method calls into PostgREST API requests:
// Direct Supabase / Oracle client operations
// SELECT
const { data } = await supabase.from('clients').select('*').eq('status', 'active');
// INSERT
const { data } = await supabase.from('clients').insert({ ... }).select().single();
// UPDATE
const { data } = await supabase.from('clients').update({ status: 'inactive' }).eq('id', clientId);
// DELETE
const { error } = await supabase.from('incidents').delete().eq('id', incidentId);
Real-time subscriptions are used for features that require live data updates without page refresh:
| Subscription | Table | Use Case |
|---|---|---|
| Shift Updates | shifts | Rostering page updates in real-time when shifts are created, assigned, or status-changed by other users |
| New Messages | messages | Messaging module receives new messages instantly without polling |
| Incident Alerts | incidents | Compliance officers receive immediate alerts when critical incidents are reported |
| Dashboard Counters | Multiple tables | Dashboard stat cards update in real-time as data changes across the system |
NDSS CRM uses a custom component library built on top of Radix UI unstyled primitives, styled with TailwindCSS utility classes. This approach provides accessible, keyboard-navigable UI components with full design control.
The following Radix UI primitives are used across the platform:
| Primitive | Package | Usage in NDSS CRM |
|---|---|---|
| Dialog | @radix-ui/react-dialog | Modal dialogs for forms (create client, create shift, edit profile), confirmation prompts, and detail views |
| DropdownMenu | @radix-ui/react-dropdown-menu | Action menus on table rows, user profile dropdown, bulk action menus |
| Select | @radix-ui/react-select | All dropdown selects (role selector, status filter, service type picker) |
| Tabs | @radix-ui/react-tabs | Tab navigation on detail pages (client profile tabs, staff profile tabs) |
| Toast | @radix-ui/react-toast | Success/error/info toast notifications |
| Tooltip | @radix-ui/react-tooltip | Contextual tooltips on icons, badges, and truncated text |
| Popover | @radix-ui/react-popover | Date pickers, filter panels, quick-view popovers |
| Switch | @radix-ui/react-switch | Toggle switches for boolean settings (active/inactive, enable/disable) |
| Checkbox | @radix-ui/react-checkbox | Multi-select checkboxes in tables, form checkboxes |
| Avatar | @radix-ui/react-avatar | User avatars with initials fallback throughout the platform |
The src/components/ directory contains custom components built on top of Radix primitives:
| Component | File Path | Description |
|---|---|---|
DataTable | components/ui/data-table.tsx | Reusable sortable, filterable, paginated table with column visibility controls |
StatCard | components/ui/stat-card.tsx | Dashboard metric card with icon, value, label, and trend indicator |
FilterBar | components/ui/filter-bar.tsx | Search input with filter chip selectors for data tables |
FormField | components/ui/form-field.tsx | Wrapper for React Hook Form fields with label, input, and error display |
StatusBadge | components/ui/status-badge.tsx | Coloured badge displaying entity status (active, inactive, pending, etc.) |
Sidebar | components/layout/sidebar.tsx | Collapsible navigation sidebar with role-based menu items |
Topbar | components/layout/topbar.tsx | Top navigation bar with breadcrumb, search, notifications, and user menu |
EmptyState | components/ui/empty-state.tsx | Placeholder displayed when a list or table has no records |
ConfirmDialog | components/ui/confirm-dialog.tsx | Reusable confirmation modal for destructive actions |
FileUploader | components/ui/file-uploader.tsx | Drag-and-drop file upload component with progress indicator |
TailwindCSS is configured with a custom design system that includes NDSS CRM brand colours, spacing scale, and responsive breakpoints:
// tailwind.config.ts - Key customisations
export default {
theme: {
extend: {
colors: {
brand: {
50: '#f0f7ff',
100: '#e0effe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
900: '#1e3a5f',
},
success: '#22c55e',
warning: '#f59e0b',
danger: '#ef4444',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
};
The following is the complete project directory tree for the NDSS CRM codebase, showing all major directories and their purposes:
ndss-crm/
+-- src/
| +-- app/
| | +-- (auth)/
| | | +-- login/page.tsx
| | | +-- register/page.tsx
| | | +-- forgot-password/page.tsx
| | | +-- reset-password/page.tsx
| | | +-- verify-email/page.tsx
| | | +-- layout.tsx
| | +-- (dashboard)/
| | | +-- dashboard/page.tsx
| | | +-- clients/
| | | | +-- page.tsx
| | | | +-- [id]/page.tsx
| | | | +-- new/page.tsx
| | | +-- staff/
| | | | +-- page.tsx
| | | | +-- [id]/page.tsx
| | | +-- rostering/
| | | | +-- page.tsx
| | | | +-- calendar/page.tsx
| | | +-- finance/
| | | | +-- page.tsx
| | | | +-- invoices/page.tsx
| | | +-- compliance/
| | | | +-- page.tsx
| | | | +-- incidents/page.tsx
| | | +-- intake/page.tsx
| | | +-- clinical/page.tsx
| | | +-- learning/page.tsx
| | | +-- messaging/page.tsx
| | | +-- reports/page.tsx
| | | +-- admin/
| | | | +-- page.tsx
| | | | +-- settings/page.tsx
| | | | +-- users/page.tsx
| | | +-- sil/page.tsx
| | | +-- layout.tsx
| | +-- (client-portal)/
| | | +-- portal/page.tsx
| | | +-- my-services/page.tsx
| | | +-- my-plan/page.tsx
| | | +-- layout.tsx
| | +-- (public)/
| | | +-- page.tsx
| | | +-- terms/page.tsx
| | | +-- privacy/page.tsx
| | +-- api/
| | | +-- claims/route.ts
| | | +-- reports/route.ts
| | | +-- payroll/route.ts
| | | +-- dashboard/route.ts
| | | +-- shifts/route.ts
| | | +-- documents/route.ts
| | | +-- notifications/route.ts
| | +-- globals.css
| | +-- layout.tsx
| +-- components/
| | +-- layout/
| | | +-- sidebar.tsx
| | | +-- topbar.tsx
| | | +-- breadcrumb.tsx
| | | +-- footer.tsx
| | +-- ui/
| | | +-- button.tsx
| | | +-- input.tsx
| | | +-- select.tsx
| | | +-- data-table.tsx
| | | +-- stat-card.tsx
| | | +-- filter-bar.tsx
| | | +-- status-badge.tsx
| | | +-- form-field.tsx
| | | +-- confirm-dialog.tsx
| | | +-- file-uploader.tsx
| | | +-- empty-state.tsx
| | | +-- toast.tsx
| | +-- clients/
| | | +-- client-form.tsx
| | | +-- client-table.tsx
| | | +-- client-profile.tsx
| | +-- staff/
| | | +-- staff-form.tsx
| | | +-- staff-table.tsx
| | +-- rostering/
| | | +-- shift-form.tsx
| | | +-- shift-calendar.tsx
| | | +-- shift-table.tsx
| | +-- finance/
| | | +-- invoice-form.tsx
| | | +-- invoice-table.tsx
| +-- lib/
| | +-- supabase/
| | | +-- client.ts
| | | +-- server.ts
| | | +-- middleware.ts
| | +-- utils.ts
| | +-- constants.ts
| | +-- roles.ts
| +-- providers/
| | +-- query-provider.tsx
| | +-- theme-provider.tsx
| | +-- auth-provider.tsx
| +-- types/
| | +-- database.ts
| | +-- roles.ts
| | +-- forms.ts
| +-- middleware.ts
+-- supabase/
| +-- migrations/
| | +-- 001_create_profiles.sql
| | +-- 002_create_clients.sql
| | +-- 003_create_goals.sql
| | +-- 004_create_shifts.sql
| | +-- 005_create_invoices.sql
| | +-- 006_create_incidents.sql
| +-- seed.sql
| +-- config.toml
+-- services/
| +-- python/
| | +-- report-engine/
| | +-- claim-processor/
| | +-- data-pipeline/
| | +-- notification-worker/
| +-- php/
| +-- payroll-bridge/
| +-- legacy-api/
| +-- doc-converter/
+-- public/
| +-- ndss-crm-logo.svg
| +-- favicon.ico
+-- package.json
+-- tsconfig.json
+-- tailwind.config.ts
+-- next.config.ts
+-- .env.local
NDSS CRM employs several design patterns consistently across the codebase to ensure maintainability, reusability, and developer productivity.
Components are designed to be composable rather than monolithic. Instead of building a single large "ClientPage" component, the page is composed of smaller, focused components that can be reused independently:
// Composable page structure example
export default function ClientsPage() {
return (
<PageShell title="Clients" subtitle="Manage NDIS participants">
<PageHeader>
<FilterBar
searchPlaceholder="Search clients..."
filters={[
{ key: 'status', label: 'Status', options: statusOptions },
{ key: 'coordinator', label: 'Coordinator', options: staffOptions },
]}
/>
<Button onClick={openCreateModal}>+ New Client</Button>
</PageHeader>
<DataTable
columns={clientColumns}
data={clients}
pagination={pagination}
onRowClick={navigateToClient}
/>
<CreateClientDialog open={isCreateOpen} onClose={closeCreateModal} />
</PageShell>
);
}
A custom <RoleGate> component conditionally renders UI elements based on the current user role. This pattern is used throughout the platform to show or hide features, navigation items, action buttons, and data columns:
// Role-based rendering pattern
import { RoleGate } from '@/components/auth/role-gate';
// Only visible to admin roles
<RoleGate allowedRoles={['master_admin', 'administrator']}>
<Button variant="danger" onClick={deleteClient}>
Delete Client
</Button>
</RoleGate>
// Visible to coordinators and above
<RoleGate allowedRoles={['master_admin', 'administrator', 'service_coordinator']}>
<Tabs.Tab value="funding">Funding</Tabs.Tab>
</RoleGate>
// Visible to everyone except clients
<RoleGate excludeRoles={['client', 'family_member']}>
<DataTable.Column header="Internal Notes" />
</RoleGate>
Data fetching follows a "server-first" pattern where React Server Components fetch data on the server whenever possible, reducing client-side JavaScript and eliminating loading spinners for initial page loads:
// Server Component - data fetched on server, no client JS needed
export default async function ClientDetailPage({ params }: { params: { id: string } }) {
const supabase = createServerClient();
const { data: client } = await supabase
.from('clients')
.select('*, goals(*), client_funding(*)')
.eq('id', params.id)
.single();
return <ClientProfile client={client} />;
}
Every route segment includes an error.tsx file that catches runtime errors and provides a user-friendly error recovery experience:
// src/app/(dashboard)/clients/error.tsx
'use client';
export default function ClientsError({ error, reset }: {
error: Error;
reset: () => void;
}) {
return (
<ErrorState
title="Failed to load clients"
message={error.message}
onRetry={reset}
/>
);
}
TypeScript is enforced across the entire codebase with strict mode enabled. Database types are auto-generated from the Supabase / Oracle schema using supabase gen types typescript, ensuring that all database queries and responses are type-checked at compile time:
// Auto-generated database types
export type Database = {
public: {
Tables: {
clients: {
Row: {
id: string;
ndis_number: string;
first_name: string;
last_name: string;
date_of_birth: string;
status: 'active' | 'inactive' | 'waitlist' | 'exited';
// ... all other fields
};
Insert: { /* ... */ };
Update: { /* ... */ };
};
// ... all other tables
};
};
};
For API endpoint details, see Chapter 19: API & Integration. For security policies and data protection measures, see Chapter 20: Security & Data. For troubleshooting build and deployment issues, see Chapter 21: Troubleshooting & FAQ.