import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Clock, Plus, Loader2, ShieldCheck, ShieldAlert, X } from "lucide-react"; import { listWorklogs, createWorklog, type Worklog } from "../api/worklogs"; import { useAuth } from "../context/AuthContext"; interface WorklogTimelineProps { entityType: string; entityId: string; /** When true, hides the Log Time button and form (read-only display). */ readOnly?: boolean; } const activityColors: Record = { red_team: { bg: "bg-orange-900/30", text: "text-orange-400", icon: "border-orange-500/40" }, blue_validation: { bg: "bg-indigo-900/30", text: "text-indigo-400", icon: "border-indigo-500/40" }, purple_review: { bg: "bg-purple-900/30", text: "text-purple-400", icon: "border-purple-500/40" }, reporting: { bg: "bg-cyan-900/30", text: "text-cyan-400", icon: "border-cyan-500/40" }, execution: { bg: "bg-orange-900/30", text: "text-orange-400", icon: "border-orange-500/40" }, }; const defaultActivity = { bg: "bg-gray-800/50", text: "text-gray-400", icon: "border-gray-600" }; function formatDuration(seconds: number): string { if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); return m > 0 ? `${h}h ${m}m` : `${h}h`; } function formatDate(dateStr: string): string { return new Date(dateStr).toLocaleDateString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } export default function WorklogTimeline({ entityType, entityId, readOnly = false }: WorklogTimelineProps) { const queryClient = useQueryClient(); const { user } = useAuth(); const [showForm, setShowForm] = useState(false); const [form, setForm] = useState({ activity_type: "red_team", duration_minutes: "60", description: "", }); // ── Query ─────────────────────────────────────────────────────── const { data: worklogs = [], isLoading } = useQuery({ queryKey: ["worklogs", entityType, entityId], queryFn: () => listWorklogs({ entity_type: entityType, entity_id: entityId }), }); const createMutation = useMutation({ mutationFn: () => createWorklog({ entity_type: entityType, entity_id: entityId, activity_type: form.activity_type, started_at: new Date().toISOString(), duration_seconds: parseInt(form.duration_minutes, 10) * 60, description: form.description || undefined, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["worklogs", entityType, entityId], }); setShowForm(false); setForm({ activity_type: "red_team", duration_minutes: "60", description: "" }); }, }); // ── Total time ────────────────────────────────────────────────── const totalSeconds = worklogs.reduce( (sum: number, wl: Worklog) => sum + wl.duration_seconds, 0, ); return (

Time Log

{totalSeconds > 0 && ( Total: {formatDuration(totalSeconds)} )} {!readOnly && ( )}
{/* New worklog form — only in edit mode */} {!readOnly && showForm && (
setForm({ ...form, duration_minutes: e.target.value })} className="w-full rounded-lg border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-200 focus:border-cyan-500 focus:outline-none" />
setForm({ ...form, description: e.target.value })} placeholder="What did you work on?" className="w-full rounded-lg border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-200 placeholder-gray-500 focus:border-cyan-500 focus:outline-none" />
)} {/* Timeline */} {isLoading ? (
) : worklogs.length === 0 ? (

No time logged yet

) : (
{/* Vertical line */}
{worklogs.map((wl: Worklog) => { const style = activityColors[wl.activity_type] || defaultActivity; return (
{/* Dot */}
{/* Content */}
{wl.activity_type.replace(/_/g, " ")} {formatDuration(wl.duration_seconds)} {wl.tempo_synced && ( )}
{wl.description && (

{wl.description}

)}

{formatDate(wl.started_at)}

); })}
)}
); }