diff --git a/.phase24-state.json b/.phase24-state.json index a3e3782..f76c727 100644 --- a/.phase24-state.json +++ b/.phase24-state.json @@ -1,10 +1,11 @@ { "completed_features": [], - "current_feature": "project-archive-icon", + "current_feature": "export-improvements", "started_at": "2026-05-23T08:33:42.059540", "attempted_features": [ "notification-bell", "workspace-switcher-stub", - "billing-history-table" + "billing-history-table", + "project-archive-icon" ] } \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index 68889af..4242627 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -2855,3 +2855,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:38:18` **INFO** Committed feature project-archive-icon +- `08:38:18` **INFO** Pushed: rc=0 + +## Phase-3 Feature: export-improvements (2026-05-23 08:38:18) + +- `08:38:18` **INFO** Description: Export-Button auch in Customers + Projects +- `08:38:18` **INFO** Generating apps/web/src/pages/Customers.tsx (ERWEITERT — füge 'CSV exportieren'-Button oben rechts. Generiert CSV i…) +- `08:40:05` **INFO** wrote 12617 chars in 106.8s (attempt 1) +- `08:40:05` **INFO** Running tsc --noEmit on api… +- `08:40:07` **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 af4aa5e..92f1a8a 100644 --- a/apps/web/src/pages/Customers.tsx +++ b/apps/web/src/pages/Customers.tsx @@ -1,6 +1,6 @@ import { useState, useRef, useMemo } from "react" import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" -import { Star, Merge, X, Upload, Plus, Archive, Trash2 } from "lucide-react" +import { Star, Merge, X, Upload, Plus, Archive, Trash2, Download } from "lucide-react" import { api } from "../lib/api" const COLOR_PALETTE = [ @@ -113,114 +113,129 @@ export default function Customers() { } } + const handleExportCsv = () => { + if (!customers) return + const headers = ["id", "name", "active", "createdAt"] + const rows = customers.map(c => [ + c.id, + `"${(c.name || "").replace(/"/g, '""')}"`, + c.active, + c.createdAt + ].join(",")) + + const csvContent = [headers.join(","), ...rows].join("\n") + const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }) + const url = URL.createObjectURL(blob) + const link = document.createElement("a") + link.setAttribute("href", url) + link.setAttribute("download", `customers_export_${new Date().toISOString().split('T')[0]}.csv`) + link.style.visibility = "hidden" + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } + if (isLoading) return
Loading customers...
if (isError) return
Error loading customers.
return ( -
-
-
-

Customers

-

Manage your client database

-
+
+
+

Customers

- + +
-
+
-
-
+ +
setName(e.target.value)} - placeholder="Customer Name" - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" + placeholder="Customer name..." + className="px-3 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 outline-none" />
-
+
setTags(e.target.value)} - placeholder="VIP, Lead, Tech..." - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none" + placeholder="VIP, Lead, etc." + className="px-3 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 outline-none" />
-
-
-
- setFilterTag(e.target.value)} - placeholder="Filter by tag..." - className="pl-3 pr-10 py-2 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 outline-none w-64" - /> - {filterTag && ( - - )} -
- +
+
+ setFilterTag(e.target.value)} + placeholder="Filter by tag..." + className="w-full pl-3 pr-10 py-2 border rounded-md focus:ring-2 focus:ring-blue-500 outline-none" + />
+
{filteredAndSortedCustomers.map(customer => (
-
+
-
- {customer.name} - {!customer.active && Archived} -
+
{customer.name}
{customer.tags?.map(tag => ( - + {tag} ))} @@ -230,71 +245,72 @@ export default function Customers() {
))} {filteredAndSortedCustomers.length === 0 && ( -
- No customers found matching your criteria. +
+ No customers found.
)}
{mergeTarget && ( -
-
-

Merge Customer

-

- Merge {customers?.find(c => c.id === mergeTarget.sourceId)?.name} into another customer. All tags and associations will be moved. +

+
+
+

Merge Customer

+ +
+

+ Merge {customers?.find(c => c.id === mergeTarget.sourceId)?.name} into another customer.

-
- - -
-
- - -
+ +
)}