almost_done
This commit is contained in:
@@ -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]">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, {
|
||||
|
||||
Reference in New Issue
Block a user