Files
temetro/backend/src/routes/tasks.ts
T
Khalid Abdi 4f8793c765 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>
2026-06-08 19:52:12 +03:00

114 lines
2.8 KiB
TypeScript

import { Router } from "express";
import { HttpError } from "../lib/http-error.js";
import { taskInputSchema, taskPatchSchema } from "../lib/task-validation.js";
import {
requireAuth,
requireOrg,
requirePermission,
} from "../middleware/auth.js";
import { recordActivity } from "../services/activity.js";
import * as service from "../services/tasks.js";
export const tasksRouter = Router();
tasksRouter.use(requireAuth, requireOrg);
tasksRouter.get(
"/",
requirePermission({ task: ["read"] }),
async (req, res, next) => {
try {
res.json(
await service.listTasks(req.organizationId!, {
userId: req.user!.id,
role: req.memberRole ?? "",
}),
);
} catch (err) {
next(err);
}
},
);
tasksRouter.post(
"/",
requirePermission({ task: ["write"] }),
async (req, res, next) => {
try {
const input = taskInputSchema.parse(req.body);
const created = await service.createTask(
req.organizationId!,
{ id: req.user!.id, name: req.user!.name },
input,
);
await recordActivity({
orgId: req.organizationId!,
actor: { id: req.user!.id, name: req.user!.name },
action: `Created task — ${created.title}`,
entityType: "task",
entityId: created.id,
});
res.status(201).json(created);
} catch (err) {
next(err);
}
},
);
tasksRouter.patch(
"/:id",
requirePermission({ task: ["write"] }),
async (req, res, next) => {
try {
const patch = taskPatchSchema.parse(req.body);
const updated = await service.updateTask(
req.organizationId!,
req.params.id as string,
patch,
);
if (!updated) throw new HttpError(404, "Task not found.");
const action =
patch.done === undefined
? `Updated task — ${updated.title}`
: patch.done
? `Completed task — ${updated.title}`
: `Reopened task — ${updated.title}`;
await recordActivity({
orgId: req.organizationId!,
actor: { id: req.user!.id, name: req.user!.name },
action,
entityType: "task",
entityId: updated.id,
});
res.json(updated);
} catch (err) {
next(err);
}
},
);
tasksRouter.delete(
"/:id",
requirePermission({ task: ["delete"] }),
async (req, res, next) => {
try {
const ok = await service.deleteTask(
req.organizationId!,
req.params.id as string,
);
if (!ok) throw new HttpError(404, "Task not found.");
await recordActivity({
orgId: req.organizationId!,
actor: { id: req.user!.id, name: req.user!.name },
action: "Deleted task",
entityType: "task",
entityId: req.params.id as string,
});
res.status(204).end();
} catch (err) {
next(err);
}
},
);