feat(dashboard): show the user's work plan for today
Adds a "Můj plán na dnešek" card near the clock-in: for the logged-in user it shows today's work-plan entry — category (coloured dot + label), the project/site (the "where"), the note, and a "součást rozsahu …" badge only for genuinely multi-day ranges. A muted "Pro dnešek nemáte naplánováno." empty state when nothing is scheduled. Server: GET /api/admin/dashboard now returns today_plan, reusing plan.service resolveCell (override-beats-range precedence) for the current user + today's local (Prague) date, enriched with the category label + colour so the widget needs no extra query. Gated by attendance.record / attendance.manage (the plan permission); omitted otherwise. No migration. Verified in Chrome: renders today's entry (Práce · OBJ-2026-006 — koko) with the category colour. tsc -b --noEmit, npm run build, vitest 152/152. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { requireAuth } from "../../middleware/auth";
|
||||
import { success } from "../../utils/response";
|
||||
import { localTimeStr } from "../../utils/date";
|
||||
import { toCzk } from "../../services/exchange-rates";
|
||||
import { resolveCell } from "../../services/plan.service";
|
||||
|
||||
export default async function dashboardRoutes(
|
||||
fastify: FastifyInstance,
|
||||
@@ -42,6 +43,29 @@ export default async function dashboardRoutes(
|
||||
result.my_shift = { has_ongoing: myShift !== null };
|
||||
}
|
||||
|
||||
// Today's work plan (personal) — powers the "Můj plán na dnešek" card.
|
||||
// resolveCell applies override-beats-range precedence; null = the user has
|
||||
// plan access but nothing is scheduled today. The category key is enriched
|
||||
// with its label + colour so the widget needs no extra query. todayStr uses
|
||||
// local (Europe/Prague) calendar date — the plan's @db.Date day.
|
||||
if (has("attendance.record") || has("attendance.manage")) {
|
||||
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
||||
const cell = await resolveCell(userId, todayStr);
|
||||
if (cell) {
|
||||
const cat = await prisma.plan_categories.findUnique({
|
||||
where: { key: cell.category },
|
||||
select: { label: true, color: true },
|
||||
});
|
||||
result.today_plan = {
|
||||
...cell,
|
||||
category_label: cat?.label ?? cell.category,
|
||||
category_color: cat?.color ?? null,
|
||||
};
|
||||
} else {
|
||||
result.today_plan = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Attendance admin — only for attendance.manage
|
||||
if (has("attendance.manage")) {
|
||||
const [todayAttendance, onLeaveToday, usersCount] = await Promise.all([
|
||||
|
||||
Reference in New Issue
Block a user