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

@@ -1,6 +1,6 @@
"use client";
import type { RecapTimelineSegment } from "@/lib/recap/types";
import type { RecapRangeMode, RecapTimelineSegment } from "@/lib/recap/types";
import {
computeWidths,
formatDuration,
@@ -19,6 +19,7 @@ type Props = {
locale: string;
hasData?: boolean;
loading?: boolean;
rangeMode?: RecapRangeMode;
};
export default function RecapFullTimeline({
@@ -28,6 +29,7 @@ export default function RecapFullTimeline({
locale,
hasData = false,
loading = false,
rangeMode,
}: Props) {
const { t } = useI18n();
const startMs = new Date(rangeStart).getTime();
@@ -36,10 +38,19 @@ export default function RecapFullTimeline({
const normalized = hasData ? normalizeTimelineSegments(segments, startMs, endMs) : [];
const widths = computeWidths(normalized, totalMs, SEGMENT_MIN_WIDTH_PCT);
const rangeSuffix =
rangeMode === "shift"
? t("recap.range.shiftCurrent")
: rangeMode === "yesterday"
? t("recap.range.yesterday")
: rangeMode === "custom"
? t("recap.range.custom")
: t("recap.range.24h");
const titleText = `${t("recap.timeline.title")} · ${rangeSuffix}`;
return (
<div className="rounded-2xl border border-white/10 bg-black/40 p-4">
<div className="mb-3 text-sm font-semibold text-white">{t("recap.timeline.title")}</div>
<div className="mb-3 text-sm font-semibold text-white">{titleText}</div>
{loading ? (
<div className="overflow-x-auto">
<div className="min-w-[560px]">

View File

@@ -59,7 +59,7 @@ export default function RecapMachineCard({ machine, rangeStart, rangeEnd }: Prop
async function loadTimeline() {
try {
const res = await fetch(
`/api/recap/${machine.machineId}/timeline?range=24h&compact=1&maxSegments=30`,
`/api/recap/${machine.machineId}/timeline?range=24h&compact=1&maxSegments=60`,
{ cache: "no-store" }
);
const json = await res.json().catch(() => null);

View File

@@ -19,7 +19,7 @@ type Props = {
hasData?: boolean;
};
const MIN_SEGMENT_PCT = 1.5;
const MIN_SEGMENT_PCT = 0.5;
export default function RecapMiniTimeline({
rangeStart,

View File

@@ -1,7 +1,6 @@
"use client";
import { useI18n } from "@/lib/i18n/useI18n";
import { formatRecapProgressPercent } from "@/lib/recap/progressDisplay";
import type { RecapSkuRow } from "@/lib/recap/types";
type Props = {
@@ -9,7 +8,7 @@ type Props = {
};
export default function RecapProductionBySku({ rows }: Props) {
const { t, locale } = useI18n();
const { t } = useI18n();
return (
<div className="rounded-2xl border border-white/10 bg-black/40 p-4">
@@ -24,29 +23,21 @@ export default function RecapProductionBySku({ rows }: Props) {
<tr className="border-b border-white/10 text-left text-xs uppercase tracking-wide text-zinc-400">
<th className="py-2 pr-3">{t("recap.production.sku")}</th>
<th className="py-2 pr-3">{t("recap.production.good")}</th>
<th className="py-2 pr-3">{t("recap.production.scrap")}</th>
<th className="py-2 pr-3">{t("recap.production.target")}</th>
<th className="py-2">{t("recap.production.progress")}</th>
<th className="py-2">{t("recap.production.scrap")}</th>
</tr>
</thead>
<tbody>
{rows.slice(0, 10).map((row) => {
const progress =
row.progressPct == null ? "—" : formatRecapProgressPercent(row.progressPct, locale);
return (
<tr key={`${row.sku}:${row.machineName}`} className="border-b border-white/5">
<td className="py-2 pr-3">{row.sku}</td>
<td className="py-2 pr-3">{row.good}</td>
<td className={`py-2 pr-3 ${row.scrap > 0 ? "text-red-300" : ""}`}>{row.scrap}</td>
<td className="py-2 pr-3">{row.target ?? "--"}</td>
<td className="py-2 text-emerald-300">{progress}</td>
</tr>
);
})}
{rows.slice(0, 10).map((row) => (
<tr key={`${row.sku}:${row.machineName}`} className="border-b border-white/5">
<td className="py-2 pr-3">{row.sku}</td>
<td className="py-2 pr-3">{row.good}</td>
<td className={`py-2 ${row.scrap > 0 ? "text-red-300" : ""}`}>{row.scrap}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
}

View File

@@ -17,7 +17,7 @@ const COLORS: Record<RecapTimelineSegment["type"], string> = {
"slow-cycle": "bg-amber-500 text-black",
idle: "bg-zinc-600 text-zinc-300",
};
const MIN_SEGMENT_PCT = 1.5;
const MIN_SEGMENT_PCT = 0.3;
const LABEL_MIN_PCT = 5;
function fmtTime(valueMs: number, locale: string) {

View File

@@ -10,7 +10,7 @@ export const TIMELINE_COLORS: Record<RecapTimelineSegment["type"], string> = {
};
export const LABEL_MIN_WIDTH_PCT = 5;
export const SEGMENT_MIN_WIDTH_PCT = 1.5;
export const SEGMENT_MIN_WIDTH_PCT = 0.3;
export function formatTime(valueMs: number, locale: string) {
return new Date(valueMs).toLocaleTimeString(locale, {