Historie: nachträglich reflektieren bis 3 Tage nach dem Tag
Reflexion/ToDo-Bearbeitung war nur bis 06:00 des Folgetags moeglich. Jetzt 3-Tage-Nachfrist (isEditableWithinGrace) und ein Bearbeiten-Weg aus der Historie: - werktag: isEditableWithinGrace(entryDate, now, 3) + Tests - submitReflection/editTodoTitle: Sperre auf die 3-Tage-Nachfrist gelockert - neue Query dayEntry.ownForDate(date): eigener Tageseintrag + editable-Flag - reflexion.tsx liest ?date= und laedt vergangene Tage; Felder werden mit der bestehenden Reflexion vorbefuellt - historie.tsx: "Bearbeiten"-Button am eigenen Eintrag (wenn <= 3 Tage) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ccd5c42709
commit
b340b45110
5 files changed
+172
-50
No files matched your search
@@ -9,6 +9,7 @@ import {
|
||||
addCalendarDays,
|
||||
workdayCutoffMs,
|
||||
isWorkdaySealed,
|
||||
isEditableWithinGrace,
|
||||
} from './werktag';
|
||||
|
||||
describe('werktag', () => {
|
||||
@@ -77,4 +78,17 @@ describe('werktag', () => {
|
||||
expect(isWorkdaySealed(sameDayStart, new Date('2026-06-07T06:00:00+02:00'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEditableWithinGrace — 3-Tage-Nachfrist', () => {
|
||||
const entry = '2026-06-05'; // Fr
|
||||
it('am Eintragstag selbst editierbar', () => {
|
||||
expect(isEditableWithinGrace(entry, new Date('2026-06-05T20:00:00+02:00'))).toBe(true);
|
||||
});
|
||||
it('bis Ende des 3. Tages danach (08.) editierbar', () => {
|
||||
expect(isEditableWithinGrace(entry, new Date('2026-06-08T23:00:00+02:00'))).toBe(true);
|
||||
});
|
||||
it('ab dem 4. Tag (09.) gesperrt', () => {
|
||||
expect(isEditableWithinGrace(entry, new Date('2026-06-09T00:30:00+02:00'))).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -46,6 +46,19 @@ export function isWorkdaySealed(entryDate: string, now: Date = new Date()): bool
|
||||
return now.getTime() >= workdayCutoffMs(entryDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* True, solange ein Eintrag mit Datum `entryDate` noch nachträglich bearbeitet
|
||||
* werden darf: bis zum Ende des `graceDays`-ten Kalendertages danach (Default 3).
|
||||
* Genutzt für die Reflexion in der Historie (Tag selbst + 3 Tage Nachfrist).
|
||||
*/
|
||||
export function isEditableWithinGrace(
|
||||
entryDate: string,
|
||||
now: Date = new Date(),
|
||||
graceDays = 3,
|
||||
): boolean {
|
||||
return now.getTime() < berlinDayStartMs(addCalendarDays(entryDate, graceDays + 1));
|
||||
}
|
||||
|
||||
export function formatIsoDate(d: Date): string {
|
||||
const fmt = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: TIMEZONE,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
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';
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
addCalendarDays,
|
||||
workdayCutoffMs,
|
||||
isWorkdaySealed,
|
||||
isEditableWithinGrace,
|
||||
} from '../lib/werktag.js';
|
||||
import { kimaiClockIn, kimaiClockOut, kimaiEnabled } from '../lib/kimai.js';
|
||||
|
||||
@@ -421,10 +423,10 @@ export const dayEntryRouter = router({
|
||||
if (!existing) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'ToDo nicht gefunden.' });
|
||||
}
|
||||
if (isWorkdaySealed(existing.date, new Date())) {
|
||||
if (!isEditableWithinGrace(existing.date, new Date())) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'ToDos eines Vortags können nicht mehr bearbeitet werden.',
|
||||
message: 'ToDos können nur bis 3 Tage nach dem Tag bearbeitet werden.',
|
||||
});
|
||||
}
|
||||
const [updated] = await ctx.db
|
||||
@@ -520,13 +522,12 @@ export const dayEntryRouter = router({
|
||||
if (!entry) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Tageseintrag nicht gefunden.' });
|
||||
}
|
||||
// Editierbar, solange der Arbeitstag noch läuft. Damit kann mehrmals am Tag
|
||||
// (auch nach Mitternacht bei Nachtarbeit) aktualisiert werden; ab 06:00 des
|
||||
// Folgetags ist der Tag final und kann nicht mehr verändert werden.
|
||||
if (isWorkdaySealed(entry.date, new Date())) {
|
||||
// Reflexion ist bis 3 Kalendertage nach dem Eintragstag nachträglich
|
||||
// editierbar (Nachfrist für die Historie). Danach final.
|
||||
if (!isEditableWithinGrace(entry.date, new Date())) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Reflexion eines Vortags kann nicht mehr bearbeitet werden.',
|
||||
message: 'Reflexion kann nur bis 3 Tage nach dem Tag bearbeitet werden.',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -758,6 +759,30 @@ export const dayEntryRouter = router({
|
||||
todos: counts.get(e.id) ?? { done: 0, open: 0, dropped: 0 },
|
||||
}));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Eigener Tageseintrag eines bestimmten Datums — zum nachträglichen Reflektieren
|
||||
* aus der Historie. `editable` zeigt, ob die 3-Tage-Nachfrist noch läuft.
|
||||
*/
|
||||
ownForDate: protectedProcedure
|
||||
.input(z.object({ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/) }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const entry =
|
||||
(
|
||||
await ctx.db
|
||||
.select()
|
||||
.from(dayEntry)
|
||||
.where(and(eq(dayEntry.userId, ctx.user.id), eq(dayEntry.date, input.date)))
|
||||
.limit(1)
|
||||
)[0] ?? null;
|
||||
if (!entry) return { entry: null, todos: [] as Todo[], editable: false };
|
||||
const todos = await ctx.db
|
||||
.select()
|
||||
.from(todo)
|
||||
.where(eq(todo.dayEntryId, entry.id))
|
||||
.orderBy(todo.order);
|
||||
return { entry, todos, editable: isEditableWithinGrace(entry.date, new Date()) };
|
||||
}),
|
||||
});
|
||||
|
||||
// Helper für Cron-Endpoint
|
||||
|
||||
Reference in new issue
Block a user