Work-order codes no longer overlap (#42), Materials before Heat map (#44), 60 min idle for Admin and Clerical (#45)
- Traveler: each work-order line shrinks, then clips, to fit its own box; 18 pt gap between boxes - Work orders tab: card text wraps inside the card, smaller work order, wider cards - Packet tabs: Materials before Heat map - Sessions: SESSION_DESK_MINUTES (60) for Admin and Clerical lock and sign-out; /me answers lock_minutes and the browser's idle lock follows it Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -8,7 +8,7 @@ import type { Department, Factor, Me } from "./types";
|
||||
import { useLocation } from "react-router";
|
||||
import { screenOf, useEvents } from "./useEvents";
|
||||
|
||||
/** The old build's screen lock. The server locks at SESSION_LOCK_MINUTES too; it is the authority. */
|
||||
/** The old build's screen lock, used until /me says the session's own lock_minutes. The server is the authority. */
|
||||
export const IDLE_LOCK_MS = 3 * 60_000;
|
||||
/** A tab hidden for longer than this comes back locked (the old build's rule). */
|
||||
export const HIDDEN_LOCK_MS = 60_000;
|
||||
@@ -26,7 +26,7 @@ function goneNotice(err: ApiError): string {
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
/** Idle time before the local lock. Tests pass a small value. */
|
||||
/** Idle time before the local lock. Defaults to the session's lock_minutes. Tests pass a small value. */
|
||||
idleLockMs?: number;
|
||||
/** How often the idle timer checks. */
|
||||
idleCheckMs?: number;
|
||||
@@ -34,7 +34,7 @@ interface AuthProviderProps {
|
||||
hiddenLockMs?: number;
|
||||
}
|
||||
|
||||
export function AuthProvider({ children, idleLockMs = IDLE_LOCK_MS, idleCheckMs = IDLE_CHECK_MS, hiddenLockMs = HIDDEN_LOCK_MS }: AuthProviderProps) {
|
||||
export function AuthProvider({ children, idleLockMs: idleLockProp, idleCheckMs = IDLE_CHECK_MS, hiddenLockMs = HIDDEN_LOCK_MS }: AuthProviderProps) {
|
||||
const [status, setStatusState] = useState<AuthStatus>("loading");
|
||||
const [me, setMeState] = useState<Me | null>(null);
|
||||
const [departments, setDepartments] = useState<Department[]>([]);
|
||||
@@ -193,6 +193,8 @@ export function AuthProvider({ children, idleLockMs = IDLE_LOCK_MS, idleCheckMs
|
||||
};
|
||||
}, []);
|
||||
|
||||
const sessionLockMin = me?.session.lock_minutes;
|
||||
const idleLockMs = idleLockProp ?? (sessionLockMin ? sessionLockMin * 60_000 : IDLE_LOCK_MS);
|
||||
useEffect(() => {
|
||||
if (status !== "ready") return;
|
||||
lastActivity.current = Date.now();
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface SessionInfo {
|
||||
expires_at: string;
|
||||
locked: boolean;
|
||||
locked_since: string | null;
|
||||
/** Idle minutes before the screen lock: 60 for Admin and Clerical, SESSION_LOCK_MINUTES otherwise. */
|
||||
lock_minutes?: number;
|
||||
device: DeviceRef | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import { packetMessage, pillClassFor, WH_STAGES, pillText, progress, whLine, whS
|
||||
import { useWhMove } from "../warehouse/useWhMove";
|
||||
|
||||
type Tab = "drawing" | "heat" | "materials" | "checklist" | "welds" | "qc" | "workorders" | "changes" | "docs" | "history";
|
||||
const TABS: readonly Tab[] = ["drawing", "heat", "materials", "checklist", "welds", "qc", "workorders", "changes", "docs", "history"];
|
||||
const TABS: readonly Tab[] = ["drawing", "materials", "heat", "checklist", "welds", "qc", "workorders", "changes", "docs", "history"];
|
||||
function isTab(v: string | null): v is Tab {
|
||||
return v !== null && (TABS as readonly string[]).includes(v);
|
||||
}
|
||||
@@ -332,8 +332,8 @@ export function PacketPage() {
|
||||
const ckOpen = p.checklist_items.filter((i) => !p.stage?.checklist?.[i.key]?.done).length;
|
||||
const tabs: Array<{ id: Tab; label: string; count?: number }> = [
|
||||
{ id: "drawing", label: "Drawing", count: drawingPages.length },
|
||||
{ id: "heat", label: "Heat map" },
|
||||
{ id: "materials", label: "Materials", count: p.bom.length },
|
||||
{ id: "heat", label: "Heat map" },
|
||||
{ id: "checklist", label: "Checklist", ...(p.stage ? { count: ckOpen } : {}) },
|
||||
{ id: "welds", label: "Weld map", count: p.welds.length },
|
||||
...(hasQc ? [{ id: "qc" as const, label: "Q.C." }] : []),
|
||||
|
||||
@@ -351,7 +351,7 @@ export function WorkOrdersTab({ p }: { p: PacketRead }) {
|
||||
return (
|
||||
<div key={w.id ?? woKey(w)} className="qr">
|
||||
<Qr text={code} />
|
||||
<div>
|
||||
<div className="txt">
|
||||
<div className="wo">{w.wo_base}</div>
|
||||
<div className="small">
|
||||
Lot <b className="mono">{w.lot}</b> · Seq <b className="mono">{show(w.seq)}</b> {w.op ?? ""}
|
||||
|
||||
@@ -337,11 +337,12 @@ input.in.num { text-align: right; font-family: var(--f-mono); }
|
||||
.act .why { color: var(--amber); }
|
||||
label.btn input.sr { pointer-events: none; }
|
||||
.balloon { display: inline-grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; border: 1.5px solid var(--cyan); color: var(--cyan); font-family: var(--f-mono); font-size: 12.5px; font-weight: 600; }
|
||||
.qrs { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; }
|
||||
.qrs { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr)); gap: 18px; }
|
||||
.qr { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 16px; display: flex; gap: 16px; align-items: center; }
|
||||
.qr .code { background: var(--white); padding: 10px; border-radius: 8px; flex: none; width: 148px; height: 148px; display: grid; place-items: center; color: var(--bg); }
|
||||
.qr .code canvas { width: 128px !important; height: 128px !important; }
|
||||
.qr .wo { font-family: var(--f-disp); font-weight: 700; font-size: 26px; color: var(--white); letter-spacing: .03em; }
|
||||
.qr .txt { min-width: 0; flex: 1; overflow-wrap: anywhere; }
|
||||
.qr .wo { font-family: var(--f-disp); font-weight: 700; font-size: 22px; color: var(--white); letter-spacing: .03em; line-height: 1.15; }
|
||||
.pdfv { display: flex; flex-direction: column; gap: 10px; }
|
||||
.pdfv-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.pdfv-bar .seg button { min-width: 44px; font-weight: 700; }
|
||||
|
||||
Reference in New Issue
Block a user