mirror of
https://github.com/temetro/temetro.git
synced 2026-08-21 15:36:47 +00:00
4f8793c765
- 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>
114 lines
2.8 KiB
TypeScript
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);
|
|
}
|
|
},
|
|
);
|