fix: clinic creation gating, analysis line charts, username + task assignee, delete confirm

- Hide "Create clinic" (sidebar footer) for non-admins; only owner/admin can
  spin up additional clinics. Onboarding for brand-new users is unaffected.
- Analysis: drop the bar charts; show line charts (Sparkline) inside KPI cards
  that open a detail dialog with the full chart + per-point breakdown.
- Add Team Member: validate the username client-side (no spaces; letters,
  numbers, dots, underscores) with a clear warning + field hint.
- Tasks: New Task now has an Assignee selector (Myself / Other → department).
  Tasks are visible to the department they're assigned to (or the creator), and
  show who created them. Backend adds assignee_role + created_by_name with
  visibility filtering in listTasks; owners/admins see all.
- Care team: removing a member now asks for confirmation first (dialog) and
  surfaces success/failure + refreshes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalid Abdi
2026-06-08 19:52:12 +03:00
parent 8ab0552cf8
commit 4f8793c765
19 changed files with 2878 additions and 144 deletions
+30 -3
View File
@@ -322,20 +322,30 @@
"details": "Details — what needs to be done",
"detailsPlaceholder": "Describe what needs to happen, any context, links…",
"assignee": "Assignee",
"assigneePlaceholder": "e.g. Dr. Okafor",
"assigneeSelf": "Myself",
"assigneeOther": "Other",
"department": "Department",
"due": "Due",
"duePlaceholder": "e.g. Today",
"priorityLabel": "Priority",
"cancel": "Cancel",
"add": "Add task"
},
"list": {
"forDept": "For {{dept}}",
"personal": "Personal",
"byCreator": "by {{name}}"
},
"detail": {
"fallbackTitle": "Task",
"priorityBadge": "{{priority}} priority",
"status": "Status",
"completed": "Completed",
"open": "Open",
"assignee": "Assignee",
"assignedTo": "Assigned to",
"createdBy": "Created by",
"deptTeam": "{{dept}} team",
"personal": "Personal task",
"due": "Due",
"patient": "Patient",
"details": "Details",
@@ -404,7 +414,10 @@
"completed": "Completed"
},
"charts": {
"patientGrowthTitle": "Patient growth",
"title": "Trends",
"subtitle": "Tap a card for the full breakdown.",
"viewDetails": "Tap for details",
"patientGrowthTitle": "New patients",
"patientGrowthDescription": "New patients per month over the last 6 months",
"weeklyAppointmentsTitle": "Appointments this week",
"weeklyAppointmentsDescription": "Bookings per day for the current week",
@@ -712,6 +725,18 @@
"you": "(you)",
"addMember": "Add team member",
"removeMember": "Remove member",
"remove": {
"title": "Remove team member?",
"description": "{{name}} will lose access to this clinic. This can't be undone.",
"thisMember": "This member",
"cancel": "Cancel",
"confirm": "Remove",
"removing": "Removing…",
"removedTitle": "Member removed",
"removedBody": "{{name}} no longer has access.",
"failedTitle": "Couldn't remove member",
"failedBody": "Please try again."
},
"add": {
"title": "Add team member",
"step1Description": "Who are you adding, and what can they do?",
@@ -722,7 +747,9 @@
"roleLabel": "Role",
"usernameLabel": "Username",
"usernamePlaceholder": "jokafor",
"usernameHint": "Letters, numbers, dots and underscores — no spaces.",
"usernameTooShort": "Username must be at least {{count}} characters.",
"usernameInvalid": "Username can't contain spaces. Use letters, numbers, dots or underscores.",
"passwordLabel": "Password",
"passwordHint": "Must be at least {{count}} characters long.",
"passwordTooShort": "Password must be at least {{count}} characters.",
+4
View File
@@ -17,6 +17,10 @@ export const PROVISIONABLE_ROLES: RoleKey[] = [
"viewer",
];
// Departments a task can be assigned to (member roles). Mirrors the backend's
// TASK_DEPARTMENTS in lib/task-validation.ts.
export const DEPARTMENTS = ["admin", "doctor", "reception"] as const;
// The current user's role in the active clinic (null while loading or if they
// aren't a member). Re-fetches when the active organization changes.
export function useActiveRole(): string | null {
+5
View File
@@ -8,11 +8,15 @@ export type Task = {
id: string;
title: string;
assignee: string;
// Department (member role) the task is assigned to; null = personal task.
assigneeRole: string | null;
due: string;
priority: Priority;
patient: string | null;
notes: string | null;
done: boolean;
createdById: string | null;
createdByName: string | null;
createdAt: string;
updatedAt: string;
};
@@ -21,6 +25,7 @@ export type Task = {
export type TaskInput = {
title: string;
assignee?: string;
assigneeRole?: string | null;
due?: string;
priority?: Priority;
patient?: string | null;