diff --git a/.phase21-state.json b/.phase21-state.json index c56fab3..7038ed0 100644 --- a/.phase21-state.json +++ b/.phase21-state.json @@ -1,10 +1,11 @@ { "completed_features": [], - "current_feature": "color-coded-customer-rows", + "current_feature": "time-entry-templates-page", "started_at": "2026-05-23T08:09:40.135892", "attempted_features": [ "keyboard-undo-stack", "snippet-shortcuts", - "smart-rounding-on-input" + "smart-rounding-on-input", + "color-coded-customer-rows" ] } \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index dc3d36c..7f289cd 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -2550,3 +2550,20 @@ src/index.ts(27,25): error TS2769: No overload matches this call. Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTy +- `08:13:58` **INFO** Committed feature color-coded-customer-rows +- `08:13:58` **INFO** Pushed: rc=0 + +## Phase-3 Feature: time-entry-templates-page (2026-05-23 08:13:58) + +- `08:13:58` **INFO** Description: UI-Page für TimeEntry-Templates CRUD +- `08:13:58` **INFO** Generating apps/web/src/pages/TimeEntryTemplates.tsx (TimeEntryTemplates-Page. Liste + Create-Form (name, description, proje…) +- `08:14:53` **INFO** wrote 6864 chars in 55.1s (attempt 1) +- `08:14:53` **INFO** Running tsc --noEmit on api… +- `08:14:55` **WARN** tsc errors: +src/index.ts(27,25): error TS2769: No overload matches this call. + Overload 1 of 3, '(plugin: FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. + Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. + Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTypeProvider>, opts: { ...; }, done: (err?: Error | undefined) => void): void'. + Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. + Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. + Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTy diff --git a/apps/web/src/pages/TimeEntryTemplates.tsx b/apps/web/src/pages/TimeEntryTemplates.tsx new file mode 100644 index 0000000..23f5880 --- /dev/null +++ b/apps/web/src/pages/TimeEntryTemplates.tsx @@ -0,0 +1,183 @@ +import { useState, useMemo } from "react" +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" +import { Plus, Trash2, Clock, FileText, Tag } from "lucide-react" +import { api } from "../lib/api" + +export default function TimeEntryTemplates() { + const queryClient = useQueryClient() + + const [name, setName] = useState("") + const [description, setDescription] = useState("") + const [projectId, setProjectId] = useState("") + const [duration, setDuration] = useState("") + + const { data: templates, isLoading, isError } = useQuery({ + queryKey: ["timeEntryTemplates"], + queryFn: () => api.listTimeEntryTemplates() + }) + + const { data: projects } = useQuery({ + queryKey: ["projects"], + queryFn: () => api.listProjects() + }) + + const createMutation = useMutation({ + mutationFn: (payload: { + name: string; + description: string; + projectId: string | null; + defaultDurationMinutes: number | null + }) => api.createTimeEntryTemplate(payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["timeEntryTemplates"] }) + setName("") + setDescription("") + setProjectId("") + setDuration("") + } + }) + + const deleteMutation = useMutation({ + mutationFn: (id: string) => api.deleteTimeEntryTemplate(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["timeEntryTemplates"] }) + } + }) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!name.trim()) return + + createMutation.mutate({ + name, + description, + projectId: projectId || null, + defaultDurationMinutes: duration ? parseInt(duration, 10) : null + }) + } + + if (isLoading) return
Loading templates...
+ if (isError) return
Error loading templates.
+ + return ( +
+
+

+ + Time Entry Templates +

+
+ +
+
+ Create New Template +
+ +
+ + setName(e.target.value)} + placeholder="e.g. Weekly Sync" + className="border rounded px-3 py-2 focus:ring-2 focus:ring-blue-500 outline-none" + /> +
+ +
+ + setDuration(e.target.value)} + placeholder="60" + className="border rounded px-3 py-2 focus:ring-2 focus:ring-blue-500 outline-none" + /> +
+ +
+ + +
+ +
+ + setDescription(e.target.value)} + placeholder="Brief description..." + className="border rounded px-3 py-2 focus:ring-2 focus:ring-blue-500 outline-none" + /> +
+ +
+ +
+
+ +
+ {templates?.length === 0 && ( +
+ No templates found. Create one above to speed up your time tracking. +
+ )} + + {templates?.map(template => ( +
+
+
+ +
+
+
{template.name}
+
+ {template.defaultDurationMinutes && ( + + {template.defaultDurationMinutes} min + + )} + {template.projectId && ( + + Project ID: {template.projectId} + + )} + {template.description && ( + {template.description} + )} +
+
+
+ + +
+ ))} +
+
+ ) +} \ No newline at end of file