Developer Handbook
Practical guide for developers working with the SPA — adding modules, routes, sidebar items, debugging, common pitfalls, and testing.
Developer Handbook
A practical, step-by-step guide for developers working with the CrelioDashboard SPA. Use this as a quick reference when adding features, debugging issues, or onboarding.
Adding a New Module to the SPA
Step 1: Create the Routes and Menu File
Create a new file in RoutesAndMenus/:
src/modules/CrelioDashboard/RoutesAndMenus/myModuleRoutesAndMenu.tsxExport two hooks — one for routes and one for sidebar:
import { CrelioRouteType, CrelioSidebarItem } from "../routeTypes";
import { useTranslation } from "react-i18next";
import React from "react";
// Lazy-load your components
const MyDashboard = React.lazy(() => import("src/components/MyModule/Dashboard"));
const MySettings = React.lazy(() => import("src/components/MyModule/Settings"));
export const useMyModuleRoutes = (): CrelioRouteType[] => {
return [
{ path: "/my-module", Component: MyDashboard },
{ path: "/my-module/settings", Component: MySettings },
];
};
export const useMyModuleSidebarMenu = (): CrelioSidebarItem[] => {
const { t } = useTranslation();
return [
{ name: t("Dashboard"), icon: "fa-chart-line", route: "/my-module" },
{ name: t("Settings"), icon: "fa-cog", route: "/my-module/settings" },
];
};Step 2: Register in routes.tsx
Open RoutesAndMenus/routes.tsx and:
- Import your hooks:
import { useMyModuleRoutes, useMyModuleSidebarMenu } from "./myModuleRoutesAndMenu";- Call the hooks inside the
Routescomponent:
const myModuleRoutes = useMyModuleRoutes();
const myModuleSidebarMenu = useMyModuleSidebarMenu();- Add to the
modulesarray:
const modules: Module[] = useMemo(() => [
// ... existing modules
{ menu: myModuleSidebarMenu, routes: myModuleRoutes, selectedModule: t("MyModule") },
], [/* dependencies */]);Step 3: Register in navigate.ts
Add your module's URL prefix to the moduleNames array:
export const moduleNames: string[] = [
"registration", "accession", "operation",
"finance", "crm", "admin", "inventory",
"my-module", // ← add here
];This ensures the navigate() utility correctly prefixes intra-module URLs.
Step 4: Add Backend URL Mapping
In livehealth_4/decorators/utils.py, add the legacy-to-SPA URL mapping:
SPA_URL_MAPPER = {
# ... existing mappings
"/my-module-legacy/": "/crelio-dashboard/#/my-module",
}Step 5: Update Session Helpers
In logins/utils/session_helpers.py, add the SPA login URL for your module's user role:
# For my-module staff
login_url = "/crelio-dashboard/#/my-module" if is_spa_enabled(user.labId) else "/my-module-legacy/"Checklist
- Created
RoutesAndMenus/myModuleRoutesAndMenu.tsxwith routes + sidebar hooks - Registered hooks in
routes.tsxmodulesarray - Added module name to
moduleNamesinnavigate.ts - Added URL mapping in
SPA_URL_MAPPER(backend) - Updated session helpers for login redirect (backend)
- All route paths start with
/my-module/prefix - Components are lazy-loaded with
React.lazy()
Adding Routes to an Existing Module
Adding a Simple Route
Add a new entry to your module's useXxxRoutes() hook:
export const useOperationsRoutes = (): CrelioRouteType[] => {
return [
// ... existing routes
{ path: "/operation/my-new-page", Component: MyNewPage },
];
};Adding a Route with Permission Check
Use routeProps to restrict access:
{
path: "/operation/admin-only-page",
Component: AdminOnlyPage,
routeProps: {
requiredPermissions: ["canAccessAdminPage"],
},
}The AuthRoute component checks routeProps.requiredPermissions against the user's session permissions before rendering.
Adding a Redirect
Use the redirect route type:
{
from: "/operation/old-path",
to: "/operation/new-path",
isRedirect: true,
}Adding Sidebar Items
Simple Menu Item
{ name: t("My Page"), icon: "fa-file", route: "/operation/my-page" }Menu Item with Submenu
{
name: t("Reports"),
icon: "fa-chart-bar",
submenuArr: [
{ name: t("Daily Report"), route: "/operation/reports/daily" },
{ name: t("Monthly Report"), route: "/operation/reports/monthly" },
],
}Conditional Menu Item (Permission-Based)
{
name: t("Admin Settings"),
icon: "fa-cog",
route: "/operation/admin-settings",
hasPermission: () => sessionData?.isAdmin === true,
}Hidden Menu Item
Use hidden: true for items that need a route but shouldn't appear in the sidebar:
{
name: t("Hidden Page"),
route: "/operation/hidden-page",
hidden: true,
}Navigation Patterns
Within the Same Module
Use navigate() with relative-style paths — the utility auto-prefixes:
import { navigate } from "src/utils/navigate";
// From within Operations module
navigate("/testwise-waiting-list/all-tests");
// → resolves to /operation/testwise-waiting-list/all-testsCross-Module Navigation
Explicitly include the target module name:
navigate("/finance/dashboard");
// → no prefix added, navigates directly to FinanceWith Route State
navigate({
pathname: "/patient-details",
state: { patientId: 123, fromModule: "registration" },
});Replace History (No Back Button)
navigate("/settings", { replace: true });Local Development
Starting the SPA Dev Server
cd apps/livehealth-frontend
yarn start local.crelioDashboardThis starts the SPA on port 3000 with hot-reload.
Starting a Single Module (Non-SPA)
yarn start local.Operations # or Registration, Finance, etc.Full Stack Setup
# Terminal 1: SPA frontend (port 3000)
cd apps/livehealth-frontend
yarn start local.crelioDashboard
# Terminal 2: Py2 backend (port 8000)
cd livehealthapp
python manage.py runserver 0.0.0.0:8000
# Terminal 3: Py3 backend (port 9000)
cd crelioapp
python manage.py runserver 0.0.0.0:9000Enabling SPA Locally
Set the Redis flag for your local lab:
redis-cli SET is_spa_enabled_YOUR_LAB_ID TrueOr via Django shell:
from django.core.cache import cache
cache.set("is_spa_enabled_YOUR_LAB_ID", True)Debugging
Module Not Resolving
Symptom: Navigating to your module shows Registration (the fallback) instead of your module.
Checklist:
- Is the module name in the
modulesarray inroutes.tsx? - Does the sidebar route's first path segment match your URL? (e.g.,
/my-module/dashboardshould match a sidebar item starting with/my-module/) - Is the
selectedModulevalue in the modules array matching the expected name?
Route Not Rendering
Symptom: URL changes but the page shows PageNotFound.
Checklist:
- Does the route
pathexactly match the URL? Routes useexact={true}. - Is the route in the correct module's
useXxxRoutes()hook? - Is the component exported correctly and lazy-loaded properly?
Navigate Not Prefixing Module Name
Symptom: navigate("/dashboard") goes to /dashboard instead of /operation/dashboard.
Checklist:
- Is the current URL within a recognized module? Check
moduleNamesinnavigate.ts. - Is
isLabLogin()returningtrue? - Is the target path accidentally matching a module name? (e.g.,
/adminwon't be prefixed because it IS a module name)
SPA Not Loading
Symptom: Hitting /crelio-dashboard/ redirects to / (login page).
Checklist:
- Is
is_spa_enabled_{lab_id}set toTruein Redis? - Is the user authenticated (valid
labIdin session)? - Is
crelio-dashboard.htmldeployed tobuild_assets/CrelioDashboard/build/? - Try logging in as support user — they bypass the flag check.
Common Pitfalls
❌ Using window.location.href for Navigation
// DON'T — this triggers a full page reload and destroys SPA state
window.location.href = "/operation/dashboard";
// DO — use the navigate utility for in-app navigation
navigate("/operation/dashboard");❌ Forgetting to Prefix Routes with Module Name
// DON'T — this will conflict with other modules using "/dashboard"
{ path: "/dashboard", Component: MyDashboard }
// DO — prefix all routes with your module name
{ path: "/my-module/dashboard", Component: MyDashboard }❌ Module-Specific State in Global Redux
// DON'T — all modules share the same Redux store in SPA mode
// Avoid generic keys that other modules might also use
dispatch({ type: "SET_DASHBOARD_DATA", payload: data });
// DO — namespace your actions with the module name
dispatch({ type: "OPERATIONS/SET_DASHBOARD_DATA", payload: data });❌ Initializing Settings in Component useEffect
// DON'T — this re-fetches on every module switch
useEffect(() => {
fetchSettings();
}, []);
// DO — initialize in the SPA bootstrap (routes.tsx) so it runs once
// Or check if data already exists before fetching
useEffect(() => {
if (!settings) fetchSettings();
}, []);❌ Using history.push Directly
// DON'T — bypasses module-aware URL prefixing
import history from "src/utils/history";
history.push("/dashboard");
// DO — use navigate which handles module prefixing
import { navigate } from "src/utils/navigate";
navigate("/dashboard");Testing
Testing SPA Mode vs v5 Mode
To compare behavior between SPA and v5 single-build:
| Test | SPA Mode | v5 Single-Build |
|---|---|---|
| Start server | yarn start local.crelioDashboard | yarn start local.Operations |
| URL pattern | /crelio-dashboard/#/operation/... | /waitingList/... or /#/... |
| Redis flag | is_spa_enabled_{lab_id} = True | Flag absent or False |
| Module switch | Instant (no reload) | Full page reload |
Quick Smoke Test
After making changes:
- Start the SPA dev server
- Log in and verify you land on
/crelio-dashboard/#/{module} - Switch between at least 2 modules via the sidebar — verify no page reload
- Use browser back/forward buttons — verify navigation works
- Use
Cmd+Kspotlight search — verify cross-module results - Hard-refresh the page — verify the SPA reloads correctly at the same route
Testing a New Route
- Navigate to the new route directly via URL
- Navigate to it via sidebar click
- Navigate to it programmatically via
navigate() - Refresh the page on the new route — verify it loads correctly
- Use browser back button from the new route
Quick Reference
File Locations
| What | Where |
|---|---|
| Module routes + sidebar | src/modules/CrelioDashboard/RoutesAndMenus/xxxRoutesAndMenu.tsx |
| Module assembly | src/modules/CrelioDashboard/RoutesAndMenus/routes.tsx |
| Route types | src/modules/CrelioDashboard/routeTypes.ts |
| Navigate utility | src/utils/navigate.ts |
| History instance | src/utils/history.ts |
| Sidebar component | src/modules/CrelioDashboard/Sidebar/sidebar.tsx |
| Module switcher | src/modules/CrelioDashboard/SwitchModuleDropdown/ |
| SPA entry point | src/modules/CrelioDashboard/index.tsx |
| Build config | app-config.js, config-overrides.js |
Key Imports
// Navigation
import { navigate } from "src/utils/navigate";
// Route types (for module hooks)
import { CrelioRouteType, CrelioSidebarItem, Module } from "../routeTypes";
// Redux state access
import { getReduxState } from "src/utils/helpers";
// History (only if you need location info, NOT for navigation)
import history from "src/utils/history";