fix(04-05): three post-deploy bug fixes

- Admin client list: remove Timer column, rename Acconto→Importo incassato
  and Saldo→Importo da saldare (calculated from payment amounts, not badges)
- Project workspace timer: pass projectId prop to TimerCell so it calls
  startTimer(projectId) directly instead of startTimerForClient(projectId)
  which was failing with "nessun progetto trovato"
- Public client page: pass client.token (real DB token) not the URL path
  segment (which may be a slug) to ClientDashboard → fixes approve/chat APIs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 12:13:31 +02:00
parent 4d8678b1a3
commit e6a9774cfe
5 changed files with 31 additions and 34 deletions
+2 -3
View File
@@ -49,9 +49,8 @@ export default async function AdminDashboard({
<tr> <tr>
<th className="text-left py-3 px-4 font-medium text-[#71717a]">Cliente</th> <th className="text-left py-3 px-4 font-medium text-[#71717a]">Cliente</th>
<th className="text-left py-3 px-4 font-medium text-[#71717a]">LTV</th> <th className="text-left py-3 px-4 font-medium text-[#71717a]">LTV</th>
<th className="text-left py-3 px-4 font-medium text-[#71717a]">Acconto</th> <th className="text-left py-3 px-4 font-medium text-[#71717a]">Importo incassato</th>
<th className="text-left py-3 px-4 font-medium text-[#71717a]">Saldo</th> <th className="text-left py-3 px-4 font-medium text-[#71717a]">Importo da saldare</th>
<th className="text-left py-3 px-4 font-medium text-[#71717a]">Timer</th>
<th className="text-left py-3 px-4 font-medium text-[#71717a]">Link</th> <th className="text-left py-3 px-4 font-medium text-[#71717a]">Link</th>
</tr> </tr>
</thead> </thead>
+2 -2
View File
@@ -113,7 +113,7 @@ export default async function ClientPage({
return ( return (
<ClientDashboard <ClientDashboard
view={projectViewToClientView(client, view)} view={projectViewToClientView(client, view)}
token={token} token={client.token}
comments={view.comments as unknown as Comment[]} comments={view.comments as unknown as Comment[]}
/> />
); );
@@ -155,7 +155,7 @@ export default async function ClientPage({
{view ? ( {view ? (
<ClientDashboard <ClientDashboard
view={projectViewToClientView(client, view)} view={projectViewToClientView(client, view)}
token={token} token={client.token}
comments={view.comments as unknown as Comment[]} comments={view.comments as unknown as Comment[]}
/> />
) : ( ) : (
+21 -28
View File
@@ -1,17 +1,18 @@
import Link from "next/link"; import Link from "next/link";
import { Badge } from "@/components/ui/badge";
import { TimerCell } from "@/components/admin/TimerCell";
import type { ClientWithPayments } from "@/lib/admin-queries"; import type { ClientWithPayments } from "@/lib/admin-queries";
const statusConfig: Record<string, { label: string; className: string }> = { function fmt(amount: number) {
da_saldare: { label: "Da saldare", className: "bg-red-100 text-red-700 border-transparent" }, return `${amount.toLocaleString("it-IT", { minimumFractionDigits: 2 })}`;
inviata: { label: "Inviata", className: "bg-[#DEF168]/30 text-[#1A463C] border-transparent" }, }
saldato: { label: "Saldato", className: "bg-[#1A463C]/10 text-[#1A463C] border-transparent font-medium" },
};
export function ClientRow({ client }: { client: ClientWithPayments }) { export function ClientRow({ client }: { client: ClientWithPayments }) {
const acconto = client.payments.find((p) => p.label.includes("Acconto")); const incassato = client.payments
const saldo = client.payments.find((p) => p.label.includes("Saldo")); .filter((p) => p.status === "saldato")
.reduce((sum, p) => sum + parseFloat(p.amount || "0"), 0);
const daSaldare = client.payments
.filter((p) => p.status !== "saldato")
.reduce((sum, p) => sum + parseFloat(p.amount || "0"), 0);
return ( return (
<tr className={`border-b border-[#f4f4f5] hover:bg-[#f9f9f9] transition-colors ${client.archived ? "opacity-60" : ""}`}> <tr className={`border-b border-[#f4f4f5] hover:bg-[#f9f9f9] transition-colors ${client.archived ? "opacity-60" : ""}`}>
@@ -34,30 +35,22 @@ export function ClientRow({ client }: { client: ClientWithPayments }) {
)} )}
</td> </td>
<td className="py-3 px-4 text-sm text-[#1a1a1a]"> <td className="py-3 px-4 text-sm text-[#1a1a1a]">
{parseFloat(client.ltv).toLocaleString("it-IT", { minimumFractionDigits: 2 })} {fmt(parseFloat(client.ltv))}
</td> </td>
<td className="py-3 px-4"> <td className="py-3 px-4 text-sm text-[#1a1a1a]">
{acconto && ( {incassato > 0 ? (
<Badge className={statusConfig[acconto.status]?.className ?? "border-transparent bg-[#f4f4f5] text-[#71717a]"}> <span className="text-[#16a34a] font-medium">{fmt(incassato)}</span>
Acconto: {statusConfig[acconto.status]?.label ?? acconto.status} ) : (
</Badge> <span className="text-[#71717a]"></span>
)} )}
</td> </td>
<td className="py-3 px-4"> <td className="py-3 px-4 text-sm">
{saldo && ( {daSaldare > 0 ? (
<Badge className={statusConfig[saldo.status]?.className ?? "border-transparent bg-[#f4f4f5] text-[#71717a]"}> <span className="text-[#1a1a1a]">{fmt(daSaldare)}</span>
Saldo: {statusConfig[saldo.status]?.label ?? saldo.status} ) : (
</Badge> <span className="text-[#71717a]"></span>
)} )}
</td> </td>
<td className="py-3 px-4">
<TimerCell
clientId={client.id}
activeEntryId={client.activeTimerEntryId}
activeStartedAt={client.activeTimerStartedAt}
totalTrackedSeconds={client.totalTrackedSeconds}
/>
</td>
<td className="py-3 px-4"> <td className="py-3 px-4">
<a <a
href={`/client/${client.token}`} href={`/client/${client.token}`}
+5 -1
View File
@@ -2,7 +2,7 @@
import { useState, useEffect, useTransition } from "react"; import { useState, useEffect, useTransition } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { startTimerForClient, stopTimer } from "@/app/admin/timer-actions"; import { startTimer, startTimerForClient, stopTimer } from "@/app/admin/timer-actions";
function formatDuration(seconds: number): string { function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600); const h = Math.floor(seconds / 3600);
@@ -14,11 +14,13 @@ function formatDuration(seconds: number): string {
export function TimerCell({ export function TimerCell({
clientId, clientId,
projectId,
activeEntryId, activeEntryId,
activeStartedAt, activeStartedAt,
totalTrackedSeconds, totalTrackedSeconds,
}: { }: {
clientId: string; clientId: string;
projectId?: string;
activeEntryId: string | null; activeEntryId: string | null;
activeStartedAt: Date | null; activeStartedAt: Date | null;
totalTrackedSeconds: number; totalTrackedSeconds: number;
@@ -46,6 +48,8 @@ export function TimerCell({
startTransition(async () => { startTransition(async () => {
if (isRunning && activeEntryId) { if (isRunning && activeEntryId) {
await stopTimer(activeEntryId); await stopTimer(activeEntryId);
} else if (projectId) {
await startTimer(projectId);
} else { } else {
await startTimerForClient(clientId); await startTimerForClient(clientId);
} }
+1
View File
@@ -26,6 +26,7 @@ export function TimerTab({
<h3 className="font-medium text-[#1a1a1a] mb-4">Timer</h3> <h3 className="font-medium text-[#1a1a1a] mb-4">Timer</h3>
<TimerCell <TimerCell
clientId={projectId} clientId={projectId}
projectId={projectId}
activeEntryId={activeTimerEntryId} activeEntryId={activeTimerEntryId}
activeStartedAt={activeTimerStartedAt} activeStartedAt={activeTimerStartedAt}
totalTrackedSeconds={totalTrackedSeconds} totalTrackedSeconds={totalTrackedSeconds}