From 1150dd635fb05ec1c3a4bbcebab643f352d1bbe4 Mon Sep 17 00:00:00 2001 From: "Dennis (via Claude+Gemma)" Date: Sat, 23 May 2026 08:19:25 +0200 Subject: [PATCH] feat(popout-tracker): Active-Timer als Popup-Window (window.open) [tsc:fail] --- .phase22-state.json | 7 +- GENERATION_LOG.md | 17 +++ apps/web/src/components/ActiveTimer.tsx | 147 ++++++++++++++++++++++-- 3 files changed, 161 insertions(+), 10 deletions(-) diff --git a/.phase22-state.json b/.phase22-state.json index 37b8ee5..9a6c444 100644 --- a/.phase22-state.json +++ b/.phase22-state.json @@ -1,5 +1,8 @@ { "completed_features": [], - "current_feature": "voice-input-stub", - "started_at": "2026-05-23T08:17:40.778724" + "current_feature": "popout-tracker", + "started_at": "2026-05-23T08:17:40.778724", + "attempted_features": [ + "voice-input-stub" + ] } \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index eab1e81..4c336bc 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -2610,3 +2610,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:18:05` **INFO** Committed feature voice-input-stub +- `08:18:05` **INFO** Pushed: rc=0 + +## Phase-3 Feature: popout-tracker (2026-05-23 08:18:05) + +- `08:18:05` **INFO** Description: Active-Timer als Popup-Window (window.open) +- `08:18:05` **INFO** Generating apps/web/src/components/ActiveTimer.tsx (ERWEITERT — behalte bestehende ActiveTimer. Füge 'Pop out'-Button: win…) +- `08:19:23` **INFO** wrote 8854 chars in 78.1s (attempt 1) +- `08:19:23` **INFO** Running tsc --noEmit on api… +- `08:19:25` **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/components/ActiveTimer.tsx b/apps/web/src/components/ActiveTimer.tsx index 0e8233a..01831ab 100644 --- a/apps/web/src/components/ActiveTimer.tsx +++ b/apps/web/src/components/ActiveTimer.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { api } from '../lib/api' -import { Loader2, Play, Square } from 'lucide-react' +import { Loader2, Play, Square, ExternalLink } from 'lucide-react' export const ActiveTimer = () => { const queryClient = useQueryClient() @@ -56,6 +56,10 @@ export const ActiveTimer = () => { : `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` } + const handlePopout = () => { + window.open('/tracker-popout', 'tracker', 'width=320,height=220') + } + if (isLoading) return
if (runningEntry) { @@ -67,13 +71,22 @@ export const ActiveTimer = () => { {formatTime(elapsed)} - +
+ + +
) } @@ -100,4 +113,122 @@ export const ActiveTimer = () => { ) +} + +export const PopoutTrackerView = () => { + const queryClient = useQueryClient() + const [description, setDescription] = useState('') + const [isInputVisible, setIsInputVisible] = useState(false) + const [elapsed, setElapsed] = useState(0) + + const { data: runningEntry, isLoading } = useQuery({ + queryKey: ['running-entry'], + queryFn: () => api.getRunningTimeEntry(), + refetchInterval: 10000, + }) + + const startMutation = useMutation({ + mutationFn: (desc: string) => api.createTimeEntry({ description, startTime: new Date().toISOString() }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['running-entry'] }) + setIsInputVisible(false) + setDescription('') + }, + }) + + const stopMutation = useMutation({ + mutationFn: (id: string) => api.stopTimeEntry(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['running-entry'] }) + }, + }) + + useEffect(() => { + let interval: NodeJS.Timeout + if (runningEntry?.startTime) { + const update = () => { + const start = new Date(runningEntry.startTime).getTime() + const now = Date.now() + setElapsed(Math.floor((now - start) / 1000)) + } + update() + interval = setInterval(update, 1000) + } else { + setElapsed(0) + } + return () => clearInterval(interval) + }, [runningEntry]) + + const formatTime = (seconds: number) => { + const h = Math.floor(seconds / 3600) + const m = Math.floor((seconds % 3600) / 60) + const s = seconds % 60 + return h > 0 + ? `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` + : `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` + } + + if (isLoading) return
+ + return ( +
+ {runningEntry ? ( + <> +
+ {runningEntry.description || 'No description'} +
+
+ {formatTime(elapsed)} +
+ + + ) : ( + <> +
No active timer
+ {isInputVisible ? ( +
+ setDescription(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && startMutation.mutate(description)} + /> +
+ + +
+
+ ) : ( + + )} + + )} +
+ ) } \ No newline at end of file