diff --git a/.phase11-state.json b/.phase11-state.json index d3ec54f..e94acf0 100644 --- a/.phase11-state.json +++ b/.phase11-state.json @@ -1,8 +1,9 @@ { "completed_features": [], - "current_feature": "time-entry-csv-import", + "current_feature": "customer-archive", "started_at": "2026-05-23T06:21:46.924268", "attempted_features": [ - "onboarding-tour" + "onboarding-tour", + "time-entry-csv-import" ] } \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index 83dcfa4..21aeb18 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -1344,3 +1344,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 +- `06:26:17` **INFO** Committed feature time-entry-csv-import +- `06:26:17` **INFO** Pushed: rc=0 + +## Phase-3 Feature: customer-archive (2026-05-23 06:26:17) + +- `06:26:17` **INFO** Description: Soft-archive von Customers (toggle active=false) + Filter +- `06:26:17` **INFO** Generating apps/web/src/pages/Customers.tsx (ERWEITERT — füge Archive-Button (statt Delete) pro Customer (PATCH act…) +- `06:27:27` **INFO** wrote 8791 chars in 70.1s (attempt 1) +- `06:27:27` **INFO** Running tsc --noEmit on api… +- `06:27:29` **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/Customers.tsx b/apps/web/src/pages/Customers.tsx index 72530db..dffb779 100644 --- a/apps/web/src/pages/Customers.tsx +++ b/apps/web/src/pages/Customers.tsx @@ -7,6 +7,7 @@ export default function Customers() { const [name, setName] = useState("") const [tags, setTags] = useState("") const [filterTag, setFilterTag] = useState("") + const [showArchived, setShowArchived] = useState(false) const fileInputRef = useRef(null) const { data: customers, isLoading, isError } = useQuery({ @@ -23,8 +24,8 @@ export default function Customers() { } }) - const deleteMutation = useMutation({ - mutationFn: (id: string) => api.deleteCustomer(id), + const archiveMutation = useMutation({ + mutationFn: (id: string) => api.updateCustomer(id, { active: false }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["customers"] }) } @@ -43,12 +44,16 @@ export default function Customers() { const filteredCustomers = useMemo(() => { if (!customers) return [] - if (!filterTag.trim()) return customers - const search = filterTag.toLowerCase() - return customers.filter(c => - c.tags?.some(t => t.toLowerCase().includes(search)) - ) - }, [customers, filterTag]) + + return customers.filter(c => { + const matchesTag = !filterTag.trim() || + c.tags?.some(t => t.toLowerCase().includes(filterTag.toLowerCase())) + + const matchesActive = showArchived || c.active !== false + + return matchesTag && matchesActive + }) + }, [customers, filterTag, showArchived]) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() @@ -116,79 +121,93 @@ export default function Customers() { className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" value={tags} onChange={(e) => setTags(e.target.value)} - placeholder="VIP, Enterprise, Lead..." + placeholder="e.g. VIP, Enterprise, Lead" />
-
- Filter by tag: - setFilterTag(e.target.value)} - placeholder="Search tags..." - /> +
+
+ setFilterTag(e.target.value)} + /> +
+
- - - - + + + + - {filteredCustomers.map((customer) => ( - - - - - - ))} - {filteredCustomers.length === 0 && ( + {filteredCustomers.length === 0 ? ( - + ) : ( + filteredCustomers.map((customer) => ( + + + + + + )) )}
CustomerTagsActions
CustomerTagsActions
{customer.name} -
- {customer.tags?.map((tag) => ( - - {tag} - - )) || No tags} -
-
- -
- No customers found matching the filter. + + No customers found.
+ {customer.name} + {customer.active === false && ( + Archived + )} + +
+ {customer.tags?.map(tag => ( + + {tag} + + ))} +
+
+ {customer.active !== false && ( + + )} +