almost_done

This commit is contained in:
Marcelo
2026-04-30 16:59:42 +00:00
parent 5e7ddaa0db
commit b2214ec46f
23 changed files with 662 additions and 196 deletions

View File

@@ -4,6 +4,7 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect, useState, type KeyboardEvent } from "react";
import { useI18n } from "@/lib/i18n/useI18n";
import { RECAP_HEARTBEAT_STALE_MS } from "@/lib/recap/recapUiConstants";
type MachineRow = {
id: string;
@@ -20,6 +21,7 @@ type MachineRow = {
};
};
const LIVE_REFRESH_MS = 5000;
const OFFLINE_MS = RECAP_HEARTBEAT_STALE_MS;
function secondsAgo(ts: string | undefined, locale: string, fallback: string) {
if (!ts) return fallback;
@@ -31,7 +33,7 @@ function secondsAgo(ts: string | undefined, locale: string, fallback: string) {
function isOffline(ts?: string) {
if (!ts) return true;
return Date.now() - new Date(ts).getTime() > 10 * 60 * 1000; // 10 min (sincronizado con RECAP_HEARTBEAT_STALE_MS)
return Date.now() - new Date(ts).getTime() > OFFLINE_MS;
}
function normalizeStatus(status?: string) {

View File

@@ -21,6 +21,7 @@ import {
import { useI18n } from "@/lib/i18n/useI18n";
import { useScreenlessMode } from "@/lib/ui/screenlessMode";
import type { RecapTimelineResponse, RecapTimelineSegment } from "@/lib/recap/types";
import { RECAP_HEARTBEAT_STALE_MS } from "@/lib/recap/recapUiConstants";
import {
computeWidths,
formatDuration,
@@ -375,6 +376,7 @@ function getMinuteFlooredOneHourRange(referenceMs = Date.now()) {
function MachineActivityTimeline({ machineId, locale, t }: MachineActivityTimelineProps) {
const [timeline, setTimeline] = useState<RecapTimelineResponse | null>(null);
const [timelineLoading, setTimelineLoading] = useState(true);
const [showWindowInfo, setShowWindowInfo] = useState(false);
const timelineHashRef = useRef("");
useEffect(() => {
@@ -444,7 +446,13 @@ function MachineActivityTimeline({ machineId, locale, t }: MachineActivityTimeli
<div className="text-sm font-semibold text-white">{t("machine.detail.activity.title")}</div>
<div className="mt-1 text-xs text-zinc-400">{t("machine.detail.activity.subtitle")}</div>
</div>
<div className="text-xs text-zinc-400">1h</div>
<button
type="button"
onClick={() => setShowWindowInfo(true)}
className="rounded-md border border-white/20 px-2 py-1 text-xs text-zinc-300 hover:border-white/40 hover:text-white"
>
{t("machine.detail.activity.windowBadge")}
</button>
</div>
<div className="mt-4 flex flex-wrap items-center gap-4 text-xs text-zinc-300">
@@ -500,6 +508,31 @@ function MachineActivityTimeline({ machineId, locale, t }: MachineActivityTimeli
)}
</div>
</div>
{showWindowInfo ? (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"
role="dialog"
aria-modal="true"
aria-labelledby="machine-timeline-window-title"
>
<div className="w-full max-w-sm rounded-2xl border border-white/15 bg-zinc-950 p-5">
<h3 id="machine-timeline-window-title" className="text-sm font-semibold text-white">
{t("machine.detail.activity.windowModalTitle")}
</h3>
<p className="mt-2 text-sm text-zinc-300">{t("machine.detail.activity.windowModalBody")}</p>
<div className="mt-4 flex justify-end">
<button
type="button"
onClick={() => setShowWindowInfo(false)}
className="rounded-lg border border-white/20 px-3 py-1.5 text-sm text-zinc-200 hover:border-white/40 hover:text-white"
>
{t("common.close")}
</button>
</div>
</div>
</div>
) : null}
</div>
);
}
@@ -800,7 +833,7 @@ export default function MachineDetailClient() {
function isOffline(ts?: string) {
if (!ts) return true;
return Date.now() - new Date(ts).getTime() > 30000;
return Date.now() - new Date(ts).getTime() > RECAP_HEARTBEAT_STALE_MS;
}
function normalizeStatus(status?: string) {