diff --git a/.phase17-state.json b/.phase17-state.json index 7bb577c..4f0ef66 100644 --- a/.phase17-state.json +++ b/.phase17-state.json @@ -1,9 +1,10 @@ { "completed_features": [], - "current_feature": "customer-merge", + "current_feature": "smart-filter-suggestions", "started_at": "2026-05-23T07:18:43.778897", "attempted_features": [ "calendar-month-view", - "batch-rename-projects" + "batch-rename-projects", + "customer-merge" ] } \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index d6bf5fb..d67493c 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -2071,3 +2071,22 @@ 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 +- `07:24:24` **INFO** Committed feature customer-merge +- `07:24:25` **INFO** Pushed: rc=0 + +## Phase-3 Feature: smart-filter-suggestions (2026-05-23 07:24:25) + +- `07:24:25` **INFO** Description: Saved-Views-Vorschläge basierend auf häufig benutzten Filters +- `07:24:25` **INFO** Generating apps/web/src/components/SmartFilters.tsx (SmartFilters-Component. Zeigt 3-4 vorgeschlagene Filter-Buttons: 'Dies…) +- `07:24:46` **INFO** wrote 2432 chars in 21.8s (attempt 1) +- `07:24:46` **INFO** Generating apps/web/src/pages/TimeEntries.tsx (ERWEITERT — behalte alles. Füge …) +- `07:26:23` **INFO** wrote 12439 chars in 96.5s (attempt 1) +- `07:26:23` **INFO** Running tsc --noEmit on api… +- `07:26: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/SmartFilters.tsx b/apps/web/src/components/SmartFilters.tsx new file mode 100644 index 0000000..a9dc939 --- /dev/null +++ b/apps/web/src/components/SmartFilters.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { cn } from '@emberclone/shared/utils'; + +export type SmartFilterValue = { + startDate?: Date; + endDate?: Date; + projectId?: string | null; + onlyWithoutProject?: boolean; +}; + +interface SmartFiltersProps { + activeFilterId?: string; + onApply: (filter: SmartFilterValue) => void; + className?: string; +} + +const FILTERS = [ + { + id: 'today', + label: 'Heute', + apply: (): SmartFilterValue => { + const start = new Date(); + start.setHours(0, 0, 0, 0); + const end = new Date(); + end.setHours(23, 59, 59, 999); + return { startDate: start, endDate: end }; + }, + }, + { + id: 'this-week', + label: 'Diese Woche', + apply: (): SmartFilterValue => { + const now = new Date(); + const day = now.getDay(); + const diff = now.getDate() - day + (day === 0 ? -6 : 1); + const start = new Date(now.setDate(diff)); + start.setHours(0, 0, 0, 0); + + const end = new Date(); + end.setHours(23, 59, 59, 999); + return { startDate: start, endDate: end }; + }, + }, + { + id: 'last-month', + label: 'Letzter Monat', + apply: (): SmartFilterValue => { + const end = new Date(); + const start = new Date(); + start.setMonth(start.getMonth() - 1); + return { startDate: start, endDate: end }; + }, + }, + { + id: 'no-project', + label: 'Nur ohne Projekt', + apply: (): SmartFilterValue => ({ onlyWithoutProject: true }), + }, +]; + +export function SmartFilters({ activeFilterId, onApply, className }: SmartFiltersProps) { + return ( +
+ {FILTERS.map((filter) => ( + + ))} + +
+ ); +} \ No newline at end of file diff --git a/apps/web/src/pages/TimeEntries.tsx b/apps/web/src/pages/TimeEntries.tsx index 72df3f0..82ed07f 100644 --- a/apps/web/src/pages/TimeEntries.tsx +++ b/apps/web/src/pages/TimeEntries.tsx @@ -4,6 +4,7 @@ import { api } from "../lib/api" import { EmptyState } from "../components/EmptyState" import { LoadingSpinner } from "../components/LoadingSpinner" import { SuggestionInput } from "../components/SuggestionInput" +import { SmartFilters } from "../components/SmartFilters" import type { TimeEntryInsert, SavedView } from "@emberclone/shared" function renderSimpleMarkdown(text: string | null) { @@ -124,20 +125,12 @@ export default function TimeEntries() { }) } - const handleExport = () => { - const params = new URLSearchParams() - if (filters.from) params.append("from", filters.from) - if (filters.to) params.append("to", filters.to) - window.location.href = `/api/time-entries/export.csv?${params.toString()}` - } - - if (isLoading) return
if (isError) return
Error loading time entries.
return ( -
-
-

Time Entries

+
+
+

Time Entries

-
+
- setFormData(prev => ({ ...prev, description: val }))} + onChange={v => setFormData(prev => ({ ...prev, description: v }))} suggestions={descriptionSuggestions} - placeholder="What are you working on?" + placeholder="What did you work on?" />
- +
- + {createMutation.isPending ? "Saving..." : "Add Entry"}
- +