diff --git a/apps/api/src/routers/day-entry.ts b/apps/api/src/routers/day-entry.ts
index ed81cdf..f627c71 100644
--- a/apps/api/src/routers/day-entry.ts
+++ b/apps/api/src/routers/day-entry.ts
@@ -1,12 +1,11 @@
import { TRPCError } from '@trpc/server';
-import { and, eq, gte, isNull, lt, lte, asc, desc, inArray } from 'drizzle-orm';
+import { and, eq, gt, gte, isNull, lt, lte, asc, desc, inArray } from 'drizzle-orm';
import { dayEntry, todo, blocker, user, type Db, type DayEntry, type Todo } from '@cockpit/db';
import { DayEntrySchemas } from '@cockpit/shared';
import { router, protectedProcedure } from '../trpc.js';
import {
todayIso,
previousWerktag,
- nextWerktag,
addCalendarDays,
workdayCutoffMs,
isWorkdaySealed,
@@ -618,7 +617,18 @@ export const dayEntryRouter = router({
.input(DayEntrySchemas.historyForDateInput)
.query(async ({ ctx, input }) => {
const today = todayIso();
- const date = input.date ?? previousWerktag(today);
+ // Default-Tag = der jüngste Tag VOR heute, an dem es überhaupt Einträge gibt
+ // (nicht starr der vorige Werktag — sonst sieht man Wochenend-Arbeit nicht).
+ const fallbackDate =
+ (
+ await ctx.db
+ .select({ d: dayEntry.date })
+ .from(dayEntry)
+ .where(lt(dayEntry.date, today))
+ .orderBy(desc(dayEntry.date))
+ .limit(1)
+ )[0]?.d ?? previousWerktag(today);
+ const date = input.date ?? fallbackDate;
const isToday = date === today;
const entries = await ctx.db.select().from(dayEntry).where(eq(dayEntry.date, date));
@@ -669,11 +679,28 @@ export const dayEntryRouter = router({
.map((u) => u.name)
.sort((a, b) => a.localeCompare(b));
- const nw = nextWerktag(date);
+ // Pfeil-Navigation springt zum nächsten/vorherigen Tag MIT Einträgen
+ // (überspringt leere Tage, erreicht aber Wochenend-Arbeit).
+ const prevRow = (
+ await ctx.db
+ .select({ d: dayEntry.date })
+ .from(dayEntry)
+ .where(lt(dayEntry.date, date))
+ .orderBy(desc(dayEntry.date))
+ .limit(1)
+ )[0];
+ const nextRow = (
+ await ctx.db
+ .select({ d: dayEntry.date })
+ .from(dayEntry)
+ .where(and(gt(dayEntry.date, date), lte(dayEntry.date, today)))
+ .orderBy(asc(dayEntry.date))
+ .limit(1)
+ )[0];
return {
date,
- prevDate: previousWerktag(date),
- nextDate: nw < today ? nw : null,
+ prevDate: prevRow?.d ?? null,
+ nextDate: nextRow?.d ?? null,
entries: list,
ohneEintrag,
};
diff --git a/apps/web/src/routes/historie.tsx b/apps/web/src/routes/historie.tsx
index b048ae1..ed0ca87 100644
--- a/apps/web/src/routes/historie.tsx
+++ b/apps/web/src/routes/historie.tsx
@@ -68,9 +68,10 @@ export function HistorieRoute() {
@@ -79,7 +80,7 @@ export function HistorieRoute() {
type="button"
onClick={() => nextDate && setDate(nextDate)}
disabled={!nextDate}
- aria-label="Nächster Werktag"
+ aria-label="Nächster Tag mit Eintrag"
className="inline-flex h-9 w-9 items-center justify-center rounded-[var(--radius-md)] border border-[var(--color-border)] text-[var(--color-ink-muted)] hover:border-[var(--color-ink)] hover:text-[var(--color-ink)] disabled:opacity-40 disabled:hover:border-[var(--color-border)]"
>
@@ -154,21 +155,25 @@ function PersonCard({ person: p }: { person: HistoryPerson }) {
: 'bg-[var(--color-border-strong)]',
)}
/>
-
- {t.title}
- {t.unplanned && (
-
- ungeplant
+
+
+ {t.title}
+ {t.unplanned && (
+
+ ungeplant
+
+ )}
+
+ {t.dropReason && (
+
+ {t.dropReason}
)}
- {t.status === 'dropped' && t.dropReason && (
- — {t.dropReason}
- )}
-
+
))}
@@ -177,7 +182,17 @@ function PersonCard({ person: p }: { person: HistoryPerson }) {
{p.reflectionText && (
{p.reflectionText}
)}
- {p.todos.length === 0 && !p.reflectionText && (
+ {p.url && (
+
+ {p.url}
+
+ )}
+ {p.todos.length === 0 && !p.reflectionText && !p.url && (
Keine Details.
)}
diff --git a/apps/web/src/routes/reflexion.tsx b/apps/web/src/routes/reflexion.tsx
index fd3a332..7bc7d14 100644
--- a/apps/web/src/routes/reflexion.tsx
+++ b/apps/web/src/routes/reflexion.tsx
@@ -51,12 +51,13 @@ function ReflectForm({
status: TodoStatus;
bezugKind: 'project' | 'deal' | 'campaign' | 'allgemein' | null;
unplanned?: boolean;
+ dropReason?: string | null;
}>;
onDone: () => void;
}) {
const [todoStates, setTodoStates] = useState<
Record
- >(Object.fromEntries(todos.map((t) => [t.id, { status: t.status }])));
+ >(Object.fromEntries(todos.map((t) => [t.id, { status: t.status, reason: t.dropReason ?? '' }])));
const [reflectionText, setReflectionText] = useState('');
const [ampel, setAmpel] = useState(null);
const [trend, setTrend] = useState(null);
@@ -86,6 +87,10 @@ function ReflectForm({
setTodoStates((prev) => ({ ...prev, [id]: { ...prev[id], status } }));
}
+ function setTodoReason(id: string, reason: string) {
+ setTodoStates((prev) => ({ ...prev, [id]: { status: prev[id]?.status ?? 'open', reason } }));
+ }
+
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!ampel) {
@@ -107,7 +112,7 @@ function ReflectForm({
todos: todos.map((t) => ({
id: t.id,
status: todoStates[t.id]?.status ?? t.status,
- dropReason: todoStates[t.id]?.reason,
+ dropReason: todoStates[t.id]?.reason?.trim() || undefined,
})),
reflectionText,
ampel,
@@ -138,36 +143,47 @@ function ReflectForm({
{todos.map((t) => {
const status = todoStates[t.id]?.status ?? t.status;
return (
-
-
- {t.unplanned && (
-
- ungeplant
-
- )}
-
-
setTodoStatus(t.id, 'done')}
- label="Erledigt"
- >
-
-
-
setTodoStatus(t.id, 'open')}
- label="Offen"
- >
-
-
-
setTodoStatus(t.id, 'dropped')}
- label="Verworfen"
- >
-
-
+
+
+
+ {t.unplanned && (
+
+ ungeplant
+
+ )}
+
+ setTodoStatus(t.id, 'done')}
+ label="Erledigt"
+ >
+
+
+ setTodoStatus(t.id, 'open')}
+ label="Offen"
+ >
+
+
+ setTodoStatus(t.id, 'dropped')}
+ label="Verworfen"
+ >
+
+
+
+ setTodoReason(t.id, e.target.value)}
+ placeholder="Notiz (optional) — Ergebnis, Kontext, Link …"
+ maxLength={500}
+ aria-label={`Notiz zu ${t.title}`}
+ className="w-full rounded-[var(--radius-md)] border border-[var(--color-border)] bg-white px-2 py-1 text-xs focus:border-[var(--color-ink)] focus:outline-none"
+ />
);
})}
diff --git a/packages/shared/src/schemas/day-entry.ts b/packages/shared/src/schemas/day-entry.ts
index 21f31d8..7267c35 100644
--- a/packages/shared/src/schemas/day-entry.ts
+++ b/packages/shared/src/schemas/day-entry.ts
@@ -28,7 +28,9 @@ export const submitReflectionInput = z.object({
z.object({
id: z.string().uuid(),
status: todoStatusSchema,
- dropReason: z.string().max(200).optional(),
+ // Optionale Notiz pro Task (frühere „Verworfen-Grund"-Spalte, jetzt für
+ // jeden Status als freie Beschreibung nutzbar).
+ dropReason: z.string().max(500).optional(),
}),
)
.default([]),