feat(historie): team-weite Tages-Historie (ENTWICKLUN-10)
ci / quality (push) Failing after 46s
ci / build (push) Has been skipped
deploy / deploy (push) Successful in 55s

Neue Seite /historie: pro vergangenem Tag sieht man, was jede/r gemacht hat —
Reflexion (Ampel/Trend/Text) + ToDos (erledigt/offen/verworfen), plus Liste
„ohne Eintrag". Datums-Navigation (vorheriger/nächster Werktag, Datum-Picker);
Default = vorheriger Werktag. Heutige Reflexionen bleiben privat (nur ToDos).

- Endpoint dayEntry.historyForDate({date?}) liefert Einträge + prev/next-Werktag.
- TopNav-Tab „Historie" (g i), Link „Ganze Historie →" im Dashboard-Feed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 4.8 committed 2026-06-06 17:46:49 +02:00
1 parent c3d3c4500e
commit 9bf20fbabd
7 files changed
+259 -5

No files matched your search

+67 -2
View File
@@ -1,9 +1,9 @@
import { TRPCError } from '@trpc/server';
import { and, eq, isNull, lt, asc, desc, inArray } from 'drizzle-orm';
import { dayEntry, todo, blocker, type Todo } from '@cockpit/db';
import { dayEntry, todo, blocker, user, type Todo } from '@cockpit/db';
import { DayEntrySchemas } from '@cockpit/shared';
import { router, protectedProcedure } from '../trpc.js';
import { todayIso } from '../lib/werktag.js';
import { todayIso, previousWerktag, nextWerktag } from '../lib/werktag.js';
export const dayEntryRouter = router({
/**
@@ -383,6 +383,71 @@ export const dayEntryRouter = router({
return entries;
}),
/**
* Team-weite Historie eines (vergangenen) Tages: was hat jede/r gemacht —
* Reflexion (Ampel/Trend/Text) + ToDos. Default-Datum = vorheriger Werktag.
* Datums-Navigation (prev/next Werktag) wird mitgeliefert.
*/
historyForDate: protectedProcedure
.input(DayEntrySchemas.historyForDateInput)
.query(async ({ ctx, input }) => {
const today = todayIso();
const date = input.date ?? previousWerktag(today);
const isToday = date === today;
const entries = await ctx.db.select().from(dayEntry).where(eq(dayEntry.date, date));
const userRows = await ctx.db
.select({ id: user.id, name: user.name })
.from(user)
.where(eq(user.active, true));
const nameById = new Map(userRows.map((u) => [u.id, u.name]));
const ids = entries.map((e) => e.id);
const todoRows = ids.length
? await ctx.db.select().from(todo).where(inArray(todo.dayEntryId, ids)).orderBy(asc(todo.order))
: [];
const todosByEntry = new Map<string, Todo[]>();
for (const t of todoRows) {
const arr = todosByEntry.get(t.dayEntryId) ?? [];
arr.push(t);
todosByEntry.set(t.dayEntryId, arr);
}
const list = entries
.map((e) => ({
userId: e.userId,
name: nameById.get(e.userId) ?? '—',
status: e.status,
ampel: e.ampel,
trend: e.trend,
// Heutige Reflexion bleibt privat bis morgen; ToDos sind teamweit sichtbar.
reflectionText: isToday ? null : e.reflectionText,
url: e.url,
todos: (todosByEntry.get(e.id) ?? []).map((t) => ({
id: t.id,
title: t.title,
status: t.status,
dropReason: t.dropReason,
})),
}))
.sort((a, b) => a.name.localeCompare(b.name));
const withEntry = new Set(entries.map((e) => e.userId));
const ohneEintrag = userRows
.filter((u) => !withEntry.has(u.id))
.map((u) => u.name)
.sort((a, b) => a.localeCompare(b));
const nw = nextWerktag(date);
return {
date,
prevDate: previousWerktag(date),
nextDate: nw < today ? nw : null,
entries: list,
ohneEintrag,
};
}),
/**
* „Heute fehlt"-Liste (Anwesenheits-Übersicht).
*/