Assessments
The assessments module is Coreola’s canonical workflow example. It demonstrates a real operational pattern — items with statuses, side resources, ownership, and a decision flow — built on the same primitives the rest of the app uses.
Treat it as the reference implementation when building your own workflow module.
- Source:
src/features/collections/assessments/ - API:
src/api/assessments/ - Routes:
/collections/assessments/list,/collections/assessments/details/:assessmentId?/:resource?/:resourceId?,/collections/assessments/queue - Dashboard:
/dashboards/assessments
Sub-modules
assessments/
├─ list/ # Catalog view — full table of all assessments
├─ details/ # Single assessment with cards (overview, findings, evidence, activity)
└─ queue/ # Operational table filtered by queue sectionEach is a Coreola page in its own right with its own model hook.
Domain entities
From src/api/assessments/assessments.types.ts:
Assessment
The primary entity. Key fields:
id,key(human-readable id),title,type,frameworkstatus— workflow status; one ofdraft,intake,in_review,waiting_for_evidence,remediation_in_progress,blocked,approved,approved_with_exceptions,rejected,completed,archived(11 states; valid transitions are enforced by the backendstatus_optionsreturned frommeta)priority,risk_level,decisionowner,reviewer,relationship_owner,customer— related entitiesfindings_count,open_findings_count,evidence_requests_count,overdue_evidence_requests_count,exceptions_countis_overdue,has_blocking_findingsinherent_risk_score,residual_risk_score,confidence_score,progressdue_date,next_review_at,submitted_at,completed_at,tags,control_sets
AssessmentFinding
A finding raised against an assessment.
id,assessment_id,title,category(one of 8:access_control,data_retention,encryption,logging,vulnerability_management,vendor_governance,privacy,resilience)severity— one oflow,medium,high,criticalstatus— one ofopen,accepted_risk,in_progress,resolved,closed(5 states)owner_id,due_date,description,recommendation,accepted_risk_reason,resolution_noteevidence_ids— links back to the evidence requests that resolve the findinginvalid— soft-flag for findings that were raised in error
EvidenceRequest
A request for supporting documentation.
id,assessment_id,title,request_note,control_refstatus— one ofrequested,in_review,received,rejected,cancelled,expired(6 states)recipient_id,recipient— the external party fulfilling the request (not always an internal user)requested_at,due_date,received_at— three time dimensions tracking the request lifecyclefile_count— number of files attached by the recipientarchived— soft-archive flag for completed requests no longer in active view
AssessmentActivity
The audit/timeline entry.
id,assessment_id,type(created,submitted,evidence_requested, …)actor_name,message,created_at
These four entities — main + sub-resources + activity — are the shape of an admin workflow module. Most domains map onto something similar.
List view
Route
/collections/assessments/list
Model hook
useListModel in features/collections/assessments/list/hooks/. Responsibilities:
- Manages a server-paginated table with
id: 'assessments-list'. - Calls
useGetAssessmentsQueryMapped(queryParams, { skip: !isHydrated }). - Builds filter config via
useAssessmentFilters(status, priority, owner, …). - Exposes
exportAllRowsviauseExportAllRowsfor CSV export.
View
Standard Table component + filter chips + per-row click to navigate to details. Pattern matches Tables.
Queue view
Route
/collections/assessments/queue
The queue is the operator’s working view: a server-paginated table focused by one queue slice at a time. An operator picks the queue slice from the table filters and scans the resulting worklist.
Queue slices
useQueueModel adds a persistent radio filter named queue_section with these slices:
needs_reviewwaiting_for_evidenceremediation_in_progressblockedoverdue
The selected slice is converted into assessment query params (status_values or is_overdue) and combined with the normal assessment filters for risk, owner, customer, and search.
Table and actions
The view renders:
StatsLinewith counts for every queue slice.- The shared
Tablecomponent with server pagination, search, filters, custom columns, CSV export, and row click navigation. - An
ActionMenuper row.
Row actions:
- View: navigate to
/collections/assessments/details/:assessmentId. - Assign owner:
AssignOwnerDialogViewwith user autocomplete. - Request evidence:
RequestEvidenceDialogView. - Change status:
ChangeStatusDialogView. - Submit decision:
SubmitDecisionDialogView.
Dialog state is owned by useQueueModel; mutation form state is owned by useUpdateAssessmentAction and useEvidenceRequest. The pattern mirrors Details Pages.
Stats
useGetAssessmentsStatsQuery returns per-slice counts independently from the table query, so the queue can refresh stats without replacing the current page of rows. The card title follows the active slice, e.g. Assessment queue: Remediation in progress.
Details view
The details page is documented in depth at UI Patterns → Details Pages. A quick recap of what is unique to assessments:
Card stack
OverviewCardView— title, status, owner, dates, key-value metadata (toggleable edit mode)DecisionCardView— the workflow controls (submit decision, change status)FindingsCardView— sub-table of findings with row actionsEvidenceCardView— sub-table of evidence requests with row actionsActivityCardView— timeline ofAssessmentActivityevents
Deep linking to sub-resources
The route is parameterized:
/collections/assessments/details/:assessmentId?/:resource?/:resourceId?with resource constrained to 'finding' | 'evidence' (declared in routes.ts via the values field). So /collections/assessments/details/123/finding/456 deep-links to finding 456 inside assessment 123.
Sub-resource dialogs
NewFindingDialogView— create a findingRequestEvidenceDialogView— request evidence from a recipientAssignFindingOwnerDialogViewChangeFindingStatusDialogViewChangeEvidenceStatusDialogViewOverviewCardEditModeView— multi-field edit
The model hook composes per-action sub-hooks (useCreateFinding, useEvidenceRequest, useAssignFindingOwner, …) so each concern stays small.
Assessments dashboard
Route
/dashboards/assessments
A KPI-and-charts view aggregating across all assessments. Same shape as the Application dashboard — KPI row, charts, supporting cards.
The view model fetches a single aggregated endpoint with pollInterval: 60 and exposes derived series.
State transitions — where they live
The status enum is enforced on the backend, not the frontend. The UI:
- Reads the available
status_optionsfrom themetaendpoint (useGetAssessmentsMetaQuery). - Renders the current status with
Status. - Triggers a transition via
useUpdateAssessmentStatus(oruseUpdateFindingStatus,useUpdateEvidenceStatusfor sub-resources). - Refetches thanks to RTK Query tag invalidation.
The same pattern applies to decisions. The decision flow is mostly a backend concern — the frontend submits and reflects.
Permissions
Assessment routes are gated by abilityCan: ['assessment.read'] at the collection level. Mutations check:
assessment.update— for editassessment.create— for new finding / new evidence request (sub-resources inherit the parent’s create ability in this implementation)assessment.delete— for archive / remove
Buttons that the user is not allowed to use are hidden, not disabled — see Principles → Consistent feedback.
How to fork this module for your domain
If your product has a similar workflow (claims, tickets, applications, audits), you can fork the assessments module:
- Rename the entity (
Assessment→<YourEntity>). - Adjust the API slice (
src/api/<yourEntity>/). - Rename the routes and the feature folder.
- Adjust the card composition: drop cards you do not need, add domain-specific ones.
- Keep the queue concept if your workflow has natural sections; drop it if not.
The conventions — model hook + views + sub-hooks + URL-driven sub-resource params — transfer 1:1.
Next steps
- Details Pages — the deep dive on the card-stack pattern
- Tables — the list view’s table
- Adding a New Module — the step-by-step recipe for cloning this shape