Files
clienthub/src/components/admin/TimerCell.tsx
T
simone e6a9774cfe 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>
2026-05-22 12:13:31 +02:00

95 lines
2.9 KiB
TypeScript

"use client";
import { useState, useEffect, useTransition } from "react";
import { useRouter } from "next/navigation";
import { startTimer, startTimerForClient, stopTimer } from "@/app/admin/timer-actions";
function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
return `${m}:${String(s).padStart(2, "0")}`;
}
export function TimerCell({
clientId,
projectId,
activeEntryId,
activeStartedAt,
totalTrackedSeconds,
}: {
clientId: string;
projectId?: string;
activeEntryId: string | null;
activeStartedAt: Date | null;
totalTrackedSeconds: number;
}) {
const router = useRouter();
const [, startTransition] = useTransition();
const isRunning = activeEntryId !== null;
// Live elapsed counter
const [elapsed, setElapsed] = useState(() => {
if (!activeStartedAt) return 0;
return Math.round((Date.now() - new Date(activeStartedAt).getTime()) / 1000);
});
useEffect(() => {
if (!isRunning) { setElapsed(0); return; }
const start = activeStartedAt ? new Date(activeStartedAt).getTime() : Date.now();
const tick = () => setElapsed(Math.round((Date.now() - start) / 1000));
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [isRunning, activeStartedAt]);
function handleToggle() {
startTransition(async () => {
if (isRunning && activeEntryId) {
await stopTimer(activeEntryId);
} else if (projectId) {
await startTimer(projectId);
} else {
await startTimerForClient(clientId);
}
router.refresh();
});
}
const displayTotal = formatDuration(totalTrackedSeconds + (isRunning ? elapsed : 0));
return (
<div className="flex items-center gap-2">
<button
onClick={handleToggle}
title={isRunning ? "Ferma timer" : "Avvia timer"}
className={`w-7 h-7 rounded-full flex items-center justify-center transition-colors shrink-0 ${
isRunning
? "bg-red-100 text-red-600 hover:bg-red-200"
: "bg-[#1A463C]/10 text-[#1A463C] hover:bg-[#1A463C]/20"
}`}
>
{isRunning ? (
// Stop square
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 16 16">
<rect x="3" y="3" width="10" height="10" rx="1.5" />
</svg>
) : (
// Play triangle
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 16 16">
<path d="M4 3.5v9l9-4.5-9-4.5z" />
</svg>
)}
</button>
<span
className={`text-xs tabular-nums font-mono ${
isRunning ? "text-red-600 font-semibold" : "text-[#71717a]"
}`}
>
{isRunning ? formatDuration(elapsed) : displayTotal}
</span>
</div>
);
}