103 lines
7.3 KiB
Markdown
103 lines
7.3 KiB
Markdown
---
|
|
name: dyolink-lab-tasks
|
|
description: Lab Tasks tab — list, sort, filters, case grouping, prosthesis colors, step-completed filter. Use when changing TasksPage, tasks API, or lab task list UX.
|
|
---
|
|
|
|
# Lab Tasks
|
|
|
|
Main UI: [`frontend/src/components/ui/lab/TasksPage.tsx`](frontend/src/components/ui/lab/TasksPage.tsx)
|
|
Backend: [`backend/src/modules/tasks/`](backend/src/modules/tasks/)
|
|
|
|
## Default sort (backend)
|
|
|
|
`sortBy=date` + `sortDir=desc`:
|
|
|
|
1. `labCase.sentAt` desc (newest case first)
|
|
2. `labCaseId`, `treatmentDetailId`, `prosthesisTypeCode` asc (stable grouping)
|
|
3. `stepOrder` asc (steps 1→N within prosthesis group)
|
|
4. `id` asc
|
|
|
|
Other sorts use flat list on the frontend; `stepOrder asc` is still a tiebreaker.
|
|
|
|
## Case grouping (frontend)
|
|
|
|
- **`sortBy === 'date'`** → grouped view via [`taskListGrouping.ts`](frontend/src/components/lab/taskListGrouping.ts): case header → prosthesis sub-header → task rows.
|
|
- **Other sorts** → flat list; show muted hint (`groupingOff*` i18n keys). Each row keeps clinic/patient/teeth context.
|
|
|
|
Components: `TaskCaseGroupHeader`, `TaskProsthesisGroupHeader`, `TaskRow`.
|
|
|
|
**Grouped comments (`sortBy=date`):** single comments button on `TaskCaseGroupHeader`; panel expands below header (`expandedCommentsCaseId`). Per-task comments button only in **flat** sort (`showCommentsButton={flatMode}`). Shared `LabCaseCommentsPanel` with `viewerSide="LAB"` (newest-first, logical start/end chat align).
|
|
|
|
## Prosthesis colors
|
|
|
|
- Map: [`catalog-type-colors.ts`](frontend/src/components/shared/catalog-type-colors.ts) → `PROSTHESIS_TYPE_COLORS` (one hex per catalog code).
|
|
- Resolve with [`prosthesisTypeDisplay.ts`](frontend/src/components/treatment/prosthesisTypeDisplay.ts) — use `prosthesisTypeBadgeStyleFromCatalog(code, catalog)`, **not** list row index.
|
|
- Load catalog via `prosthesisCatalogApi.list()` on Tasks/Cases/Today dashboard.
|
|
|
|
## Filters
|
|
|
|
| Param | API | UI |
|
|
|-------|-----|-----|
|
|
| `q`, `clinicOrganizationId`, `status` | `GET /tasks` | Search, clinic, status |
|
|
| `stepCompleted` | `GET /tasks` | Workflow step dropdown |
|
|
| `pinImportant` | `GET /tasks` | Important first (sort pin) |
|
|
| `assignedToMe` | `GET /tasks` | Only tasks assigned to current user |
|
|
| `prosthesisTypeCode` | `GET /tasks` | From Today prosthesis chart deep link |
|
|
| `unassignedOnly` | `GET /tasks` | Tasks with no assignee |
|
|
| `overdue` | `GET /tasks` | Cases with due date before today and at least one in-progress task |
|
|
| `sortBy=dueDate` | `GET /tasks` | Sort by `LabCase.dueDate` (flat list; grouping off) |
|
|
| Clinics + steps options | `GET /tasks/filter-options` | Populates dropdowns (not from current page) |
|
|
|
|
**Task assignment:** Managed in **Cases** (`TAB_CASES_EDIT`), not on Tasks tab. `PATCH /cases/:caseId/tasks/:taskId/assign`; assignable staff via `GET /cases/assignable-staff` (members with `TAB_TASKS_EDIT`, including participating owner). Case detail task row: step label, status badge, assign dropdown, and last-updated line on one compact row.
|
|
|
|
**Tasks visibility & status edit:** All tasks remain visible to every user with task access (no hiding assigned tasks). **Unassigned** tasks or tasks **assigned to you** → status dropdown when `TAB_TASKS_EDIT`. **Assigned to someone else** → read-only “Assigned to {name}” badge instead of the dropdown (backend rejects status PATCH). Same rules on **lab case share link** page (`CaseTasksFocusView` + `canEditLabTaskStatus`). Managers assign/monitor in Cases.
|
|
|
|
**Step completed filter:** Restricts to prosthesis groups `(labCaseId, treatmentDetailId, prosthesisTypeCode)` where that `workflowStepCode` task is `COMPLETED`. Combined with `status=IN_PROGRESS`, returns only in-progress tasks in those groups (completed step row hidden).
|
|
|
|
- **Important first:** `pinImportant=true` prepends important cases in sort order.
|
|
- **Assigned to me:** `assignedToMe=true` filters to current user's assigned tasks only.
|
|
- **Overdue cases:** `overdue=true` — `LabCase.dueDate` before start of UTC day **and** at least one task still `IN_PROGRESS`. Shown with error badge on Cases list/detail and task case headers.
|
|
- **Sort by due date:** `sortBy=dueDate` — flat list (grouping off); tiebreakers match other non-date sorts.
|
|
- **Reset view:** `resetView` restores `DEFAULT_TASKS_VIEW` from `tasksViewDefaults.ts`.
|
|
- **URL state:** `parseTasksSearchParams` applies Today deep-link query params on mount (`importantOnly`, `overdueOnly`, `unassignedOnly`, `prosthesisTypeCode`, `status`, sort).
|
|
- **Show in case:** flat-sort rows only; resets filters/sort, calls `GET /tasks/locate-page` to find the correct page in the full default-sorted list, then highlights + scrolls to the task.
|
|
- **Complete animation:** when marking done under in-progress filter, row plays exit animation + success toast before refetch.
|
|
|
|
## APIs
|
|
|
|
- `GET /tasks` — paginated flat task list (grouping is client-side when `sortBy=date`)
|
|
- `PATCH /tasks/:taskId` — update status (only assignee or unassigned task). Completing **`intraoral_scan`** also completes every other `intraoral_scan` task in the same case (case-scoped; catalog first step for all prosthesis types).
|
|
- `GET /tasks/filter-options` — clinics + workflow steps (localized)
|
|
- `GET /tasks/locate-page` — page number for a task in the sorted filtered list
|
|
- `GET /cases/assignable-staff` — staff eligible for task assignment
|
|
- `PATCH /cases/:caseId/tasks/:taskId/assign` — assign or unassign (`assigneeUserId` nullable)
|
|
|
|
List items include `caseSentAt`, `caseDueDate`, `isCaseOverdue`, `assignee`, `assignedAt` for case headers / flat rows.
|
|
|
|
## Case due dates (clinic → lab)
|
|
|
|
- **Schema:** `LabCase.dueDate` (optional `DateTime`).
|
|
- **Clinic set:** Treatment lab dispatch panel — date input on unsent shipment (saved with draft/send); on sent cases, blur saves via `PATCH /treatments/lab-cases/:labCaseId/due-date`.
|
|
- **Edit lock:** Clinic cannot change due date after **all** tasks are completed (`taskProgress.completed === taskProgress.total`).
|
|
- **Lab display:** Cases list + detail; Tasks case group header when grouped by date.
|
|
- **Utils:** `backend/src/common/lab-case-due-date.ts`, `frontend/src/components/lab/labCaseDueDateDisplay.ts`, `LabCaseDueDateBadge`.
|
|
|
|
## Mobile UX (Tasks + Treatment dispatch)
|
|
|
|
Keep changes minimal — match existing `sm:` breakpoint patterns elsewhere in the app.
|
|
|
|
- **Task status control:** `LAB_TASK_STATUS_SELECT_CLASS` in [`formSelectStyles.ts`](frontend/src/components/shared/formSelectStyles.ts) — full-width, `min-h-[44px]`, `text-base` on mobile; compact on `sm+`. Read-only status / assignee badges match height on mobile.
|
|
- **Sticky case header:** `TaskCaseGroupHeader` uses `sticky top-0 z-10` + translucent background when `sortBy=date` (grouped view). Sticks within dashboard `<main>` scroll.
|
|
- **Tasks filters:** filter `<select>`s use the same 44px mobile height as other form controls.
|
|
- **Treatment lab dispatch:** shipment card `p-3 sm:p-4`; **Send to lab** is `w-full sm:w-auto`.
|
|
|
|
## Tab badges + live soft refresh
|
|
|
|
See `.cursor/skills/tab-badges/SKILL.md` — split lab Cases/Tasks counts, clinic Treatment; `useTabBadgeCounts` + `notifyTabBadgesChanged`.
|
|
|
|
Inbox Socket.IO `notification.created` → `notifyTabBadgesChanged()` → silent `loadTasks({ silent: true })` on an open Tasks page (filters preserved; no remount). Details: `.cursor/skills/notifications-inbox/SKILL.md`.
|
|
|
|
## Permissions
|
|
|
|
`TAB_TASKS_READ` / `TAB_TASKS_EDIT`; `LabOrgGuard` on all task routes.
|