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:
@@ -49,9 +49,8 @@ export default async function AdminDashboard({
|
||||
<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]">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]">Saldo</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]">Importo incassato</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]">Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -113,7 +113,7 @@ export default async function ClientPage({
|
||||
return (
|
||||
<ClientDashboard
|
||||
view={projectViewToClientView(client, view)}
|
||||
token={token}
|
||||
token={client.token}
|
||||
comments={view.comments as unknown as Comment[]}
|
||||
/>
|
||||
);
|
||||
@@ -155,7 +155,7 @@ export default async function ClientPage({
|
||||
{view ? (
|
||||
<ClientDashboard
|
||||
view={projectViewToClientView(client, view)}
|
||||
token={token}
|
||||
token={client.token}
|
||||
comments={view.comments as unknown as Comment[]}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import Link from "next/link";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { TimerCell } from "@/components/admin/TimerCell";
|
||||
import type { ClientWithPayments } from "@/lib/admin-queries";
|
||||
|
||||
const statusConfig: Record<string, { label: string; className: string }> = {
|
||||
da_saldare: { label: "Da saldare", className: "bg-red-100 text-red-700 border-transparent" },
|
||||
inviata: { label: "Inviata", className: "bg-[#DEF168]/30 text-[#1A463C] border-transparent" },
|
||||
saldato: { label: "Saldato", className: "bg-[#1A463C]/10 text-[#1A463C] border-transparent font-medium" },
|
||||
};
|
||||
function fmt(amount: number) {
|
||||
return `€${amount.toLocaleString("it-IT", { minimumFractionDigits: 2 })}`;
|
||||
}
|
||||
|
||||
export function ClientRow({ client }: { client: ClientWithPayments }) {
|
||||
const acconto = client.payments.find((p) => p.label.includes("Acconto"));
|
||||
const saldo = client.payments.find((p) => p.label.includes("Saldo"));
|
||||
const incassato = client.payments
|
||||
.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 (
|
||||
<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 className="py-3 px-4 text-sm text-[#1a1a1a]">
|
||||
€{parseFloat(client.ltv).toLocaleString("it-IT", { minimumFractionDigits: 2 })}
|
||||
{fmt(parseFloat(client.ltv))}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{acconto && (
|
||||
<Badge className={statusConfig[acconto.status]?.className ?? "border-transparent bg-[#f4f4f5] text-[#71717a]"}>
|
||||
Acconto: {statusConfig[acconto.status]?.label ?? acconto.status}
|
||||
</Badge>
|
||||
<td className="py-3 px-4 text-sm text-[#1a1a1a]">
|
||||
{incassato > 0 ? (
|
||||
<span className="text-[#16a34a] font-medium">{fmt(incassato)}</span>
|
||||
) : (
|
||||
<span className="text-[#71717a]">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{saldo && (
|
||||
<Badge className={statusConfig[saldo.status]?.className ?? "border-transparent bg-[#f4f4f5] text-[#71717a]"}>
|
||||
Saldo: {statusConfig[saldo.status]?.label ?? saldo.status}
|
||||
</Badge>
|
||||
<td className="py-3 px-4 text-sm">
|
||||
{daSaldare > 0 ? (
|
||||
<span className="text-[#1a1a1a]">{fmt(daSaldare)}</span>
|
||||
) : (
|
||||
<span className="text-[#71717a]">—</span>
|
||||
)}
|
||||
</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">
|
||||
<a
|
||||
href={`/client/${client.token}`}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useTransition } from "react";
|
||||
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 {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
@@ -14,11 +14,13 @@ function formatDuration(seconds: number): string {
|
||||
|
||||
export function TimerCell({
|
||||
clientId,
|
||||
projectId,
|
||||
activeEntryId,
|
||||
activeStartedAt,
|
||||
totalTrackedSeconds,
|
||||
}: {
|
||||
clientId: string;
|
||||
projectId?: string;
|
||||
activeEntryId: string | null;
|
||||
activeStartedAt: Date | null;
|
||||
totalTrackedSeconds: number;
|
||||
@@ -46,6 +48,8 @@ export function TimerCell({
|
||||
startTransition(async () => {
|
||||
if (isRunning && activeEntryId) {
|
||||
await stopTimer(activeEntryId);
|
||||
} else if (projectId) {
|
||||
await startTimer(projectId);
|
||||
} else {
|
||||
await startTimerForClient(clientId);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export function TimerTab({
|
||||
<h3 className="font-medium text-[#1a1a1a] mb-4">Timer</h3>
|
||||
<TimerCell
|
||||
clientId={projectId}
|
||||
projectId={projectId}
|
||||
activeEntryId={activeTimerEntryId}
|
||||
activeStartedAt={activeTimerStartedAt}
|
||||
totalTrackedSeconds={totalTrackedSeconds}
|
||||
|
||||
Reference in New Issue
Block a user