Files
kandev/apps/web/components/task/task-item-stats-row.tsx
T
2026-08-18 10:15:42 +01:00

118 lines
4.0 KiB
TypeScript

"use client";
import { IconClockHour4, IconMail } from "@tabler/icons-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
import { useTranslation } from "react-i18next";
import { useAppStore } from "@/components/state-provider";
import { isDebugUI } from "@/lib/config";
import type { SessionPollMode } from "@/lib/state/slices/session-runtime/types";
import { cn, formatRelativeTime } from "@/lib/utils";
import type { WipQueueStatus } from "@/lib/kanban/wip-queue";
/** Debug-overlay-only poll-mode legend. Labels resolve through `t()` so the
* pseudo-locale stays complete; the row itself is gated behind `isDebugUI()`. */
const POLL_MODE_CONFIG: Record<
SessionPollMode,
{ letter: string; color: string; labelKey: string }
> = {
fast: { letter: "F", color: "text-emerald-500", labelKey: "sidebar:pollModeFastLabel" },
slow: { letter: "S", color: "text-yellow-500", labelKey: "sidebar:pollModeSlowLabel" },
paused: {
letter: "P",
color: "text-muted-foreground/40",
labelKey: "sidebar:pollModePausedLabel",
},
};
/**
* The sidebar row's metadata line: relative last-update time, PR number,
* queued-prompt mail badge, WIP queue icon, and (debug UI only) the session
* poll-mode letter.
*/
export function TaskItemStatsRow({
updatedAt,
prInfo,
primarySessionId,
queuedCount,
wipQueue,
}: {
updatedAt?: string;
prInfo?: { number: number; state: string; aggregateState?: string };
primarySessionId?: string | null;
queuedCount?: number;
wipQueue?: WipQueueStatus;
}) {
const { t } = useTranslation();
const pollMode = useAppStore((s) =>
isDebugUI() && primarySessionId
? (s.sessionPollMode.bySessionId[primarySessionId] ?? null)
: null,
);
if (!updatedAt && !prInfo && !pollMode && !queuedCount && !wipQueue) return null;
const modeConfig = pollMode ? POLL_MODE_CONFIG[pollMode] : null;
const modeLabel = modeConfig ? t(modeConfig.labelKey) : "";
const wipQueueLabel = wipQueue
? t("sidebar:wipQueuePosition", {
position: wipQueue.position,
total: wipQueue.total,
step: wipQueue.destinationTitle,
})
: "";
return (
<span className="flex items-center gap-1.5 text-[11px]">
{updatedAt && (
<span
data-testid="sidebar-task-time"
data-time-value={updatedAt}
className="text-muted-foreground/50"
>
{formatRelativeTime(updatedAt)}
</span>
)}
{prInfo && <span className="text-muted-foreground/50">#{prInfo.number}</span>}
{queuedCount ? (
<span
data-testid="sidebar-task-queued-count"
aria-label={t("sidebar:queuedPromptCount", { count: queuedCount })}
title={t("sidebar:queuedPromptCount", { count: queuedCount })}
className="inline-flex shrink-0 items-center gap-0.5 text-muted-foreground/60"
>
<IconMail className="h-3 w-3" aria-hidden="true" />
{queuedCount}
</span>
) : null}
{wipQueue ? (
<Tooltip>
<TooltipTrigger asChild>
<span
data-testid="sidebar-task-wip-queue"
tabIndex={0}
aria-label={wipQueueLabel}
title={wipQueueLabel}
className="inline-flex shrink-0 items-center text-muted-foreground/60 outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
<IconClockHour4 className="h-3.5 w-3.5" aria-hidden="true" />
</span>
</TooltipTrigger>
<TooltipContent side="right">{wipQueueLabel}</TooltipContent>
</Tooltip>
) : null}
{modeConfig && (
<Tooltip>
<TooltipTrigger asChild>
<span className={cn("font-mono text-[10px] font-semibold", modeConfig.color)}>
{modeConfig.letter}
</span>
</TooltipTrigger>
<TooltipContent side="right">
{t("task:gitPollMode", { mode: pollMode, label: modeLabel })}
</TooltipContent>
</Tooltip>
)}
</span>
);
}