PageSourceSearch

https://ridedna.ai/assets/CompetitorModels-9i5DBNFq.js

js ridedna.ai collected 2026-10-05 14:39:02 UTC 117,670 bytes, 783 lines download raw bytes

1import { c as ua, d as He, r as d, e as Ve, J as ke, K as se, y as ne, j as e, s as De, t as Oe, v as Ie, w as Pe, x as Ee, B as $, ag as Z, cs as Ca, aX as Ae, a9 as qe, z as $t, L as ie, a5 as pe, A as je, aF as sa, ct as wa, cu as Sa, be as ot, a7 as ha, cv as Ma, cw as Ct, cx as ht, cy as st, aU as wt, aG as xa, I as ye, S as St, f as Mt, g as Tt, h as Rt, l as be, M as At, C as ga, a4 as fa, bk as pa, V as We, a$ as Ta, ay as Ra, b0 as Fa, o as Ce, p as we, q as Se, T as ba, bv as $a, bJ as Aa, N as ra } from "./index-B9miRpwC.js";
2import { T as Dt, a as Ot, b as Ke, c as I, d as It, e as P } from "./table-D-NY6-3m.js";
3import { A as xt, a as gt, b as ft, c as pt, d as bt, e as jt, f as yt, g as kt } from "./alert-dialog-Cv39x3F5.js";
4import { C as Da, a as Oa, b as Ia } from "./collapsible-lrkyHkBq.js";
5import { r as Pa, G as Ea, c as La, e as Ba, s as Ua, f as lt, a as za, C as _a } from "./resolveRearCenterConvention-Ck2Uhhpy.js";
6import { C as ja } from "./chevrons-right-qnSTlkCF.js";
7import { H as Ft } from "./history-CEPm62B8.js";
8import { G as Va, C as Ga } from "./GeometryReportCard-Bn1sTy_a.js";
9import { R as Pt } from "./refresh-cw-D2_WynW5.js";
10import { P as qa } from "./play-BHXr4qu-.js";
11import { L as ya } from "./list-checks-iqZBxtyj.js";
12import { U as Ge } from "./upload-u0DU9Zyt.js";
13import { E as ia } from "./eye-3Ma80GoX.js";
14import { E as na } from "./eye-off-DOl5DV4h.js";
15import { P as la } from "./pencil-CWIGhVLH.js";
16import { O as Ka } from "./octagon-alert-DIrZyqAr.js";
17import { B as Ha } from "./ban-DEw-6ye0.js";
18import { S as ka } from "./save-fTnHtNzX.js";
19import "./progress-DFvIGKNt.js";
20import "./topTubeCalculator-fCM7LY55.js";
21import "./link-BVyy0oqf.js";
22import "./package-CQTVcVCl.js";
23import "./calculator-B2vnHHEJ.js";
24import "./chevron-left-B-jr5clS.js";
25import "./file-down-S4Ft3bwC.js";
26import "./info-CWBPgXHY.js";
27import "./git-merge-F2YjpL8M.js";
28import "./minus-NuscadqZ.js";
29import "./trending-up-Cbn6FDrL.js";
30/**
31* @license lucide-react v0.453.0 - ISC
32*
33* This source code is licensed under the ISC license.
34* See the LICENSE file in the root directory of this source tree.
35*/
36const Wa = ua("ChartNoAxesColumn", [["line", { x1: "18", x2: "18", y1: "20", y2: "10", key: "1xfpm4" }], ["line", { x1: "12", x2: "12", y1: "20", y2: "4", key: "be30l9" }], ["line", { x1: "6", x2: "6", y1: "20", y2: "14", key: "1r4le6" }]]);
37/**
38* @license lucide-react v0.453.0 - ISC
39*
40* This source code is licensed under the ISC license.
41* See the LICENSE file in the root directory of this source tree.
42*/
43const dt = ua("ScanSearch", [["path", { d: "M3 7V5a2 2 0 0 1 2-2h2", key: "aa7l1z" }], ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2", key: "4qcy5o" }], ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2", key: "6vwrx8" }], ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2", key: "ioqczr" }], ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }], ["path", { d: "m16 16-1.9-1.9", key: "1dq9hf" }]]);
44function Nt(t, m) {
45  return (t ?? "").localeCompare(m ?? "", void 0, { sensitivity: "base", numeric: !0 });
46}
47function Ya(t, m) {
48  const h = t.modelYear ?? Number.NEGATIVE_INFINITY, T = m.modelYear ?? Number.NEGATIVE_INFINITY;
49  if (h !== T) return T - h;
50  const A = Nt(t.brand, m.brand);
51  if (A !== 0) return A;
52  const N = Nt(t.name, m.name);
53  return N !== 0 ? N : Nt(t.id, m.id);
54}
55function oa(t, m = 1) {
56  return t === null ? "\u2014" : t.toFixed(m);
57}
58function Qa(t) {
59  return t.currentValue === null && t.extractedValue !== null ? "NEW" : t.deltaMm === null ? "\u2014" : `${t.extractedValue > t.currentValue ? "+" : "-"}${t.deltaMm.toFixed(1)}`;
60}
61function da(t) {
62  return t === "headTubeAngle" || t === "seatTubeAngle";
63}
64function Na({ open: t, onOpenChange: m, modelId: h, modelName: T, hasImage: A }) {
65  const { toast: N } = He(), [f, V] = d.useState(!0), [S, R] = d.useState({}), [K, Q] = d.useState(!1), L = d.useRef(null), { data: F, isLoading: X, refetch: H } = Ve({ queryKey: ["/api/admin/competitor-database/models", h, "reaudit", "latest"], queryFn: async () => {
66    const l = await fetch(`/api/admin/competitor-database/models/${h}/reaudit/latest`, { credentials: "include" });
67    if (!l.ok) throw new Error("Failed to fetch reaudit run");
68    return l.json();
69  }, enabled: t, staleTime: 0 }), k = F?.run ?? null, x = F?.diffs ?? [], n = F?.labelViolations ?? [], o = x.map((l) => ({ ...l, decision: S[l.id] ?? l.decision })), u = f ? o.filter((l) => l.flagged) : o, r = u.reduce((l, j) =>
69 (l[j.bikeSize] || (l[j.bikeSize] = []), l[j.bikeSize].push(j), l), {}), p = ke({ mutationFn: () => se("POST", `/api/admin/competitor-database/models/${h}/reaudit`), onSuccess: () => {
70    R({}), H(), N({ title: "Re-audit complete", description: "Diffs are ready for review." });
71  }, onError: (l) => {
72    N({ title: "Re-audit failed", description: l?.message ?? "Unknown error", variant: "destructive" });
73  } }), w = d.useCallback(async (l, j) => {
74    if (!k || l.appliedAt) return;
75    const g = S[l.id] ?? l.decision;
76    R((v) => ({ ...v, [l.id]: j }));
77    try {
78      await se("PATCH", `/api/admin/competitor-database/models/${h}/reaudit/${k.id}/decisions`, { decisions: [{ diffId: l.id, decision: j }] });
79    } catch {
80      R((v) => ({ ...v, [l.id]: g })), N({ title: "Error", description: "Failed to save decision.", variant: "destructive" });
81    }
82  }, [k, S, h, N]), B = d.useCallback((l) => {
83    const g = (S[l.id] ?? l.decision) === "accepted" ? "pending" : "accepted";
84    return w(l, g);
85  }, [S, w]), q = d.useCallback((l) => {
86    const g = (S[l.id] ?? l.decision) === "rejected" ? "pending" : "rejected";
87    return w(l, g);
88  }, [S, w]), M = d.useCallback(async () => {
89    if (!k) return;
90    const j = o.filter((v) => v.flagged && !v.appliedAt).map((v) => ({ diffId: v.id, decision: "accepted" })), g = {};
91    for (const v of j) g[v.diffId] = "accepted";
92    R((v) => ({ ...v, ...g }));
93    try {
94      await se("PATCH", `/api/admin/competitor-database/models/${h}/reaudit/${k.id}/decisions`, { decisions: j }), N({ title: "Accepted", description: `${j.length} flagged diffs accepted.` });
95    } catch {
96      R((v) => {
97        const a = { ...v };
98        for (const D of j) delete a[D.diffId];
99        return a;
100      }), N({ title: "Error", description: "Failed to accept diffs.", variant: "destructive" });
101    }
102  }, [k, o, h, N]), E = d.useCallback(async () => {
103    if (!k) return;
104    const j = o.filter((v) => v.flagged && !v.appliedAt).map((v) => ({ diffId: v.id, decision: "rejected" })), g = {};
105    for (const v of j) g[v.diffId] = "rejected";
106    R((v) => ({ ...v, ...g }));
107    try {
108      await se("PATCH", `/api/admin/competitor-database/models/${h}/reaudit/${k.id}/decisions`, { decisions: j }), N({ title: "Declined", description: `${j.length} flagged diffs marked N/A.` });
109    } catch {
110      R((v) => {
111        const a = { ...v };
112        for (const D of j) delete a[D.diffId];
113        return a;
114      }), N({ title: "Error", description: "Failed to decline diffs.", variant: "destructive" });
115    }
116  }, [k, o, h, N]), U = ke({ mutationFn: async () => (await se("POST", `/api/admin/competitor-database/models/${h}/reaudit/${k.id}/apply`)).json(), onSuccess: (l) => {
117    R({}), H(), ne.invalidateQueries({ predicate: (g) => typeof g.queryKey[0] == "string" && g.queryKey[0].includes("competitor-database") });
118    const j = l.reprocessed ? "Geometry recalculated automatically." : "Note: geometry recalculation could not run.";
119    N({ title: `${l.applied} field${l.applied !== 1 ? "s" : ""} applied`, description: `${j}${l.skipped > 0 ? ` (${l.skipped} skipped)` : ""}` });
120  }, onError: (l) => {
121    N({ title: "Apply failed", description: l?.message ?? "Unknown error", variant: "destructive" });
122  } }), Y = o.filter((l) => l.decision === "accepted" && !l.appliedAt).length, ce = o.filter((l) => l.appliedAt).length, C = o.filter((l) => l.flagged).length, ee = d.useCallback(async (l) => {
123    Q(!0);
124    try {
125      const j = new FormData();
126      j.append("file", l);
127      const g = await fetch(`/api/admin/competitor-database/models/${h}/geometry-image`, { method: "POST", credentials: "include", body: j });
128      if (!g.ok) throw new Error((await g.json()).error ?? "Upload failed");
129      N({ title: "Chart image replaced", description: "Running re-audit with the new image\u2026" }), await se("POST", `/api/admin/competitor-database/models/${h}/reaudit`), R({}), H(), N({ title: "Re-audit complete", description: "Diffs updated from the new chart image." });
130    } catch (j) {
131      N({ title: "Image replace failed", description: j?.message ?? "Unknown error", variant: "destructive" });
132    } finally {
133      Q(!1), L.current && (L.current.value = "");
134    }
135  }, [h, H, N]);
136  return e.jsxs(De, { open: t, onOpenChange: m, children: [e.jsx("input", { ref: L, type: "file", accept: "image/*", className: "hidden", onChange: (l) => {
137    var g;
138    const j = (g = l.target.files) == null ? void 0 : g[0];
139    j && ee(j);
140  } }), e.jsxs(Oe, { className: "max-w-6xl max-h-[90vh] flex flex-col gap-0 p-0", children: [e.jsxs(Ie, { className: "px-6 pt-6 pb-4 border-b border-border", children: [e.jsxs(Pe, { className: "flex items-center gap-2", children: [e.jsx(dt, { className: "w-5 h-5" }), "Vision Re-Audit \u2014 ", T] }), e.jsx(Ee, { children: "Re-extract geometry from the stored chart image, compare against current DB values, and selectively approve corrections." })] }), e.jsxs("div", { className: "flex items-center justify-between px-6 py-3 border-b border-border gap-4 flex-wrap", children: [e.jsxs("div", { className: "flex items-center gap-3", children: [e.jsxs($, { size: "sm", variant: "outline", onClick: () => p.mutate(), disabled: p.isPending || K || !A, "data-testid": `button-run-reaudit-${h}`, children: [p.isPending ? e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }) : e.jsx(dt, { className: "w-4 h-4 mr-2" }), p.isPending ? "Running\u2026" : k ? "Re-Run" : "Run Re-Audit"] }), e.jsxs($, { size: "sm", variant: "outline", onClick: () => {
141    var l;
142    return (l = L.current) == null ? void 0 : l.click();
143  }, disabled: p.isPending || K, "data-testid": `button-replace-image-${h}`, title: "Upload a new chart image, then automatically re-run the re-audit", children: [K ? e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }) : e.jsx(Ca, { className: "w-4 h-4 mr-2" }), K ? "Uploading\u2026" : "Replace Image"] }), !A && e.jsx("span", { className: "text-xs text-muted-foreground", children: 'No chart image \u2014 upload one via "Replace Image"' }), k && e.jsx("span", { className: "text-xs text-muted-foreground", children: k.status === "completed" ? `Last run: ${new Date(k.createdAt).toLocaleDateString()} \u2014 ${k.totalDiffs ?? 0} diffs, ${k.flaggedDiffs ?? 0} flagged` : k.status === "failed" ? `Failed: ${k.errorMsg ?? "unknown error"}` : "Running\u2026" })] }), e.jsxs("div", { className: "flex items-center gap-3", children: [C > 0 && e.jsxs($, { size: "sm", variant: "outline", onClick: M, "data-testid": "button-accept-all-flagged", children: [e.jsx(Ae, { className: "w-4 h-4 mr-2 text-amber-500" }), "Accept All Flagged (", C, ")"] }), C > 0 && e.jsxs($, { size: "sm", variant: "outline", onClick: E, "data-testid": "button-decline-
143all-flagged", children: [e.jsx(qe, { className: "w-4 h-4 mr-2 text-muted-foreground" }), "Decline All Flagged (", C, ")"] }), e.jsxs("div", { className: "flex items-center gap-2", children: [e.jsx($t, { id: "flagged-only", checked: f, onCheckedChange: V, "data-testid": "switch-flagged-only" }), e.jsx(ie, { htmlFor: "flagged-only", className: "text-sm whitespace-nowrap", children: "Flagged only" })] }), e.jsxs($, { size: "sm", onClick: () => U.mutate(), disabled: Y === 0 || U.isPending, "data-testid": "button-apply-accepted", children: [U.isPending && e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }), !U.isPending && e.jsx(ja, { className: "w-4 h-4 mr-2" }), U.isPending ? "Applying & Recalculating\u2026" : `Apply Accepted (${Y})`] })] })] }), k && k.status === "completed" && e.jsxs("div", { className: "flex items-center gap-2 px-6 py-2 border-b border-border flex-wrap", children: [e.jsxs(pe, { variant: "outline", children: [k.totalDiffs ?? 0, " diffs total"] }), n.length > 0 && e.jsxs(pe, { className: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 border-red-300 dark:border-red-700", children: [e.jsx(je, { className: "w-3 h-3 mr-1" }), n.length, " label ", n.length === 1 ? "problem" : "problems"] }), C > 0 && e.jsxs(pe, { className: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300 border-amber-300 dark:border-amber-700", children: [e.jsx(je, { className: "w-3 h-3 mr-1" }), C, " flagged"] }), Y > 0 && e.jsxs(pe, { className: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300 border-emerald-300 dark:border-emerald-700", children: [e.jsx(Ae, { className: "w-3 h-3 mr-1" }), Y, " accepted"] }), ce > 0 && e.jsxs(pe, { className: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 border-blue-300 dark:border-blue-700", children: [ce, " applied"] })] }), n.length > 0 && e.jsxs("div", { className: "px-6 py-4 border-b border-border bg-red-50/60 dark:bg-red-950/20", children: [e.jsxs("div", { className: "flex items-start gap-2 mb-3", children: [e.jsx(je, { className: "w-4 h-4 text-red-600 dark:text-red-400 mt-0.5 shrink-0" }), e.jsxs("div", { children: [e.jsxs("h3", { className: "text-sm font-semibold text-red-700 dark:text-red-300", children: ["Size label integrity problems (", n.length, ")"] }), e.jsx("p", { className: "text-xs text-red-600/80 dark:text-red-400/80 mt-0.5", children: "These structural problems with the frame size labels are invisible to the geometry diff engine. They must be corrected directly in the database before this model can be re-published." })] })] }), e.jsx("div", { className: "flex flex-col gap-2", children: n.map((l, j) => e.jsxs("div", { className: "rounded-md border border-red-200 dark:border-red-800 bg-white dark:bg-red-950/40 px-3 py-2", children: [e.jsxs("div", { className: "flex items-center gap-2 mb-0.5", children: [e.jsx(pe, { className: l.type === "pinarello_label_mismatch" ? "text-[10px] bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300 border-red-300" : l.type === "duplicate_label" ? "text-[10px] bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300 border-orange-300" : "text-[10px] bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300 border-amber-300", children: l.type === "pinarello_label_mismatch" ? "Label \u2260 CC" : l.type === "duplicate_label" ? "Duplicate" : "Non-monotonic" }), e.jsxs("span", { className: "text-xs font-mono font-semibold text-foreground", children: ['Size "', l.bikeSize, '"'] }), l.suggestedValue && e.jsxs("span", { className: "text-xs text-muted-foreground", children: ["\u2192 should be", " ", e.jsx("span", { className: "font-mono font-semibold text-emerald-700 dark:text-emerald-400", children: l.suggestedValue })] })] }), e.jsx("p", { className: "text-xs text-muted-foreground", children: l.detail })] }, `${l.type}-${l.sizeId}-${j}`)) })] }), e.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: X ? e.jsxs("div", { className: "flex items-center justify-center py-16 text-muted-foreground", children: [e.jsx(Z, { className: "w-5 h-5 animate-spin mr-2" }), "Loading\u2026"] }) : k ? k.status === "failed" ? e.jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-destructive gap-3", children: [e.jsx(je, { className: "w-8 h-8" }), e.jsx("p", { className: "text-sm", children: k.errorMsg ?? "Re-audit failed" })] }) : u.length === 0 ? e.jsx("div", { className: "flex items-center justify-center py-16 text-muted-foreground text-sm", children: f ? "No flagged diffs." : "No diffs recorded for this run." }) : e.jsxs(Dt, { children: [e.jsx(Ot, { children: e.jsxs(Ke, { children: [e.jsx(I, { className: "w-20", children: "Size" }), e.jsx(I, { className: "w-36", children: "Field" }), e.jsx(I, { className: "text-right w-24", children: "Current" }), e.jsx(I, { className: "text-right w-24", children: "Extracted" }), e.jsx(I, { className: "text-right w-20", children: "Delta" }), e.jsx(I, { className: "w-16 text-center", children: "Flag" }), e.jsx(I, { className: "w-28 text-center", children: "Accept / N/A" })] }) }), e.jsx(It, { children: Object.entries(r).map(([l, j]) => j.map((g, v) => {
144    const a = !!g.appliedAt, D = g.decision === "accepted", z = g.decision === "rejected", Me = da(g.fieldKey) ? 2 : 1, xe = da(g.fieldKey) ? "\xB0" : "mm";
145    return e.jsxs(Ke, { "data-testid": `diff-row-${g.id}`, className: a ? "opacity-50" : D ? "bg-emerald-50/40 dark:bg-emerald-950/20" : z ? "opacity-50" : g.flagged ? "bg-amber-50/40 dark:bg-amber-950/20" : "", children: [e.jsx(P, { className: "font-mono text-sm", children: v === 0 ? l : "" }), e.jsx(P, { className: "text-sm", children: g.fieldLabel ?? g.fieldKey }), e.jsxs(P, { className: "text-right font-mono text-sm text-muted-foreground", children: [oa(g.currentValue, Me), g.currentValue !== null ? xe : ""] }), e.jsxs(P, { className: "text-right font-mono text-sm", children: [oa(g.extractedValue, Me), g.extractedValue !== null ? xe : ""] }), e.jsxs(P, { className: `text-right font-mono text-sm font-medium ${g.flagged ? "text-amber-600 dar
145k:text-amber-400" : "text-muted-foreground"}`, children: [Qa(g), g.deltaMm !== null ? xe : ""] }), e.jsxs(P, { className: "text-center", children: [g.flagged && e.jsx(je, { className: "w-4 h-4 text-amber-500 inline-block" }), g.consistencyImpact === "degrades" && e.jsx("span", { className: "block text-[10px] font-semibold text-red-500 dark:text-red-400 leading-none mt-0.5", title: "Accepting this change would worsen geometric consistency \u2014 auto-de-flagged", children: "\u2191worse" }), g.consistencyImpact === "improves" && e.jsx("span", { className: "block text-[10px] font-semibold text-emerald-600 dark:text-emerald-400 leading-none mt-0.5", title: "Accepting this change would improve geometric consistency", children: "\u2193better" })] }), e.jsx(P, { className: "text-center", children: a ? e.jsx(pe, { className: "text-xs bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 border-blue-300", children: "Applied" }) : e.jsxs("div", { className: "flex items-center justify-center gap-1.5", children: [e.jsx("button", { onClick: () => B(g), "data-testid": `button-accept-${g.id}`, title: D ? "Un-accept" : "Accept this change", className: `w-7 h-7 rounded-md border transition-colors flex items-center justify-center ${D ? "bg-emerald-500 border-emerald-600 text-white" : "border-border bg-background hover:bg-muted"}`, children: D && e.jsx(Ae, { className: "w-4 h-4" }) }), e.jsx("button", { onClick: () => q(g), "data-testid": `button-dismiss-${g.id}`, title: z ? "Un-dismiss (mark pending)" : "Dismiss \u2014 measurement N/A or not applicable", className: `w-7 h-7 rounded-md border transition-colors flex items-center justify-center ${z ? "bg-muted border-border text-muted-foreground" : "border-border bg-background hover:bg-muted"}`, children: e.jsx(qe, { className: `w-3.5 h-3.5 ${z ? "text-muted-foreground" : "text-muted-foreground/40"}` }) })] }) })] }, g.id);
146  })) })] }) : e.jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-muted-foreground gap-3", children: [e.jsx(dt, { className: "w-10 h-10 opacity-30" }), e.jsx("p", { className: "text-sm", children: 'No re-audit run found. Click "Run Re-Audit" to start.' })] }) })] })] });
147}
148const Ja = { reaudit_apply: { label: "Re-Audit Apply", className: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300 border-emerald-300" }, admin_patch: { label: "Admin Edit", className: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 border-blue-300" }, direct_fix: { label: "Direct Fix", className: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300 border-amber-300" }, startup_backfill: { label: "Startup Backfill", className: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300 border-purple-300" }, system_write: { label: "System Write", className: "bg-muted text-muted-foreground border-muted" } };
149function ca(t) {
150  if (t === null) return "\u2014";
151  const m = Number(t);
152  return !isNaN(m) && t.trim() !== "" ? Number.isInteger(m) ? String(m) : m.toFixed(2).replace(/\.?0+$/, "") : t;
153}
154function Xa(t, m) {
155  if (t === null || m === null) return null;
156  const h = Number(t), T = Number(m);
157  return isNaN(h) || isNaN(T) ? null : T - h;
158}
159function Za(t) {
160  return new Date(t).toLocaleString("en-US", { month: "short", day: "
160numeric", year: "numeric", hour: "2-digit", minute: "2-digit" });
161}
162function es(t, m = 24) {
163  return t.length > m ? `${t.slice(0, m)}\u2026` : t;
164}
165function ts({ open: t, onOpenChange: m, modelId: h, modelName: T }) {
166  const { data: A, isLoading: N } = Ve({ queryKey: ["/api/admin/competitor-database/models", h, "change-history"], queryFn: async () => {
167    const f = await fetch(`/api/admin/competitor-database/models/${h}/change-history`, { credentials: "include" });
168    if (!f.ok) throw new Error("Failed to fetch change history");
169    return f.json();
170  }, enabled: t, staleTime: 3e4 });
171  return e.jsx(De, { open: t, onOpenChange: m, children: e.jsxs(Oe, { className: "max-w-5xl max-h-[90vh] flex flex-col gap-0 p-0", children: [e.jsxs(Ie, { className: "px-6 pt-6 pb-4 border-b border-border", children: [e.jsxs(Pe, { className: "flex items-center gap-2", children: [e.jsx(Ft, { className: "w-5 h-5" }), "Change History \u2014 ", T] }), e.jsx(Ee, { children: "Append-only audit trail of every write to this model's geometry data, regardless of the path that triggered it." })] }), e.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: N ? e.jsxs("div", { className: "flex items-center justify-center py-16 text-muted-foreground", children: [e.jsx(Z, { className: "w-5 h-5 animate-spin mr-2" }), "Loading\u2026"] }) : !A || A.length === 0 ? e.jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-muted-foreground gap-3", children: [e.jsx(Ft, { className: "w-10 h-10 opacity-30" }), e.jsx("p", { className: "text-sm", children: "No changes recorded yet." })] }) : e.jsxs(Dt, { children: [e.jsx(Ot, { children: e.jsxs(Ke, { children: [e.jsx(I, { className: "w-44", children: "Date" }), e.jsx(I, { className: "w-16", children: "Size" }), e.jsx(I, { className: "w-44", children: "Field" }), e.jsx(I, { className: "text-right w-24", children: "Before" }), e.jsx(I, { className: "text-right w-28", children: "After" }), e.jsx(I, { className: "w-36", children: "Source" }), e.jsx(I, { className: "w-36", children: "By" }), e.jsx(I, { children: "Note" })] }) }), e.jsx(It, { children: A.map((f) => {
172    const V = Ja[f.change_source] ?? { label: f.change_source, className: "bg-muted text-muted-foreground border-muted" }, S = Xa(f.old_value, f.new_value);
173    return e.jsxs(Ke, { children: [e.jsx(P, { className: "text-xs text-muted-foreground whitespace-nowrap", children: Za(f.created_at) }), e.jsx(P, { className: "font-mono text-sm", children: f.bike_size ?? "\u2014" }), e.jsx(P, { className: "font-mono text-xs", children: f.field_name }), e.jsx(P, { className: "text-right text-sm tabular-nums text-muted-foreground", children: ca(f.old_value) }), e.jsx(P, { className: "text-right text-sm tabular-nums font-medium", children: e.jsxs("span", { className: S != null && Math.abs(S) > 0 ? S > 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400" : "", children: [ca(f.new_value), S != null && Math.abs(S) > 0 && e.jsxs("span", { className: "ml-1 text-xs opacity-60", children: ["(", S > 0 ? "+" : "", Math.abs(S) < 1 ? S.toFixed(2) : S.toFixed(1), ")"] })] }) }), e.jsx(P, { children: e.jsx(pe, { variant: "outline", className: `text-xs ${V.className}`, children: V.label }) }), e.jsx(P, { className: "text-xs text-muted-foreground font-mono", children: f.changed_by === "system" ? "system" : es(f.changed_by) }), e.jsx(P, { className: "text-xs text-muted-foreground max-w-xs truncate", children: f.note ?? "" })] }, f.id);
174  }) })] }) }), e.jsx("div", { className: "px-6 py-3 border-t border-border text-xs text-muted-foreground", children: "Showing up to 200 most recent entries. This log is append-only and covers all writes to any admin-editable field." })] }) });
175}
176const as = { green: "#10b981", yellow: "#f59e0b", red: "#ef4444", purple: "#a855f7" }, ss = { green: "Acceptable (0\u20132mm)", yellow: "Minor Variance (2\u20135mm)", red: "Significant Variance (>5mm)", purple: "Incomplete \u2014 manufacturer did not publish required dimensions" };
177function ma({ severity: t, size: m = "sm" }) {
178  if (!t) return null;
179  const h = m === "md" ? 16 : 14, T = as[t], A = t === "green" ? Ae : t === "yellow" ? je : t === "red" ? Ka : Ha;
180  return e.jsx("span", { className: "inline-flex flex-shrink-0", "data-testid": `indicator-consistency-${t}`, title: ss[t], children: e.jsx(A, { size: h, color: T, strokeWidth: 2 }) });
181}
182function rs(t) {
183  if (t.engineUsedFrontTireSize != null) return Math.round(t.engineUsedFrontTireSize);
184  if (t.actualTrail == null || t.headTubeAngle == null || t.forkOffset == null) return null;
185  const m = t.headTubeAngle * Math.PI / 180, h = (t.actualTrail * Math.sin(m) + t.forkOffset) / Math.cos(m);
186  return Math.round(h);
187}
188function is(t, m) {
189  if (t.actualTrail == null || t.headTubeAngle == null || t.forkOffset == null) return null;
190  const h = t.headTubeAngle * Math.PI / 180;
191  return ((t.actualTrail * Math.sin(h) + t.forkOffset) / Math.cos(h) * Math.cos(h) - m) / Math.sin(h);
192}
193function _s() {
194  var Bt;
195  const { toast: t }
195 = He(), [m, h] = d.useState(""), [T, A] = d.useState(null), [N, f] = d.useState(!1), [V, S] = d.useState("year"), [R, K] = d.useState("models"), [Q, L] = d.useState(/* @__PURE__ */ new Set()), [F, X] = d.useState(null), [H, k] = d.useState(!1), [x, n] = d.useState(!1), [o, u] = d.useState(null), p = (() => {
196    const s = new URLSearchParams();
197    T && s.set("brand", T), m && s.set("search", m), s.set("includeUnpublished", "true"), N && s.set("status", "draft");
198    const b = s.toString();
199    return b ? `/api/admin/competitor-database/models?${b}` : "/api/admin/competitor-database/models";
200  })(), { data: w = [] } = Ve({ queryKey: ["/api/competitor-database/brands"] }), { data: B = [], isLoading: q } = Ve({ queryKey: [p] }), [M, E] = d.useState(null), U = ke({ mutationFn: async ({ modelId: s, overrideReason: b }) => se("POST", `/api/admin/competitor-database/models/${s}/toggle-status`, b ? { overrideReason: b } : void 0), onSuccess: () => {
201    ne.invalidateQueries({ predicate: (s) => typeof s.queryKey[0] == "string" && s.queryKey[0].includes("competitor-database") }), t({ title: "Status Updated", description: "Model status has been toggled." });
202  }, onError: (s, b) => {
203    const y = s.message.match(/^\d+: ([\s\S]+)$/);
204    if (y) try {
205      const _ = JSON.parse(y[1]);
206      if (_.requiresOverride && _.advisoryReasons && _.advisoryReasons.length > 0) {
207        E({ modelId: b.modelId, reasons: _.advisoryReasons, overrideReason: "" });
208        return;
209      }
210      if (_.hardBlockReasons && _.hardBlockReasons.length > 0) {
211        t({ title: "Cannot Publish", description: _.hardBlockReasons.join(" "), variant: "destructive" });
212        return;
213      }
214    } catch {
215    }
216    t({ title: "Error", description: "Failed to update status.", variant: "destructive" });
217  } }), Y = ke({ mutationFn: async (s) => se("DELETE", `/api/admin/competitor-database/models/${s}`), onSuccess: () => {
218    ne.invalidateQueries({ predicate: (s) => typeof s.queryKey[0] == "string" && s.queryKey[0].includes("competitor-database") }), t({ title: "Model Deleted", description: "Model and all sizes have been deleted." }), X(null);
219  }, onError: () => {
220    t({ title: "Error", description: "Failed to delete model.", variant: "destructive" });
221  } }), ce = ke({ mutationFn: async (s) => se("POST", "/api/admin/competitor-database/import", s), onSuccess: () => {
222    ne.invalidateQueries({ predicate: (s) => typeof s.queryKey[0] == "string" && s.queryKey[0].includes("competitor-database") });
223  } }), [C, ee] = d.useState(null), [l, j] = d.useState(!1);
224  d.useEffect(() => {
225    se("GET", "/api/admin/competitor-database/models/batch-reprocess/status").then((s) => s.json()).then((s) => {
226      s != null && s.running && (ee({ running: !0, total: s.total || 0, completed: s.completed || 0, failed: s.failed || 0 }), g());
227    }).catch(() => {
228    });
229  }, []);
230  const g = d.useCallback(() => {
231    let s = 0;
232    const b = 120, y = setInterval(async () => {
233      s++;
234      try {
235        const J = await (await se("GET", "/api/admin/competitor-database/models/batch-reprocess/status")).json();
236        ee(J), J.running ? s >= b && (clearInterval(y), ee(null)) : (clearInterval(y), ee(null), ne.invalidateQueries({ predicate: (Re) => typeof Re.queryKey[0] == "string" && Re.queryKey[0].includes("competitor-database") }), t({ title: "Recalculation Complete", description: `${J.completed} model${J.completed !== 1 ? "s" : ""} recalculated${J.failed > 0 ? `, ${J.failed} with errors` : "."}` }));
237      } catch {
238        s >= b && clearInterval(y);
239      }
240    }, 5e3);
241  }, []), v = ke({ mutationFn: async () => {
242    var y;
243    const s = await fetch("/api/admin/competitor-database/models/batch-reprocess", { method: "POST", headers: { "X-Requested-With": "XMLHttpRequest" }, credentials: "include" }), b = await s.json();
244    if (s.status === 409 && (y = b.state) != null && y.running) return { status: "already_running", ...b.state };
245    if (!s.ok) throw new Error(b.error || "Failed to start recalculation");
246    return b;
247  }, onSuccess: (s) => {
248    if (n(!1), s.status === "already_running") {
249      ee({ running: !0, total: s.total || 0, completed: s.completed || 0, failed: s.failed || 0 }), t({ title: "Already Recalculating", description: "A recalculation is already running. Monitoring progress\u2026" }), g();
250      return;
251    }
252    s.status === "started" ? (ee({ running: !0, total: s.total, completed: 0, failed: 0 }), t({ title: "Recalculation Started", description: `Processing ${s.total} models in the background. The list will update automatically when complete.` }), g()) : (ne.invalidateQueries({ predicate: (b) => typeof b.queryKey[0] == "string" && b.queryKey[0].includes("competitor-database") }), t({ title: "Recalculation Complete", description: `${s.total} models recalculated.` }));
253  }, onError: (s) => {
254    t({ title: "Error", description: s?.message || "Failed to start recalculation.", variant: "destructive" }), n(!1);
255  } }), [a, D] = d.useState(null), [z, Me] = d.useState(null), [xe, Le] = d.useState(!1), [Ue, ge] = d.useState(!1), [Ne, c] = d.useState("all"), [O, G] = d.useState(!1), [me, le] = d.useState(/* @__PURE__ */ new Set()), { data: W = [] } = Ve({ queryKey: ["/api/admin/competitor-database/models?includeUnpublished=true"], enabled: O }), Be = W.filter((s) => s.geometryImageUrl), Ye = d.useRef(t);
256  Ye.current = t;
257  const Te = d.useCallback(async () => {
258    try {
259      const b = await (await se("GET", "/api/admin/competitor-database/reaudit/batch/status")).json();
260      return b && (D(b), Me(b)), b;
261    } catch {
262      return null;
263    }
264  }, []), Qe = d.useCallback(() => {
265    let s = !0, b = 0;
266    const y = 300, _ = async () => {
267      if (!s) return;
268      b++;
269      const J = await Te();
270      if (s) {
271        if (!J || !J.running) {
272          if (s = !1, J && !J.running) {
273            const Re = J.results.filter((mt) => mt.flaggedDiffs > 0).length;
274            Ye.current({ title: "Vision Re-Audit Complete", description: Re > 0 ? `${Re} model${Re !== 1 ? "s" : ""} have flagged diffs needing review.` : `${J.completed} models checked \u2014 no flagged diffs found.` });
275          }
276          return;
277        }
278        if (b >= y) {
279          s = !1;
280          return;
281        }
282        setTimeout(_, 3e3);
283      }
284    };
285    return _(), () => {
286      s = !1;
287    };
288  }, [Te]), Je = "ridedna_reaudit_dismissed_at";
289  d.useEffect(() => {
290    let s;
291    return (async () => {
292      const y = await Te();
293      if (y != null && y.running) s = Qe();
294      else if (y && !y.running) try {
295        const _ = localStorage.getItem(Je);
296        if (_ && Number(_) === y.startedAt) {
297          D(null);
298          return;
299        }
300      } catch {
301      }
302    })(), () => s?.();
303  }, [Te, Qe]);
304  const ct = d.useCallback(() => {
305    if (a != null && a.running) {
306      Le(!0);
307      return;
308    }
309    if (a) try {
310      localStorage.setItem(Je, String(a.startedAt));
311    } catch {
312    }
313    D(null);
314  }, [a]);
315  d.useEffect(() => {
316    xe && a && !a.running && Le(!1);
317  }, [a, xe]);
318  const rt = d.useCallback(() => {
319    if (C != null && C.running) {
320      j(!0);
321      return;
322    }
323    ee(null);
324  }, [C]);
325  d.useEffect(() => {
326    l && C && !C.running && j(!1);
327  }, [C, l]);
328  const it = d.useCallback(async () => {
329    try {
330      localStorage.removeItem(Je);
331    } catch {
332    }
333    await Te();
334  }, [Te]), ue = ke({ mutationFn: async ({ mode: s, modelIds: b }) => {
335    const y = { mode: s };
336    s === "selected" && b && (y.modelIds = b);
337    const _ = await se("POST", "/api/admin/competitor-database/reaudit/batch", y);
338    if (!_.ok) {
339      const J = await _.json().catch(() => ({}));
340      throw new Error(J?.error ?? `HTTP ${_.status}`);
341    }
342    return _.json();
343  }, onSuccess: (s) => {
344    ge(!1), G(!1), le(/* @__PURE__ */ new Set()), s.state && D(s.state), s.queued > 0 ? Qe() : t({ title: "Nothing to process", description: s.message ?? "No models matched the selected run mode." });
345  }, onError: (s) => {
346    t({ title: "Failed to start re-audit", description: s?.message ?? "Unknown error", variant: "destructive" }), ge(!1);
347  } }), ze = (s) => {
348    L((b) => {
349      const y = new Set(b);
350      return y.has(s) ? y.delete(s) : y.add(s), y;
351    });
352  }, va = (s) => {
353    h(""), A(null), f(!1), L((b) => new Set(b).add(s)), K("models"), u(s);
354  };
355  d.useEffect(() => {
356    if (!o || R !== "models" || !B.find((y) => y.id === o)) return;
357    const b = document.querySelector(`[data-testid="card-admin-model-${o}"]`);
358    b && (b.scrollIntoView({ behavior: "smooth", block: "start" }), u(null));
359  }, [o, R, B]);
360  const Et = d.useCallback(async (s, b) => {
361    const y = s[0], _ = y?.brand, J = y?.modelName, Re = y?.modelYear, mt = y?.bikeType;
362    if (!_ || !J) {
363      t({ title: "Missing Information", description: "Please enter brand and model name in the upload dialog.", variant: "destructive" });
364      return;
365    }
366    const Fe = s.map((i) => {
367      var ve, Xe, Ze, et, tt, nt, de, at, _t, Vt, Gt, qt, Kt, Ht, Wt, Yt, Qt, Jt, Xt, Zt;
368      const te = (i.geometry.wheelSize || "700c").toLowerCase().trim().replace(/[\s-]*(inch(?:es)?|in\.?)\s*$/, "").trim(), re = te.includes("650b") || te.includes("27.5") || te === "584" || te.includes("584mm"), he = te === "26" || te === "26er" || te === "559" ? 559 : re ? 584 : 622, $e = Pa({ chainstayMm: i.geometry.chainstayMm ?? null, ...i.geometry.rearCenterMm !== void 0 ? { rearCenterMm: i.geometry.rearCenterMm } : {}, wheelbaseMm: i.geometry.wheelbaseMm ?? null, frontCenterMm: i.geometry.frontCenterMm ?? null, bbDropMm: i.geometry.bbDropMm ?? null, sizeLabel: i.selectedSize }), ae = $e.csLength0, oe = $e.horizontalRC;
369      return { bikeSize: i.selectedSize, rimDiameter: he, headTubeAngle: i.geometry.headTubeAngleDeg, seatTubeAngle: i.geometry.seatTubeAngleDeg, headTubeLength: i.geometry.headTubeLengthMm, csLength0: ae, rearCenter: oe ?? void 0, csIsHorizontalSource: $e.interpretation === "horizontal", bbDrop0: ((ve = i.geometrySources) == null ? void 0 : ve.forkA2CMm) === "not-published" ? null : i.geometry.bbDropMm != null ? Math.abs(i.geometry.bbDropMm) : null, forkLength: i.geometry.forkA2CMm == null || ((Xe = i.geometrySources) == null ? void 0 : Xe.forkA2CMm) === "chart" || ((Ze = i.geometrySources) == null ? void 0 : Ze.forkA2CMm) === "user" ? i.geometry.forkA2CMm : Math.round(i.geometry.forkA2CMm), forkLengthSource: ((et = i.geometrySources) == null ? void 0 : et.forkA2CMm) === "chart" ? "chart" : ((tt = i.geometrySources) == null ? void 0 : tt.forkA2CMm) === "user" ? "manual" : ((nt = i.geometrySources) == null ? void 0 : nt.forkA2CMm) === "calculated" ? "calculated" : ((de = i.geometrySources) == null ? void 0 : de.forkA2CMm) === "not-published" ? "not-published" : void 0, forkOffset: ((at = i.geometrySources) == null ? void 0 : at.forkA2CMm) === "not-published" ? null : (() => {
370        var ta, aa;
371        const ut = i.geometry.forkOffsetMm ?? i.geometry.forkRakeMm ?? null, ea = ((ta = i.geometrySources) == null ? void 0 : ta.forkOffsetMm) ?? ((aa = i.geometrySources) == null ? void 0 : aa.forkRakeMm);
372        return ut == null || ea === "chart" || ea === "user" ? ut : Math.round(ut);
373      })(), forkOffsetSource: ((_t = i.geometrySources) == null ? void 0 : _t.forkA2CMm) === "not-published" ? "not-published" : ((Vt = i.geometrySources) == null ? void 0 : Vt.forkOffsetMm) === "chart" || ((Gt = i.geometrySources) == null ? void 0 : Gt.forkRakeMm) === "chart" ? "chart" : ((qt = i.geometrySources) == null ? void 0 : qt.forkOffsetMm) === "user" || ((Kt = i.geometrySources) == null ? void 0 : Kt.forkRakeMm) === "user" ? "manual" : ((Ht = i.geometrySources) == null ? void 0 : Ht.forkOffsetMm) === "calculated" || ((Wt = i.geometrySources) == null ? void 0 : Wt.forkRakeMm) === "calculated" || i.geometry.forkOffsetMm != null || i.geometry.forkRakeMm != null ? "calculated" : void 0, isFlipChip: i.geometry.forkOffsetMm2 != null || i.geometry.forkRakeMm2 != null || i.geometry.forkA2CMm2 != null, forkOffset2: i.geometry.forkOffsetMm2 ?? i.geometry.forkRakeMm2 ?? void 0, forkLength2: i.geometry.forkA2CMm2 ?? void 0, frontCenter: i.geometry.frontCenterMm, stackRef: ((Yt = i.geometrySources) == null ? void 0 : Yt.stackMm) === "chart" ? i.geometry.stackMm : void 0, reachRef: ((Qt = i.geometrySources) == null ? void 0 : Qt.reachMm) === "chart" ? i.geometry.reachMm : void 0, wheelbaseRef: i.geometry.wheelbaseMm, frontCenterRef: ((Jt = i.geometrySources) == null ? void 0 : Jt.frontCenterMm) === "chart" ? i.geometry.frontCenterMm : void 0, standoverRef: i.geometry.standoverMm, stack: ((Xt = i.geometrySources) == null ? void 0 : Xt.stackMm) === "chart" ? i.geometry.stackMm : void 0, reach: ((Zt = i.geometrySources) == null ? void 0 : Zt.reachMm) === "chart" ? i.geometry.reachMm : void 0, wheelbase: i.geometry.wheelbaseMm, standoverHeight: i.geometry.standoverMm, seatTubeC2C: i.geometry.seatTubeC2CMm, seatTubeC2T: i.geometry.seatTubeC2TMm, effectiveTopTube: i.geometry.effectiveTopTubeMm, actualTopTube: i.geometry.actualTopTubeMm, bbHeight: i.geometry.bbHeightMm, maxRearTireSize: i.geometry.maxRearTireClearanceMm, forkMaxTireClearance: i.geometry.maxTireClearanceMm, specificationTireMm: i.geometry.specificationTireMm, manufacturerTrail: i.geometry.trailMm, effectiveStack: i.geometry.effectiveStackMm, effectiveReach: i.geometry.effectiveReachMm, riderHeightMinCm: i.geometry.riderHeightMinCm, riderHeightMaxCm: i.geometry.riderHeightMaxCm, inseamMinCm: i.geometry.inseamMinCm, inseamMaxCm: i.geometry.inseamMaxCm, standoverClearanceCm: i.geometry.standoverClearanceCm, torsoLengthMinCm: i.geometry.torsoLengthMinCm, torsoLengthMaxCm: i.geometry.torsoLengthMaxCm, armLengthMinCm: i.geometry.armLengthMinCm, armLengthMaxCm: i.geometry.armLengthMaxCm, fieldsFoundInChart: i.fieldsFoundInChart ?? void 0 };
374    }), Ut = Fe.filter((i) =>
374 i.seatTubeC2T != null).length, zt = Fe.filter((i) => i.seatTubeC2C != null && i.seatTubeC2T == null).length;
375    Ut > 0 && zt > 0 && (`${Ut}${zt}`, Fe.forEach((i) => {
376      i.seatTubeC2C != null && i.seatTubeC2T == null && (i.seatTubeC2T = i.seatTubeC2C, i.seatTubeC2C = void 0, `${i.bikeSize}${i.seatTubeC2T}`);
377    }));
378    const _e = Fe.map((i) => i.forkLength).filter((i) => i != null);
379    if (_e.length > 1) {
380      const i = _e.map((ae) => Math.round(ae)), te = /* @__PURE__ */ new Map();
381      i.forEach((ae) => te.set(ae, (te.get(ae) || 0) + 1));
382      const re = [...te.keys()].sort((ae, oe) => ae - oe);
383      let fe = [], he = 0;
384      for (const ae of re) {
385        const oe = i.filter((ve) => ve >= ae && ve <= ae + 5);
386        oe.length > he && (he = oe.length, fe = oe);
387      }
388      const $e = Math.ceil(_e.length * 0.6);
389      if (he >= $e && he >= 2) {
390        const ae = /* @__PURE__ */ new Map();
391        fe.forEach((de) => ae.set(de, (ae.get(de) || 0) + 1));
392        let oe = fe[0], ve = 0;
393        ae.forEach((de, at) => {
394          (de > ve || de === ve && at < oe) && (oe = at, ve = de);
395        });
396        const Xe = oe, Ze = i.filter((de) => de < oe - 5 || de > oe + 5), et = Math.min(...i), tt = Math.max(...i), nt = tt - et;
397        `${_e.length}${et}${tt}${nt}${he}${_e.length}${Xe}${ve}${Ze.length > 0 ? Ze.join(", ") : "none"}`, Fe.forEach((de) => {
398          de.forkLength != null && (de.forkLength = Xe);
399        });
400      } else {
401        const ae = Math.min(...i), oe = Math.max(...i);
402        `${he}${_e.length}${$e}${ae}${oe}`;
403      }
404    }
405    `${Fe.length}${_}${J}`, Fe.map((i) => ({ size: i.bikeSize, forkLength: i.forkLength, forkOffset: i.forkOffset }));
406    try {
407      const i = y?.manufacturerUrl, te = await ce.mutateAsync({ model: { brand: _, name: J, modelYear: Re ? parseInt(Re, 10) : void 0, bikeType: mt || void 0, sourceUrl: i || void 0 }, sizes: Fe });
408      let re = null;
409      try {
410        re = await te.json();
411      } catch {
412      }
413      if (b && re != null && re.id) try {
414        const fe = new FormData();
415        fe.append("file", b);
416        const he = await fetch(`/api/admin/competitor-database/models/${re.id}/geometry-image`, { method: "POST", body: fe });
417        he.ok ? (`${re.id}`, ne.invalidateQueries({ predicate: ($e) => typeof $e.queryKey[0] == "string" && $e.queryKey[0].includes("competitor-database") })) : console.warn("[CompetitorBatchImport] Failed to save geometry chart image:", await he.text());
418      } catch (fe) {
419        console.warn("[CompetitorBatchImport] Error uploading geometry chart image:", fe);
420      }
421      return re || { success: !0, sizesImported: Fe.length };
422    } catch (i) {
423      console.error("[CompetitorBatchImport] Import failed:", i);
424      const te = i instanceof Error ? i.message : "Unknown error";
425      let re = te;
426      try {
427        const fe = te.indexOf("{");
428        if (fe !== -1) {
429          const he = JSON.parse(te.slice(fe));
430          re = he.message || he.error || te;
431        }
432      } catch {
433      }
434      t({ title: "Import Failed", description: re.length > 200 ? re.slice(0, 200) + "\u2026" : re, variant: "destructive" });
435      return;
436    }
437  }, [ce, t, ne]), Lt = B.filter((s) => !(N && s.status !== "draft")).sort((s, b) => {
438    if (V === "year") return Ya(s, b);
439    if (V === "date") return new Date(b.createdAt).getTime() - new Date(s.createdAt).getTime();
440    if (V === "type") {
441      const y = (s.bikeType ?? "zzz").toLowerCase(), _ = (b.bikeType ?? "zzz").toLowerCase();
442      return y !== _ ? y.localeCompare(_) : `${s.brand} ${s.name}`.localeCompare(`${b.brand} ${b.name}`);
443    }
444    return `${s.brand} ${s.name}`.localeCompare(`${b.brand} ${b.name}`);
445  });
446  return e.jsxs("div", { className: "h-full flex flex-col", children: [e.jsxs("div", { className: "px-6 py-4 border-b border-border", children: [e.jsxs("div", { className: "flex items-center justify-between gap-4 mb-4", children: [e.jsxs("div", { className: "flex items-center gap-2", children: [e.jsx(sa, { className: "w-6 h-6 text-primary" }), e.jsx("h1", { className: "text-2xl font-bold", children: "Geometry Database Admin" })] }), e.jsxs("div", { className: "flex items-center gap-2", children: [e.jsxs($, { variant: "outline", onClick: () => n(!0), disabled: v.isPending || !!(C != null && C.running), "data-testid": "button-recalculate-all", children: [e.jsx(Pt, { className: `w-4 h-4 mr-2 ${v.isPending || C != null && C.running ? "animate-spin" : ""}` }), C != null && C.running ? `Recalculating\u2026 ${C.completed}/${C.total}` : v.isPending ? "Starting\u2026" : "Recalculate All"] }), e.jsxs(wa, { children: [e.jsx(Sa, { asChild: !0, children: e.jsxs($, { variant: "outline", disabled: ue.isPending || a?.running, "data-testid": "button-reaudit-menu", children: [ue.isPending || a != null && a.running ? e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }) : e.jsx(ot, { className: "w-4 h-4 mr-2" }), a != null && a.running ? `Re-Auditing\u2026 ${a.completed}/${a.total}` : ue.isPending ? "Starting\u2026" : "Re-Audit", e.jsx(ha, { className: "w-3.5 h-3.5 ml-1.5 opacity-60" })] }) }), e.jsxs(Ma, { align: "end", children: [z && e.jsxs(e.Fragment, { children: [e.jsx("div", { className: "px-2 py-1.5 flex items-center gap-1.5 text-xs text-muted-foreground select-none", children: z.running ? e.jsxs(e.Fragment, { children: [e.jsx(Z, { className: "w-3 h-3 animate-spin shrink-0" }), e.jsxs("span", { children: ["In progress: ", z.completed, "/", z.total, " models"] })] }) : e.jsxs(e.Fragment, { children: [e.jsx(Ct, { className: "w-3 h-3 shrink-0" }), e.jsxs("span", { children: ["Last run: ", z.completed, "/", z.total, " models complete"] })] }) }), e.jsx(ht, {})] }), e.jsxs(st, { "data-testid": "menu-reaudit-view-results", onClick: it, children: [e.jsx(Ct, { className: "w-4 h-4 mr-2" }), z != null && z.running ? "View Progress" : "View Last Results"] }), e.jsx(ht, {}), z && !z.running && z.completed > 0 && z.completed < z.total && e.jsxs(st, { "data-testid": "menu-reaudit-resume", onClick: () => {
447    c("all"), ge(!0);
448  }, className: "font-medium", children: [e.jsx(qa, { className: "w-4 h-4 mr-2 text-primary" }), "Resume", e.jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: ["(", z.total - z.completed, " remaining)"] })] }), e.jsxs(st, { "data-testid": "menu-reaudit-all", onClick: () => {
449    c("all"), ge(!0);
450  }, children: [e.jsx(ot, { className: "w-4 h-4 mr-2" }), "Run All", e.jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: "(skip today's)" })] }), e.jsxs(st, { "data-testid": "menu-reaudit-exceptions", onClick: () => {
451    c("exceptions"), ge(!0);
452  }, children: [e.jsx(wt, { className: "w-4 h-4 mr-2" }), "Run Last Exceptions"] }), e.jsx(ht, {}), e.jsxs(st, { "data-testid": "menu-reaudit-selected", onClick: () => {
453    c("selected"), G(!0);
454  }, children: [e.jsx(ya, { className: "w-4 h-4 mr-2" }), "Run Selected\u2026"] })] })] }), e.jsxs($, { onClick: () => k(!0), "data-testid": "button-import-model", children: [e.jsx(Ge, { className: "w-4 h-4 mr-2" }), "Import Model"] })] })] }), e.jsxs("div", { className: "flex items-center gap-1 mt-2 mb-3", 
454children: [e.jsxs("button", { className: `flex items-center gap-1.5 px-3 h-8 rounded-md text-sm font-medium transition-colors ${R === "models" ? "bg-primary text-white" : "text-muted-foreground hover:text-foreground hover:bg-muted"}`, onClick: () => K("models"), "data-testid": "tab-models", children: [e.jsx(sa, { className: "w-3.5 h-3.5" }), "Models"] }), e.jsxs("button", { className: `flex items-center gap-1.5 px-3 h-8 rounded-md text-sm font-medium transition-colors ${R === "report-card" ? "bg-primary text-white" : "text-muted-foreground hover:text-foreground hover:bg-muted"}`, onClick: () => K("report-card"), "data-testid": "tab-report-card", children: [e.jsx(Wa, { className: "w-3.5 h-3.5" }), "Report Card"] })] }), R === "models" && e.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [e.jsxs("div", { className: "relative flex-1 min-w-[200px] max-w-sm", children: [e.jsx(xa, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" }), e.jsx(ye, { placeholder: "Search brands or models...", value: m, onChange: (s) => h(s.target.value), className: "pl-10", "data-testid": "input-admin-search" })] }), e.jsxs(St, { value: T || "__all__", onValueChange: (s) => A(s === "__all__" ? null : s), children: [e.jsx(Mt, { className: "w-[160px]", "data-testid": "select-admin-brand", children: e.jsx(Tt, { placeholder: "All Brands" }) }), e.jsxs(Rt, { children: [e.jsx(be, { value: "__all__", children: "All Brands" }), w.map((s) => e.jsx(be, { value: s, children: s }, s))] })] }), e.jsxs(St, { value: V, onValueChange: (s) => S(s), children: [e.jsx(Mt, { className: "w-[160px]", "data-testid": "select-sort-by", children: e.jsx(Tt, { placeholder: "Sort by\u2026" }) }), e.jsxs(Rt, { children: [e.jsx(be, { value: "year", children: "Sort: Newest Model Year" }), e.jsx(be, { value: "brand", children: "Sort: Brand" }), e.jsx(be, { value: "date", children: "Sort: Date Created" }), e.jsx(be, { value: "type", children: "Sort: Bike Type" })] })] }), e.jsxs("div", { className: "flex items-center gap-2", children: [e.jsx($t, { checked: N, onCheckedChange: f, id: "draft-filter", "data-testid": "switch-draft-only" }), e.jsx(ie, { htmlFor: "draft-filter", className: "text-sm", children: "Draft Only" })] })] })] }), R === "models" ? e.jsx(At, { className: "flex-1", children: e.jsx("div", { className: "p-6 space-y-4", children: q ? e.jsx("div", { className: "flex items-center justify-center py-12", children: e.jsx(Z, { className: "w-8 h-8 animate-spin text-muted-foreground" }) }) : Lt.length === 0 ? e.jsx(ga, { children: e.jsx(fa, { className: "py-12 text-center text-muted-foreground", children: e.jsx("p", { children: "No competitor models found. Import your first model to get started." }) }) }) : Lt.map((s) => e.jsx(cs, { model: s, isExpanded: Q.has(s.id), onToggleExpand: () => ze(s.id), onToggleStatus: () => U.mutate({ modelId: s.id }), onDelete: () => X(s.id), isTogglingStatus: U.isPending }, s.id)) }) }) : e.jsx(Va, { onNavigateToModel: va }), H && e.jsx(Ea, { open: H, onOpenChange: k, onGeometryParsed: (s) => Et([s]), onBatchImport: Et, destination: "competitor-database" }), e.jsx(xt, { open: x, onOpenChange: n, children: e.jsxs(gt, { children: [e.jsxs(ft, { children: [e.jsx(pt, { children: "Recalculate All Models?" }), e.jsx(bt, { children: "This will re-run geometry calculations and AI analysis for every model in the database. It may take several minutes. The page will refresh automatically when complete." })] }), e.jsxs(jt, { children: [e.jsx(yt, { disabled: v.isPending, children: "Cancel" }), e.jsx(kt, { onClick: () => v.mutate(), disabled: v.isPending, "data-testid": "button-confirm-recalculate-all", children: v.isPending ? e.jsxs(e.Fragment, { children: [e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }), "Recalculating\u2026"] }) : "Recalculate All" })] })] }) }), e.jsx(xt, { open: Ue, onOpenChange: ge, children: e.jsxs(gt, { children: [e.jsxs(ft, { children: [e.jsx(pt, { children: Ne === "exceptions" ? "Re-Audit Models With Discrepancies?" : "Vision Re-Audit All Models?" }), e.jsx(bt, { children: Ne === "exceptions" ? "This will re-run the vision parser on any model whose most recent audit found flagged geometry differences \u2014 or whose run failed outright. Use this to verify whether extraction fixes resolved those discrepancies." : "This will re-run the vision parser on every model's geometry chart image and generate field-level diffs showing what has changed. Models already audited today are skipped. Results appear as each model completes." })] }), e.jsxs(jt, { children: [e.jsx(yt, { disabled: ue.isPending, children: "Cancel" }), e.jsx(kt, { onClick: () => ue.mutate({ mode: Ne }), disabled: ue.isPending, "data-testid": "button-confirm-reaudit", children: ue.isPending ? e.jsxs(e.Fragment, { children: [e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }), "Starting\u2026"] }) : Ne === "exceptions" ? e.jsxs(e.Fragment, { children: [e.jsx(wt, { className: "w-4 h-4 mr-2" }), "Run Last Exceptions"] }) : e.jsxs(e.Fragment, { children: [e.jsx(ot, { className: "w-4 h-4 mr-2" }), "Re-Audit All"] }) })] })] }) }), e.jsx(De, { open: !!M, onOpenChange: (s) => !s && E(null), children: e.jsxs(Oe, { children: [e.jsxs(Ie, { children: [e.jsxs(Pe, { className: "flex items-center gap-2", children: [e.jsx(je, { className: "w-5 h-5 text-yellow-500" }), "Advisory: Missing Geometry Dimensions"] }), e.jsx(Ee, { children: "This model can still be published, but the following issues were detected. Provide a brief reason to confirm you are aware and proceeding intentionally." })] }), e.jsxs("div", { className: "space-y-3 py-2", children: [e.jsx("ul", { className: "list-disc list-inside text-sm text-muted-foreground space-y-1", children: M?.reasons.map((s, b) => e.jsx("li", { children: s }, b)) }), e.jsxs("div", { className: "space-y-1", children: [e.jsxs(ie, { htmlFor: "override-reason", className: "text-sm font-medium", children: ["Override reason ", e.jsx("span", { className: "text-destructive", children: "*" })] }), e.jsx(pa, { id: "override-reason", placeholder: "e.g. Manufacturer does not publish ETT \u2014 this is expected for this brand.", value: M?.overrideReason ?? "", onChange: (s) => E((b) => b ? { ...b, overrideReason: s.target.value } : null), rows: 3 })] })] }), e.jsxs(We, { children: [e.jsx($, { variant: "outline", onClick: () => E(null), children: "Cancel" }), e.jsx($, { disabled: !((Bt = M?.overrideReason) != null && Bt.trim()) || U.isPending, onClick: () => {
455    var s;
456    (s = M?.overrideReason) != null && s.trim() && U.mutate({ modelId: M.modelId, overrideReason: M.overrideReason.trim() }, { onSuccess: () => E(null) });
457  }, children: U.isPending ? e.jsxs(e.Fragment, { children: [e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }), "Publishing\u2026"] }) : "Publish Anyway" })] })] }) }), e.jsx(xt, { open: !!F, onOpenChange: (s) => !s && X(null), children: e.jsxs(gt, { children: [e.jsxs(ft, { children: [e.jsx(pt, { children: "Delete Competitor Model?" }), e.jsx(bt, { children: "This will permanently delete the model and all its sizes. This action cannot be undone." })] }), e.jsxs(jt, { children: [e.jsx(yt, { children: "Cancel" }), e.jsx(kt, { onClick: () => F && Y.mutate(F), className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Delete" })] })] }) }), O && e.jsx(os, { models: Be, selectedIds: me, onChangeIds: le, isPending: ue.isPending, onConfirm: () => ue.mutate({ mode: "selected", modelIds: [...me] }), onClose: () => {
458    G(!1), le(/* @__PURE__ */ new Set());
459  } }), a && !xe && e.jsx(ns, { status: a, onDismiss: ct, onReauditFailed: (s) => {
460    le(new Set(s)), G(!0);
461  } }), C && !l && e.jsx(ds, { progress: C, onDismiss: rt })] });
462}
463function ns({ status: t, onDismiss: m, onReauditFailed: h }) {
464  const { toast: T } = He(), [A, N] = d.useState(null), [f, V] = d.useState(null), [S, R] = d.useState(!1), [K, Q] = d.useState(/* @__PURE__ */ new Map()), L = d.useRef(null), F = d.useMemo(() => t.results.map((r) => K.get(r.modelId) ?? r), [t.results, K]), X = t.total > 0 ? Math.round(t.completed / t.total * 100) : 0, H = F.filter((r) => r.flaggedDiffs > 0 && r.status === "done"), k = F.filter((r) => r.status === "failed"), x = F.find((r) => r.status === "running"), n = async (r, p, w) => {
465    if (!["image/png", "image/jpeg", "image/webp", "application/pdf"].includes(r.type)) {
466      T({ title: "Invalid File", description: "Only PNG, JPG, WebP, or PDF files are allowed.", variant: "destructive" });
467      return;
468    }
469    if (r.size > 20 * 1024 * 1024) {
470      T({ title: "File Too Large", description: "Maximum file size is 20MB.", variant: "destructive" });
471      return;
472    }
473    R(!0), Q((q) => {
474      const M = new Map(q), E = t.results.find((U) => U.modelId === p) ?? { modelId: p, brand: "", name: w, status: "failed", flaggedDiffs: 0, totalDiffs: 0, runI
474d: null, errorMsg: null };
475      return M.set(p, { ...E, status: "running", errorMsg: null }), M;
476    });
477    try {
478      const q = new FormData();
479      q.append("file", r);
480      const M = await fetch(`/api/admin/competitor-database/models/${p}/geometry-image`, { method: "POST", credentials: "include", body: q });
481      if (!M.ok) {
482        const Y = await M.json().catch(() => ({ error: "Upload failed" }));
483        throw new Error(Y.error || "Upload failed");
484      }
485      const E = await fetch(`/api/admin/competitor-database/models/${p}/reaudit`, { method: "POST", credentials: "include" });
486      if (!E.ok) {
487        const Y = await E.json().catch(() => ({ error: "Re-audit failed" }));
488        throw new Error(Y.error || "Re-audit failed");
489      }
490      const U = await E.json();
491      Q((Y) => {
492        const ce = new Map(Y), C = t.results.find((ee) => ee.modelId === p);
493        return ce.set(p, { ...C, status: "done", errorMsg: null, flaggedDiffs: U.flaggedDiffs ?? 0, totalDiffs: U.totalDiffs ?? 0, runId: U.runId ?? null }), ce;
494      }), V(null), T({ title: "Re-Audit Complete", description: U.flaggedDiffs > 0 ? `${w}: ${U.flaggedDiffs} diff${U.flaggedDiffs !== 1 ? "s" : ""} found \u2014 click the row to review.` : `${w}: no geometry changes detected.` });
495    } catch (q) {
496      Q((M) => {
497        const E = new Map(M), U = t.results.find((Y) => Y.modelId === p);
498        return E.set(p, { ...U, status: "failed", errorMsg: q.message }), E;
499      }), T({ title: "Repair Failed", description: q.message, variant: "destructive" });
500    } finally {
501      R(!1);
502    }
503  }, o = (r) => r.status === "running" ? e.jsx(Z, { className: "w-4 h-4 animate-spin text-primary shrink-0" }) : r.status === "done" && r.flaggedDiffs > 0 ? e.jsx(je, { className: "w-4 h-4 text-amber-500 shrink-0" }) : r.status === "done" ? e.jsx(Ae, { className: "w-4 h-4 text-emerald-500 shrink-0" }) : r.status === "skipped" ? e.jsx(Ae, { className: "w-4 h-4 text-muted-foreground shrink-0" }) : r.status === "failed" ? e.jsx(qe, { className: "w-4 h-4 text-destructive shrink-0" }) : e.jsx("div", { className: "w-4 h-4 rounded-full border border-border shrink-0" }), u = f ? F.find((r) => r.modelId === f) : null;
504  return e.jsxs(e.Fragment, { children: [e.jsx("input", { ref: L, type: "file", accept: "image/png,image/jpeg,image/webp,application/pdf", className: "sr-only", onChange: (r) => {
505    var w;
506    const p = (w = r.target.files) == null ? void 0 : w[0];
507    p && f && u && n(p, f, `${u.brand} ${u.name}`.trim()), r.target.value = "";
508  } }), e.jsx(De, { open: !0, onOpenChange: (r) => {
509    r || m();
510  }, children: e.jsxs(Oe, { className: "max-w-2xl w-full", "data-testid": "dialog-reaudit-progress", children: [e.jsxs(Ie, { children: [e.jsx(Pe, { className: "flex items-center gap-2", children: t.running ? e.jsxs(e.Fragment, { children: [e.jsx(Z, { className: "w-5 h-5 animate-spin text-primary" }), " Vision Re-Audit in Progress"] }) : H.length > 0 || k.length > 0 ? e.jsxs(e.Fragment, { children: [e.jsx(je, { className: "w-5 h-5 text-amber-500" }), " Re-Audit Complete \u2014 Review Flagged Diffs"] }) : e.jsxs(e.Fragment, { children: [e.jsx(Ae, { className: "w-5 h-5 text-emerald-500" }), " Re-Audit Complete \u2014 All Clear"] }) }), e.jsx(Ee, { children: t.running ? x ? `Processing ${x.brand} ${x.name} \u2014 ${t.completed} of ${t.total} complete` : `${t.completed} of ${t.total} models processed` : `${t.completed} models audited${t.failed > 0 ? `, ${t.failed} failed` : ""}.${H.length > 0 ? ` ${H.length} model${H.length !== 1 ? "s" : ""} have diffs that need review.` : " No geometry changes detected."}` })] }), e.jsxs("div", { className: "space-y-1", children: [e.jsxs("div", { className: "flex justify-between text-xs text-muted-foreground", children: [e.jsxs("span", { children: [t.completed, " / ", t.total, " models"] }), e.jsxs("span", { children: [X, "%"] })] }), e.jsx("div", { className: "h-2 bg-muted rounded-full overflow-hidden", children: e.jsx("div", { className: "h-full bg-primary rounded-full transition-all duration-700", style: { width: `${X}%` } }) }), t.failed > 0 && e.jsxs("p", { className: "text-xs text-destructive", children: [t.failed, " model", t.failed !== 1 ? "s" : "", " failed \u2014 click a failed row to replace its chart image"] })] }), e.jsx(At, { className: "h-72 border border-border rounded-md", children: e.jsx("div", { className: "divide-y divide-border", children: (t.running ? F : [...F].sort((r, p) => {
511    const w = (B) => B.status === "done" && B.flaggedDiffs > 0 ? 0 : B.status === "failed" ? 1 : 2;
512    return w(r) - w(p);
513  })).map((r) => e.jsxs("div", { children: [e.jsxs("div", { className: `flex items-center gap-3 px-3 py-3 ${r.status === "running" ? "bg-primary/5" : r.status === "done" && r.flaggedDiffs > 0 ? "bg-amber-50 dark:bg-amber-950/20 cursor-pointer hover-elevate" : r.status === "failed" ? `cursor-pointer hover-elevate ${f === r.modelId ? "bg-blue-50 dark:bg-blue-950/20" : ""}` : ""}`, onClick: () => {
514    r.status === "done" && r.flaggedDiffs > 0 ? N({ id: r.modelId, name: `${r.brand} ${r.name}`.trim() }) : r.status === "failed" && V((p) => p === r.modelId ? null : r.modelId);
515  }, "data-testid": `reaudit-row-${r.modelId}`, children: [o(r), e.jsxs("div", { className: "flex-1 min-w-0", children: [e.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [e.jsxs("span", { className: `text-sm font-medium ${r.status === "pending" ? "text-muted-foreground" : ""}`, children: [r.brand, " ", r.name] }), r.status === "done" && r.flaggedDiffs > 0 && e.jsxs(pe, { className: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300 border-amber-300 dark:border-amber-700 text-xs", children: [r.flaggedDiffs, " diff", r.flaggedDiffs !== 1 ? "s" : "", " \u2014 click to review"] }), r.status === "done" && r.flaggedDiffs === 0 && r.totalDiffs === 0 && e.jsx("span", { className: "text-xs text-emerald-600 dark:text-emerald-400", children: "no changes" }), r.status === "done" && r.flaggedDiffs === 0 && r.totalDiffs > 0 && e.jsxs("span", { className: "text-xs text-muted-foreground", children: [r.totalDiffs, " minor diff", r.totalDiffs !== 1 ? "s" : ""] }), r.status === "failed" && e.jsx(pe, { className: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 border-blue-300 dark:border-blue-700 text-xs", children: f === r.modelId ? "cancel \u2191" : "click to replace image" })] }), r.status === "failed" && r.errorMsg && f !== r.modelId && e.jsx("p", { className: "text-xs text-destructive truncate", children: r.errorMsg })] }), e.jsxs("div", { className: "shrink-0 text-muted-foreground", children: [r.status === "pending" && e.jsx("span", { className: "text-xs", children: "queued" }), r.status === "running" && e.jsx("span", { className: "text-xs text-primary", children: "auditing\u2026" }), r.status === "done" && r.flaggedDiffs > 0 && e.jsx(ja, { className: "w-4 h-4 text-amber-500" }), r.status === "failed" && f !== r.modelId && e.jsx(Ge, { className: "w-4 h-4 text-blue-500" })] })] }), f === r.modelId && e.jsx("div", { className: "mx-3 mb-3 border-2 border-dashed border-blue-300 dark:border-blue-700 rounded-md p-4 bg-blue-50/50 dark:bg-blue-950/10", onDragOver: (p) => p.preventDefault(), onDrop: (p) => {
516    p.preventDefault();
517    const w = p.dataTransfer.files[0];
518    w && n(w, r.modelId, `${r.brand} ${r.name}`.trim());
519  }, children: S ? e.jsxs("div", { className: "flex items-center gap-2 justify-center py-2 text-sm text-muted-foreground", children: [e.jsx(Z, { className: "w-4 h-4 animate-spin" }), "Uploading chart and re-auditing\u2026"] }) : e.jsxs("div", { className: "text-center space-y-2", children: [e.jsxs("p", { className: "text-xs text-muted-foreground", children: ["Chart image for ", e.jsxs("span", { className: "font-medium text-foreground", children: [r.brand, " ", r.name] }), " is missing. Upload a replacement to re-audit immediately."] }), e.jsxs("div", { className: "flex items-center justify-center gap-2", children: [e.jsxs($, { size: "sm", variant: "outline", className: "gap-1.5 text-blue-700 border-blue-300 hover:bg-blue-100 dar
519k:text-blue-400 dark:border-blue-700 dark:hover:bg-blue-900/30", onClick: () => {
520    var p;
521    return (p = L.current) == null ? void 0 : p.click();
522  }, children: [e.jsx(Ge, { className: "w-3.5 h-3.5" }), "Choose File"] }), e.jsx("span", { className: "text-xs text-muted-foreground", children: "or drag & drop here" })] }), e.jsx("p", { className: "text-xs text-muted-foreground/70", children: "PNG, JPG, WebP, or PDF \xB7 max 20 MB" })] }) })] }, r.modelId)) }) }), e.jsx(We, { children: t.running ? e.jsx("p", { className: "text-xs text-muted-foreground mr-auto", children: "Audit is running in the background and will complete automatically." }) : e.jsxs("div", { className: "flex items-center gap-2 w-full", children: [H.length > 0 && !k.length && e.jsx("p", { className: "text-xs text-muted-foreground mr-auto", children: "Click any highlighted row above to inspect and approve or reject each diff." }), k.length > 0 && h && e.jsxs($, { variant: "outline", size: "sm", className: "gap-1.5 text-blue-700 border-blue-300 hover:bg-blue-100 dark:text-blue-400 dark:border-blue-700 dark:hover:bg-blue-900/30", onClick: () => {
523    h(k.map((r) => r.modelId)), m();
524  }, children: [e.jsx(Pt, { className: "w-3.5 h-3.5" }), "Re-Audit ", k.length, " Failed Model", k.length !== 1 ? "s" : ""] }), e.jsx($, { onClick: m, "data-testid": "button-close-reaudit-dialog", className: "ml-auto", children: "Dismiss" })] }) })] }) }), A && e.jsx(Na, { open: !0, onOpenChange: () => N(null), modelId: A.id, modelName: A.name, hasImage: !0 })] });
525}
526const ls = [{ status: "green", icon: Ae, label: "0\u20132mm", chipCls: "border-emerald-300 text-emerald-700 dark:border-emerald-700 dark:text-emerald-400", activeCls: "bg-emerald-100 dark:bg-emerald-900/40 border-emerald-400 dark:border-emerald-500" }, { status: "yellow", icon: je, label: "2\u20135mm", chipCls: "border-amber-300 text-amber-700 dark:border-amber-700 dark:text-amber-400", activeCls: "bg-amber-100 dark:bg-amber-900/40 border-amber-400 dark:border-amber-500" }, { status: "red", icon: Aa, label: ">5mm", chipCls: "border-red-300 text-red-700 dark:border-red-700 dark:text-red-400", activeCls: "bg-red-100 dark:bg-red-900/40 border-red-400 dark:border-red-500" }, { status: "purple", icon: Ga, label: "Incomplete", chipCls: "border-purple-300 text-purple-700 dark:border-purple-700 dark:text-purple-400", activeCls: "bg-purple-100 dark:bg-purple-900/40 border-purple-400 dark:border-purple-500" }];
527function os({ models: t, selectedIds: m, onChangeIds: h, isPending: T, onConfirm: A, onClose: N }) {
528  const [f, V] = d.useState(""), [S, R] = d.useState(null), K = { green: t.filter((n) => n.overallConsistency === "green").length, yellow: t.filter((n) => n.overallConsistency === "yellow").length, red: t.filter((n) => n.overallConsistency === "red").length, purple: t.filter((n) => n.overallConsistency === "purple").length }, Q = (n) => {
529    const o = t.filter((p) => p.overallConsistency === n), u = S === n;
530    R(u ? null : n);
531    const r = new Set(m);
532    u ? o.forEach((p) => r.delete(p.id)) : o.forEach((p) => r.add(p.id)), h(r);
533  }, L = t.filter((n) => {
534    const o = !f.trim() || `${n.brand ?? ""} ${n.name}`.toLowerCase().includes(f.toLowerCase()), u = !S || n.overallConsistency === S;
535    return o && u;
536  }), F = L.length > 0 && L.every((n) => m.has(n.id)), X = L.some((n) => m.has(n.id)), H = () => {
537    const n = new Set(m);
538    F ? L.forEach((o) => n.delete(o.id)) : L.forEach((o) => n.add(o.id)), h(n);
539  }, k = (n) => {
540    const o = new Set(m);
541    o.has(n) ? o.delete(n) : o.add(n), h(o);
542  }, x = L.reduce((n, o) => {
543    const u = o.brand ?? "Unknown";
544    return n[u] || (n[u] = []), n[u].push(o), n;
545  }, {});
546  return e.jsx(De, { open: !0, onOpenChange: (n) => {
547    n || N();
548  }, children: e.jsxs(Oe, { className: "max-w-lg w-full", "data-testid": "dialog-reaudit-picker", children: [e.jsxs(Ie, { children: [e.jsxs(Pe, { className: "flex items-center gap-2", children: [e.jsx(ya, { className: "w-5 h-5" }), "Run Selected \u2014 Choose Models"] }), e.jsx(Ee, { children: "Select which models to re-audit. Only models with a geometry chart image are listed." })] }), e.jsxs("div", { className: "relative", children: [e.jsx(xa, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" }), e.jsx("input", { className: "w-full pl-9 pr-3 py-2 text-sm border border-border rounded-md bg-background focus:outline-none focus:ring-1 focus:ring-ring", placeholder: "Filter models\u2026", value: f, onChange: (n) => V(n.target.value), "data-testid": "input-picker-search" })] }), e.jsxs("div", { className: "flex items-center gap-1.5 flex-wrap", children: [ls.map(({ status: n, icon: o, label: u, chipCls: r, activeCls: p }) => {
549    const w = K[n], B = S === n;
550    return e.jsxs("button", { onClick: () => Q(n), "data-testid": `picker-status-filter-${n}`, title: B ? `Deselect all ${u} models` : `Select all ${u} models`, className: `inline-flex items-center gap-1 px-2 py-1 rounded-full border text-xs font-medium transition-colors ${r} ${B ? p : "bg-background hover:bg-muted"}`, children: [e.jsx(o, { className: "w-3 h-3 shrink-0" }
550), u, e.jsxs("span", { className: "opacity-70", children: ["(", w, ")"] })] }, n);
551  }), S && e.jsxs("button", { onClick: () => R(null), title: "Clear status filter (keeps current selection)", className: "inline-flex items-center gap-1 px-2 py-1 rounded-full border border-border text-xs text-muted-foreground hover:bg-muted transition-colors", children: [e.jsx(qe, { className: "w-3 h-3" }), " Clear filter"] })] }), e.jsxs("div", { className: "flex items-center gap-2 py-1 border-b border-border", children: [e.jsx(ra, { id: "picker-select-all", checked: F ? !0 : X ? "indeterminate" : !1, onCheckedChange: H, "data-testid": "checkbox-select-all" }), e.jsxs("label", { htmlFor: "picker-select-all", className: "text-sm font-medium cursor-pointer select-none", children: [F ? "Deselect all" : "Select all", f || S ? " (filtered)" : ""] }), e.jsxs("span", { className: "ml-auto text-xs text-muted-foreground", children: [m.size, " / ", t.length, " selected"] })] }), e.jsx(At, { className: "h-72 -mx-1 px-1", children: t.length === 0 ? e.jsx("p", { className: "text-sm text-muted-foreground text-center py-8", children: "Loading models\u2026" }) : L.length === 0 ? e.jsxs("p", { className: "text-sm text-muted-foreground text-center py-8", children: ['No models match "', f, '"'] }) : Object.entries(x).sort(([n], [o]) => n.localeCompare(o)).map(([n, o]) => e.jsxs("div", { className: "mb-3", children: [e.jsx("p", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider px-1 mb-1", children: n }), e.jsx("div", { className: "space-y-0.5", children: o.map((u) => e.jsxs("div", { className: `flex items-center gap-3 px-2 py-2 rounded-md cursor-pointer hover-elevate ${m.has(u.id) ? "bg-primary/5" : ""}`, onClick: () => k(u.id), "data-testid": `picker-row-${u.id}`, children: [e.jsx(ra, { id: `picker-${u.id}`, checked: m.has(u.id), onCheckedChange: () => k(u.id), onClick: (r) => r.stopPropagation() }), e.jsx("label", { htmlFor: `picker-${u.id}`, className: "text-sm cursor-pointer select-none flex-1", children: u.name })] }, u.id)) })] }, n)) }), e.jsxs(We, { children: [e.jsx($, { variant: "outline", onClick: N, disabled: T, children: "Cancel" }), e.jsx($, { onClick: A, disabled: m.size === 0 || T, "data-testid": "button-confirm-picker", children: T ? e.jsxs(e.Fragment, { children: [e.jsx(Z, { className: "w-4 h-4 mr-2 animate-spin" }), "Starting\u2026"] }) : e.jsxs(e.Fragment, { children: [e.jsx(ot, { className: "w-4 h-4 mr-2" }), "Run ", m.size, " Model", m.size !== 1 ? "s" : ""] }) })] })] }) });
552}
553function ds({ progress: t, onDismiss: m }) {
554  const h = t.total > 0 ? Math.round(t.completed / t.total * 100) : 0;
555  return e.jsx(De, { open: !0, onOpenChange: (T) => {
556    T || m();
557  }, children: e.jsxs(Oe, { className: "max-w-md w-full", "data-testid": "dialog-recalculate-progress", children: [e.jsxs(Ie, { children: [e.jsx(Pe, { className: "flex items-center gap-2", children: t.running ? e.jsxs(e.Fragment, { children: [e.jsx(Z, { className: "w-5 h-5 animate-spin text-primary" }), " Recalculating Geometry\u2026"] }) : e.jsxs(e.Fragment, { children: [e.jsx(Ae, { className: "w-5 h-5 text-emerald-500" }), " Recalculation Complete"] }) }), e.jsx(Ee, { children: t.running ? `Running geometry engine and AI analysis on all models. ${t.completed} of ${t.total} done.` : `${t.completed} model${t.completed !== 1 ? "s" : ""} recalculated${t.failed > 0 ? `, ${t.failed} with errors` : " successfully"}.` })] }), e.jsxs("div", { className: "space-y-2", children: [e.jsxs("div", { className: "flex justify-between text-xs text-muted-foreground", children: [e.jsxs("span", { children: [t.completed, " / ", t.total, " models"] }), e.jsxs("span", { children: [h, "%"] })] }), e.jsx("div", { className: "h-2 bg-muted rounded-full overflow-hidden", children: e.jsx("div", { className: "h-full bg-primary rounded-full transition-all duration-700", style: { width: `${h}%` } }) }), t.failed > 0 && e.jsxs("p", { className: "text-xs text-destructive", children: [t.failed, " model", t.failed !== 1 ? "s" : "", " encountered errors during recalculation."] })] }), e.jsx(We, { children: t.running ? e.jsx("p", { className: "text-xs text-muted-foreground", children: "Recalculation runs in the background \u2014 the model list updates automatically when complete." }) : e.jsx($, { onClick: m, "data-testid": "button-close-recalculate-dialog", children: "Done" }) })] }) });
558}
559function cs({ model: t, isExpanded: m, onToggleExpand: h, onToggleStatus: T, onDelete: A, isTogglingStatus: N }) {
560  var v;
561  const { toast: f } = He(), [V, S] = d.useState(!1), [R, K] = d.useState(!1), [Q, L] = d.useState(!1), [F, X] = d.useState(!1), [H, k] = d.useState(!1), [x, n] = d.useState(1), { data: o = [], isLoading: u } = Ve({ queryKey: ["/api/admin/competitor-database/models", t.id, "sizes"], queryFn: async () => {
562    const a = await fetch(`/api/admin/competitor-database/models/${t.id}/sizes`, { credentials: "include" });
563    if (!a.ok) throw new Error("Failed to fetch sizes");
564    return a.json();
565  }, enabled: m }), r = d.useMemo(() => [...o].sort((a, D) => (a.effectiveTopTube ?? 1 / 0) - (D.effectiveTopTube ?? 1 / 0)), [o]), p = d.useMemo(() => r.some((a) => a.isFlipChip && a.forkOffset2 != null), [r]), w = m && !u && o.length > 0 ? La(o) : [], B = w.some((a) => a.key === "forkLength"), q = t.isManufacturerIncomplete || B, M = m && !u && o.length > 0 ? w.map((a) => a.label) : t.missingManufacturerDimensions ?? [], Y = ["fork axle-to-crown length (A2C)", "fork offset", "BB drop"].every((a) => M.includes(a)) && (m && !u && o.length > 0 ? o.every((a) => a.forkOffsetSource === "not-published" || a.forkOffsetSource == null) : !0), ce = Y ? ["front center", "fork axle-to-crown length (A2C)", "BB drop", "fork offset", "trail"] : M, C = m && !u && o.length > 0 ? Ba(o) : null, ee = t.status === "draft" && C != null && !C.canPublish;
566  ke({ mutationFn: async ({ sizeId: a, data: D }) => se("PATCH", `/api/admin/competitor-database/sizes/${a}`, D), onSuccess: () => {
567    ne.invalidateQueries({ predicate: (a) => typeof a.queryKey[0] == "string" && a.queryKey[0].includes("competitor-database") });
568  }, onError: () => {
569    f({ title: "Error", description: "Failed to update size.", variant: "destructive" });
570  } });
571  const l = ke({ mutationFn: async (a) => {
572    await Promise.all(a.map((D) => se("PATCH", `/api/admin/competitor-database/sizes/${D.sizeId}`, D.data)));
573  }, onSuccess: () => {
574    ne.invalidateQueries({ predicate: (a) => typeof a.queryKey[0] == "string" && a.queryKey[0].includes("competitor-database") }), f({ title: "Geometry Saved", description: "All geometry values have been updated." }), S(!1), t.status === "published" && j.mutate();
575  }, onError: () => {
576    f({ title: "Error", description: "Failed to save geometry changes.", variant: "destructive" });
577  } }), j = ke({ mutationFn: async () => (await se("POST", `/api/admin/competitor-database/models/${t.id}/process`)).json(), onSuccess: (a) => {
578    ne.invalidateQueries({ predicate: (D) => typeof D.queryKey[0] == "string" && D.queryKey[0].includes("competitor-database") }), f({ title: "Geometry Recalculated", description: `${a.processed} sizes processed${a.failed ? `, ${a.failed} failed` : ""}.` });
579  }, onError: () => {
580    f({ title: "Error", description: "Failed to recalculate geometry.", variant: "destructive" });
581  } }), g = ke({ mutationFn: async (a) => se("PATCH", `/api/admin/competitor-database/models/${t.id}`, a), onSuccess: () => {
582    ne.invalidateQueries({ predicate: (a) => typeof a.queryKey[0] == "string" && a.queryKey[0].includes("competitor-database") }), f({ title: "Model Updated", description: "Model details have been saved." }), K(!1);
583  }, onError: () => {
584    f({ title: "Error", description: "Failed to update model.", variant: "destructive" });
585  } });
586  return e.jsxs(e.Fragment, { children: [e.jsx(ga, { "data-testid": `card-admin-model-${t.id}`, children: e.jsxs(Da, { open: m, onOpenChange: h, children: [e.jsx(Oa, { asChild: !0, children: e.jsx(Ta, { className: "cursor-pointer hover-elevate py-3 px-4", children: e.jsxs("div", { className: "flex items-center justify-between gap-4", children: [e.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [e.jsx(Ra, { className: "w-5 h-5 text-muted-foreground flex-shrink-0" }), e.jsx("div", { className: "min-w-0", children: e.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [e.jsxs(Fa, { className: "text-base font-semibold", children: [t.modelYear ? `${t.modelYear} ` : "", t.brand, " ", t.name] }), t.bikeType && e.jsx(pe, { variant: "outline", className: "text-xs", children: t.bikeType }), e.jsx(pe, { variant: t.status === "published" ? "default" : "secondary", className: "text-xs", children: t.status === "published" ? e.jsxs(e.Fragment, { children: [e.jsx(ia, { className: "w-3 h-3 mr-1" }), " Published"] }) : e.jsxs(e.Fragment, { children: [e.jsx(na, { className: "w-3 h-3 mr-1" }), " Draft"] }) }), t.dimensionConflict && !t.dimensionConflict.resolution && e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsxs("span", { className: "inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-semibold cursor-default select-none", style: { background: "rgba(202,138,4,0.12)", color: "#b45309", border: "1px solid rgba(202,138,4,0.4)" }, "data-testid": `badge-admin-dimension-conflict-${t.id}`, children: [e.jsx(wt, { className: "w-3 h-3 flex-shrink-0" }), "Dimension Conflict"] }) }), e.jsxs(Se, { side: "bottom", className: "max-w-xs", children: [e.jsx("p", { className: "font-semibold text-xs mb-1", children: "Geometry Inconsistency \u2014 Review Required" }), e.jsxs("p", { className: "text-xs leading-relaxed text-muted-foreground", children: [t.dimensionConflict.outlierLabel, " \u2014 geometric inconsistency detected coordinate dimensions (Reach, WB, FC) by >5mm in", " ", t.dimensionConflict.conflictingSizes.length, " size", t.dimensionConflict.conflictingSizes.length !== 1 ? "s" : "", ". Open the audit dialog to override or dismiss."] })] })] }), q && e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsxs("span", { className: "inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-semibold cursor-default select-none", style: { background: "rgba(220,38,38,0.12)", color: "#dc2626", border: "1px solid rgba(220,38,38,0.35)" }, "data-testid": `badge-admin-manufacturer-incomplete-${t.id}`, children: [e.jsx(je, { className: "w-3 h-3 flex-shrink-0" }), "Incomplete Manufacturer Data"] }) }), e.jsxs(Se, { side: "bottom", className: "max-w-sm", children: [e.jsx("p", { className: "font-semibold text-xs mb-1", children: "Manufacturer Has Not Published Sufficient Geometry" }), e.jsxs("p", { className: "text-xs leading-relaxed text-muted-foreground", children: [Y ? "Five dimensions are absent from this manufacturer's published specification: front center, fork axle-to-crown length (A2C), BB drop, fork offset, trail." : (t.missingManufacturerDimensions ?? []).length > 0 ? `Missing from manufacturer's spec: ${(t.missingManufacturerDimensions ?? []).join(", ")}.` : "Critical dimensions are absent from this manufacturer's published specifications.", " ", "Stability index and agility index cannot be calculated, so these sizes cannot be added to Garage or used for c
586omparison. The model can still be published and audited \u2014 the audit icon reflects real findings."] })] })] }), e.jsx(ma, { severity: t.overallConsistency, size: "md" }), e.jsx("span", { className: "text-xs text-muted-foreground ml-2", children: new Date(t.createdAt).toLocaleDateString() }), (t.frontTireRange || t.rearTireRange || t.specTireMm != null) && e.jsxs("span", { className: "text-xs text-muted-foreground flex items-center gap-2 ml-1", children: [t.frontTireRange && e.jsxs("span", { children: ["Max F: ", e.jsxs("span", { className: "font-medium text-foreground", children: [t.frontTireRange.max, "mm"] })] }), t.rearTireRange && e.jsxs("span", { children: ["Max R: ", e.jsxs("span", { className: "font-medium text-foreground", children: [t.rearTireRange.max, "mm"] })] }), t.specTireMm != null && e.jsxs("span", { children: ["RT: ", e.jsxs("span", { className: "font-medium text-foreground", children: [t.specTireMm, "mm"] })] })] })] }) })] }), e.jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx($, { size: "icon", variant: "ghost", onClick: (a) => {
587    a.stopPropagation(), K(!0);
588  }, "data-testid": `button-edit-model-${t.id}`, children: e.jsx(la, { className: "w-4 h-4" }) }) }), e.jsx(Se, { children: "Edit Model Details" })] }), e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx("span", { children: e.jsx($, { size: "icon", variant: "ghost", onClick: (a) => {
589    a.stopPropagation(), T();
590  }, disabled: N || ee, "data-testid": `button-toggle-status-${t.id}`, children: t.status === "published" ? e.jsx(na, { className: "w-4 h-4" }) : e.jsx(ia, { className: `w-4 h-4 ${ee ? "text-muted-foreground" : ""}` }) }) }) }), e.jsx(Se, { children: t.status === "published" ? "Unpublish" : ee && (v = C?.hardBlockReasons) != null && v.length ? `Cannot publish: ${C.hardBlockReasons.join(" ")}` : "Publish" })] }), e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx($, { size: "icon", variant: "ghost", onClick: (a) => {
591    a.stopPropagation(), j.mutate();
592  }, disabled: j.isPending, "data-testid": `button-reprocess-${t.id}`, children: e.jsx(Pt, { className: `w-4 h-4 ${j.isPending ? "animate-spin" : ""}` }) }) }), e.jsx(Se, { children: "Recalculate Geometry" })] }), e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx($, { size: "icon", variant: "ghost", onClick: (a) => {
593    a.stopPropagation(), m || h(), L(!0);
594  }, "data-testid": `button-audit-${t.id}`, children: e.jsx(Ct, { className: "w-4 h-4" }) }) }), e.jsx(Se, { children: "View Audit Report" })] }), e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx($, { size: "icon", variant: "ghost", onClick: (a) => {
595    a.stopPropagation(), X(!0);
596  }, "data-testid": `button-reaudit-${t.id}`, children: e.jsx(dt, { className: "w-4 h-4" }) }) }), e.jsx(Se, { children: "Vision Re-Audit" })] }), e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx($, { size: "icon", variant: "ghost", onClick: (a) => {
597    a.stopPropagation(), k(!0);
598  }, "data-testid": `button-change-history-${t.id}`, children: e.jsx(Ft, { className: "w-4 h-4" }) }) }), e.jsx(Se, { children: "Change History" })] }), e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx($, { size: "icon", variant: "ghost", onClick: (a) => {
599    a.stopPropagation(), A();
600  }, "data-testid": `button-delete-${t.id}`, children: e.jsx(ba, { className: "w-4 h-4 text-destructive" }) }) }), e.jsx(Se, { children: "Delete Model" })] }), m ? e.jsx($a, { className: "w-4 h-4 text-muted-foreground" }) : e.jsx(ha, { className: "w-4 h-4 text-muted-foreground" })] })] }) }) }), e.jsx(Ia, { children: e.jsx(fa, { className: "pt-0 pb-4", children: u ? e.jsx("div", { className: "flex items-center justify-center py-8", children: e.jsx(Z, { className: "w-6 h-6 animate-spin text-muted-foreground" }) }) : o.length === 0 ? e.jsx("p", { className: "text-sm text-muted-foreground text-center py-4", children: "No sizes imported" }) : e.jsxs(e.Fragment, { children: [q && e.jsxs("div", { className: "flex items-start gap-3 rounded-lg p-3 mb-4", style: { background: "rgba(220,38,38,0.08)", border: "1px solid rgba(220,38,38,0.35)" }, "data-testid": `banner-manufacturer-incomplete-${t.id}`, children: [e.jsx(je, { className: "w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" }), e.jsxs("div", { className: "space-y-1 min-w-0", children: [e.jsx("p", { className: "text-sm font-semibold text-red-600 dark:text-red-400", children: "Manufacturer has not published sufficient geometry data" }), e.jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: [ce.length > 0 ? e.jsxs(e.Fragment, { children: ["The following dimensions are absent from this manufacturer's published specification: ", e.jsx("strong", { children: ce.join(", ") }), "."] }) : "Critical geometry dimensions are absent from this manufacturer's published specification.", " ", Y ? "Without fork offset, fork A2C, and BB drop, stability index and agility index also cannot be calculated, so these sizes cannot be added to Garage or used for geometry comparison." : "Without these values, trail, stability index, and agility index cannot be calculated, so these sizes cannot be added to Garage or used for geometry comparison.", " ", "This model can still be published and audited \u2014 all applicable warnings will be attributed to the manufacturer on public and admin views."] })] })] }), p && e.jsxs("div", { className: "flex items-center gap-2 mb-3 px-1", children: [e.jsx("span", { className: "text-xs text-muted-foreground font-medium", children: "Flip Chip:" }), e.jsx($, { size: "sm", variant: x === 1 ? "default" : "outline", className: "text-xs", onClick: () => n(1), "data-testid": `button-admin-flip-pos-1-${t.id}`, children: "Position 1" }), e.jsx($, { size: "sm", variant: x === 2 ? "default" : "outline", className: "text-xs", onClick: () => n(2), "data-testid": `button-admin-flip-pos-2-${t.id}`, children: "Position 2" }), e.jsx("span", { className: "text-xs text-muted-foreground", children: "Offset shows both positions \xB7 Trail recalculates for active position" })] }), e.jsxs("div", { className: "border rounded-md overflow-hidden", children: [e.jsxs(Dt, { children: [e.jsx(Ot, { children: e.jsxs(Ke, { children: [e.jsx(I, { className: "w-20", children: "Size" }), e.jsx(I, { className: "text-right", children: "Stack" }), e.jsx(I, { className: "text-right", children: "Reach" }), e.jsx(I, { className: "text-right", children: "HTA" }), e.jsx(I, { className: "text-right", children: "STA" }), e.jsx(I, { className: "text-right", children: "ETT" }), e.jsx(I, { className: "text-right", children: "WB" }), e.jsx(I, { className: "text-right", children: "CS" }), e.jsx(I, { className: "text-right", children: "BBD" }), e.jsx(I, { className: "text-right", children: "A2C" }), e.jsx(I, { className: "text-right", children: "Offset" }), e.jsx(I, { className: "text-right", children: "Stab" }), e.jsx(I, { className: "text-right", children: "Agil" }), e.jsx(I, { className: "text-right", children: "Trail" }), e.jsx(I, { className: "text-center w-8", children: "Audit" })] }) }), e.jsx(It, { children: r.map((a) => {
601    var D, z, Me, xe, Le, Ue, ge, Ne, c, O, G, me;
602    return e.jsxs(Ke, { "data-testid": `row-size-${a.id}`, children: [e.jsx(P, { className: "font-medium", children: a.bikeSize }), e.jsx(P, { className: "text-right", children: ((D = a.stack) == null ? void 0 : D.toFixed(1)) ?? "-" }), e.jsx(P, { className: "text-right", children: ((z = a.reach) == null ? void 0 : z.toFixed(1)) ?? "-" }), e.jsxs(P, { className: "text-right", children: [((Me = a.headTubeAngle) == null ? void 0 : Me.toFixed(2)) ?? "-", "\xB0"] }), e.jsxs(P, { className: "text-right", children: [((xe = a.seatTubeAngle) == null ? void 0 : xe.toFixed(2)) ?? "-", "\xB0"] }), e.jsx(P, { className: "text-right", children: ((Le = a.effectiveTopTube) == null ? void 0 : Le.toFixed(1)) ?? "-" }), e.jsx(P, { className: "text-right", children: ((Ue = a.wheelbase) == null ? void 0 : Ue.toFixed(1)) ?? "-" }), e.jsx(P, { className: "text-right", children: ((ge = a.csLength0) == null ? void 0 : ge.toFixed(1)) ?? "-" }), e.jsx(P, { className: "text-right", children: (() => {
603      if (a.forkOffsetSource === "not-published" || a.forkOffsetSource == null && a.forkOffset === 0 || a.bbDrop0 == null) return "-";
604      const W = Ua(a);
605      return e.jsxs(e.Fragment, { children: [a.bbDrop0.toFixed(1), W && e.jsx("sup", { className: "text-amber-500 text-[9px] ml-0.5 cursor-help", title: "Not published by manufacturer", children: "*" })] });
606    })() }), e.jsxs(P, { className: "text-right", children: [a.forkLength == null ? "-" : lt(a.forkLength), za(a) && e.jsx("sup", { className: "text-amber-500 text-[9px] ml-0.5 cursor-help", title: a.forkLengthSource === "manual" ? "Entered from third-party research" : "Analytically derived \u2014 not published by manufacturer", children: "*" })] }), e.jsx(P, { className: "text-right tabular-nums", children: (() => {
607      if (a.forkOffsetSource === "not-published" || a.forkOffsetSource == null && a.forkOffset === 0) return "-";
608      const W = a.forkOffsetSource === "manual" || a.forkOffsetSource === "calculated" || a.forkOffsetSource === "derived" ? e.jsx("sup", { className: "text-amber-500 text-[9px] ml-0.5 cursor-help", title: "Not published by manufacturer \u2014 third-party sourced or engine-derived", children: "*" }) : null;
609      return a.isFlipChip && a.forkOffset2 != null ? e.jsxs("span", { className: "whitespace-nowrap", children: [e.jsxs("span", { className: x === 1 ? "font-medium" : "text-muted-foreground", children: [a.forkOffset == null ? "-" : lt(a.forkOffset), W] }), e.jsx("span", { className: "text-muted-foreground mx-0.5", children: "/" }), e.jsx("span", { className: x === 2 ? "font-medium" : "text-muted-foreground", children: lt(a.forkOffset2) })] }) : e.jsxs(e.Fragment, { children: [a.forkOffset == null ? "-" : lt(a.forkOffset), W] });
610    })() }), e.jsx(P, { className: "text-right", children: ((Ne = a.stabilityIndex) == null ? void 0 : Ne.toFixed(1)) ?? "-" }), e.jsx(P, { className: "text-right", children: ((c = a.agilityIndex) == null ? void 0 : c.toFixed(1)) ?? "-" }), e.jsx(P, { className: "text-right", children: (() => {
611      const W = x === 2 && a.isFlipChip && a.forkOffset2 != null ? is(a, a.forkOffset2) : a.actualTrail;
612      if (W == null) return "-";
613      const Be = rs(a);
614      return e.jsxs("span", { className: "whitespace-nowrap text-foreground
614/80", children: [(Math.floor(W * 10) / 10).toFixed(1), Be != null && e.jsxs("span", { className: "text-muted-foreground text-[10px] ml-0.5", children: ["@R\u209C", Be] })] });
615    })() }), e.jsx(P, { className: "text-center", children: a.geometryConsistency ? e.jsxs(Ce, { children: [e.jsx(we, { asChild: !0, children: e.jsx("span", { className: "cursor-help inline-flex items-center justify-center", children: e.jsx(ma, { severity: a.geometryConsistency.overall, size: "md" }) }) }), e.jsxs(Se, { side: "left", className: "max-w-md", children: [e.jsx("p", { className: "font-medium text-xs mb-1", children: a.geometryConsistency.summary }), ((O = a.geometryConsistency.engineeringAnalysis) == null ? void 0 : O.conclusion) && e.jsx("p", { className: "text-xs mb-1.5 leading-relaxed font-medium text-foreground", "data-testid": "text-audit-interpretation", children: a.geometryConsistency.engineeringAnalysis.conclusion }), ((G = a.geometryConsistency.engineeringAnalysis) == null ? void 0 : G.impliedA2CConsensus) != null && e.jsxs("p", { className: "text-xs mb-1 text-muted-foreground", children: ["Implied A2C: ", a.geometryConsistency.engineeringAnalysis.impliedA2CConsensus, "mm", a.geometryConsistency.engineeringAnalysis.impliedA2CSpread != null && ` (\xB1${a.geometryConsistency.engineeringAnalysis.impliedA2CSpread}mm spread)`] }), ((me = a.geometryConsistency.engineeringAnalysis) == null ? void 0 : me.impliedOffsetFromTrail) != null && e.jsxs("p", { className: "text-xs mb-1 text-muted-foreground", children: ["Implied offset: ", a.geometryConsistency.engineeringAnalysis.impliedOffsetFromTrail, "mm"] }), a.geometryConsistency.checks.filter((le) => le.severity !== "green").map((le, W) => e.jsx("p", { className: "text-xs text-muted-foreground", children: le.explanation }, W))] })] }) : e.jsx("span", { className: "text-muted-foreground text-xs", children: "-" }) })] }, a.id);
616  }) })] }), r.some((a) => a.forkLengthSource === "manual" || a.forkLengthSource === "calculated" || a.forkOffsetSource === "manual" || a.forkOffsetSource === "calculated") && e.jsx("p", { className: "text-[11px] text-amber-600/70 mt-1.5 px-1", children: "* This dimension is not published by the manufacturer." })] }), e.jsx("div", { className: "flex flex-wrap justify-end mt-3", children: e.jsxs($, { variant: "outline", onClick: (a) => {
617    a.stopPropagation(), S(!0);
618  }, "data-testid": `button-edit-geometry-${t.id}`, children: [e.jsx(la, { className: "w-4 h-4 mr-2" }), "Edit Geometry"] }) })] }) }) })] }) }), V && r.length > 0 && e.jsx(ms, { sizes: r, modelName: `${t.modelYear ? t.modelYear + " " : ""}${t.brand} ${t.name}`, model: t, open: V, onOpenChange: S, onSave: (a) => l.mutate(a), onSaveModel: (a) => g.mutate(a), isSaving: l.isPending }), e.jsx(us, { model: t, open: R, onOpenChange: K, onSave: (a) => g.mutate(a), isSaving: g.isPending }), e.jsx(_a, { open: Q, onOpenChange: L, model: t, sizes: o, isLoading: u && Q, onModelUpdated: () => {
619    ne.invalidateQueries({ predicate: (a) => typeof a.queryKey[0] == "string" && a.queryKey[0].includes("competitor-database") });
620  } }), e.jsx(Na, { open: F, onOpenChange: X, modelId: t.id, modelName: `${t.modelYear ? `${t.modelYear} ` : ""}${t.brand ?? ""} ${t.name}`.trim(), hasImage: !!t.geometryImageUrl }), e.jsx(ts, { open: H, onOpenChange: k, modelId: t.id, modelName: `${t.modelYear ? `${t.modelYear} ` : ""}${t.brand ?? ""} ${t.name}`.trim() })] });
621}
622const vt = [{ key: "headTubeAngle", label: "Head Tube Angle", unit: "\xB0", group: "Frame Angles", decimals: 2 }, { key: "seatTubeAngle", label: "Seat Tube Angle", unit: "\xB0", group: "Frame Angles", decimals: 2 }, { key: "stack", label: "Stack", unit: "mm", group: "Fit", decimals: 1 }, { key: "reach", label: "Reach", unit: "mm", group: "Fit", decimals: 1 }, { key: "wheelbase", label: "Wheelbase", unit: "mm", group: "Fit", decimals: 1 }, { key: "standoverHeight", label: "Standover Height", unit: "mm", group: "Fit", decimals: 1 }, { key: "effectiveTopTube", label: "Effective Top Tube", unit: "mm", group: "Fit", decimals: 1 }, { key: "frontCenter", label: "Front Center", unit: "mm", group: "Fit", decimals: 1 }, { key: "headTubeLength", label: "Head Tube Length", unit: "mm", group: "Frame Tubes", decimals: 1 }, { key: "seatTubeC2C", label: "Seat Tube C2C", unit: "mm", group: "Frame Tubes", decimals: 1 }, { key: "seatTubeC2T", label: "Seat Tube C2T", unit: "mm", group: "Frame Tubes", decimals: 1 }, { key: "actualTopTube", label: "Actual Top Tube", unit: "mm", group: "Frame Tubes", decimals: 1 }, { key: "csLength0", label: "Chainstay Length", unit: "mm", group: "Rear Triangle", decimals: 1 }, { key: "rearCenter", label: "Rear Center", unit: "mm", group: "Rear Triangle", decimals: 1 }, { key: "bbDrop0", label: "BB Drop", unit: "mm", group: "Rear Triangle", decimals: 1 }, { key: "bbHeight", label: "BB Height", unit: "mm", group: "Rear Triangle", decimals: 1 }, { key: "maxRearTireSize", label: "Max Rear Tire", unit: "mm", group: "Rear Triangle", decimals: 0 }, { key: "forkLength", label: "Fork A2C", unit: "mm", group: "Fork", decimals: 1 }, { key: "forkOffset", label: "Fork Offset", unit: "mm", group: "Fork", decimals: 1 }, { key: "forkMaxTireClearance", label: "Max Tire Clearance", unit: "mm", group: "Fork", decimals: 0 }, { key: "specificationTireMm", label: "Specification Tire", unit: "mm", group: "Fork", decimals: 0 }, { key: "isSuspension", label: "Suspension Fork", unit: "", group: "Fork", decimals: 0, type: "boolean" }, { key: "forkTravel", label: "Fork Travel", unit: "mm", group: "Fork", decimals: 0 }, { key: "isFlipChip", label: "Flip-Chip", unit: "", group: "Fork", decimals: 0, type: "boolean" }, { key: "forkLength2", label: "Fork A2C (Position 2)", unit: "mm", group: "Fork", decimals: 1 }, { key: "forkOffset2", label: "Fork Offset (Position 2)", unit: "mm", group: "Fork", decimals: 1 }, { key: "rimDiameter", label: "Rim Diameter", unit: "mm", group: "Wheels", decimals: 0 }, { key: "stackRef", label: "Stack (Mfr Ref)", unit: "mm", group: "Reference Values", decimals: 1 }, { key: "reachRef", label: "Reach (Mfr Ref)", unit: "mm", group: "Reference Values", decimals: 1 }, { key: "standoverRef", label: "Standover (Mfr Ref)", unit: "mm", group: "Reference Values", decimals: 1 }, { key: "wheelbaseRef", label: "Wheelbase (Mfr Ref)", unit: "mm", group: "Reference Values", decimals: 1 }, { key: "frontCenterRef", label: "Front Center (Mfr Ref)", unit: "mm", group: "Reference Values", decimals: 1 }, { key: "actualTrail", label: "Trail", unit: "mm", group: "Calculated", decimals: 1 }, { key: "mechanicalTrail", label: "Mechanical Trail", unit: "mm", group: "Calculated", decimals: 1 }, { key: "manufacturerTrail", label: "Manufacturer Trail", unit: "mm", group: "Calculated", decimals: 1 }, { key: "wheelFlop", label: "Wheel Flop", unit: "mm", group: "Calculated", decimals: 1 }, { key: "stabilityIndex", label: "Stability Index", unit: "", group: "Calculated", decimals: 2 }, { key: "agilityIndex", label: "Agility Index", unit: "", group: "Calculated", decimals: 2 }, { key: "handlingIndex", label: "Handling Index", unit: "", group: "Calculated", decimals: 2 }];
623function ms({ sizes: t, modelName: m, model: h, open: T, onOpenChange: A, onSave: N, onSaveModel: f, isSaving: V }) {
624  const { toast: S } = He(), [R, K] = d.useState({}), [Q, L] = d.useState(h.additionalWheelSizes ?? []);
625  d.useEffect(() => {
626    L(h.additionalWheelSizes ?? []);
627  }, [h]);
628  const F = (x) => {
629    L((n) => n.includes(x) ? n.filter((o) => o !== x) : [...n, x]);
630  };
631  d.useEffect(() => {
632    const x = {};
633    t.forEach((n) => {
634      const o = {};
635      o.bikeSize = n.bikeSize, vt.forEach((u) => {
636        const r = n[u.key];
637        u.type === "boolean" ? o[u.key] = r === !0 ? "true" : r === !1 ? "false" : "" : o[u.key] = r != null && !Number.isNaN(r) ? String(r) : "";
638      }), x[n.id] = o;
639    }), K(x);
640  }, [t]);
641  const X = (x, n, o) => {
642    K((u) => ({ ...u, [x]: { ...u[x], [n]: o } }));
643  }, H = () => {
644    const x = [], n = [];
645    if (t.forEach((r) => {
646      const p = R[r.id];
647      if (!p) return;
648      const w = {};
649      p.bikeSize && p.bikeSize !== r.bikeSize && (w.bikeSize = p.bikeSize), vt.forEach((B) => {
650        const q = p[B.key], M = r[B.key];
651        if (B.type === "boolean") {
652          const E = q === "true" ? !0 : q === "false" ? !1 : null;
653          E !== M && (w[B.key] = E);
654        } else if (q === "" && M != null) w[B.key] = null;
655        else if (q !== "") {
656          const E = parseFloat(q);
657          isNaN(E) ? x.push(`${r.bikeSize} \u2192 ${B.label}`) : E !== M && (w[B.key] = E);
658        }
659      }), Object.keys(w).length > 0 && n.push({ sizeId: r.id, data: w });
660    }), x.length > 0) {
661      S({ title: "Invalid values", description: `Fix non-numeric entries: ${x.slice(0, 5).join(", ")}${x.length > 5 ? ` and ${x.length - 5} more` : ""}`, variant: "destructive" });
662      return;
663    }
664    const o = h.additionalWheelSizes ?? [], u = JSON.stringify([...Q].sort()) !== JSON.stringify([...o].sort());
665    if (n.length === 0 && !u) {
666      A(!1);
667      return;
668    }
669    u && f && f({ additionalWheelSizes: Q }), n.length > 0 ? N(n) : A(!1);
670  }, k = vt.reduce((x, n) => (x[n.group] || (x[n.group] = []), x[n.group].push(n), x), {});
671  return e.jsx(De, { open: T, onOpenChange: A, children: e.jsxs(Oe, { className: "max-w-[95vw] w-full max-h-[90vh] overflow-hidden flex flex-col", children: [e.jsxs(Ie, { children: [e.jsx(Pe, { children: "Edit Geometry" }), e.jsx(Ee, { children: m })] }), e.jsx("div", { className: "border border-border rounded-md px-4 py-3 flex-shrink-0", children: e.jsxs("div", { className: "flex items-start gap-4 flex-wrap", children: [e.jsxs("div", { className: "flex-shrink-0", children: [e.jsx("p", { className: "text-sm font-medium leading-none mb-0.5", children: "Dual Wheel Compatibility" }), e.jsx("p", { className: "text-xs text-muted-foreground", children: "Additional sizes this frame can run (beyond native wheel size)" })] }), e.jsx("div", { className: "flex gap-4 flex-wrap items-center", children: ["700c", "650b", "26"].map((x) => e.jsxs("label", { className: "flex items-center gap-2 cursor-pointer select-none", "data-testid": `checkbox-geo-additional-wheel-${x}`, children: [e.jsx("input", { type: "checkbox", checked: Q.includes(x), onChange: () => F(x), className: "accent-primary" }), e.jsx("span", { className: "text-sm font-medium", children: x === "700c" ? '700c / 29"' : x === "650b" ? '650b / 27.5"' : '26"' })] }, x)) })] }) }), e.jsx("div", { className: "flex-1 overflow-auto min-h-0", children: e.jsxs("table", { className: "w-full border-collapse text-sm", children: [e.jsx("thead", { className: "sticky top-0 z-10 bg-background", children: e.jsxs("tr", { children: [e.jsx("th", { className: "text-left px-2 py-1.5 font-medium border-b min-w-[180px] sticky left-0 bg-background z-20", children: "Field" }), t.map((x) => {
672    var n;
673    return e.jsx("th", { className: "text-center px-1 py-1.5 font-medium border-b min-w-[90px]", children: e.jsx(ye, { value: ((n = R[x.id]) == null ? void 0 : n.bikeSize) ?? "", onChange: (o) => X(x.id, "bikeSize", o.target.value), className: "h-7 text-center text-xs font-semibold", "data-testid": `input-size-name-${x.id}` }) }, x.id);
674  })] }) }), e.jsx("tbody", { children: Object.entries(k).map(([x, n]) => e.jsxs(d.Fragment, { children: [e.jsx("tr", { children: e.jsx("td", { colSpan: t.length + 1, className: "px-2 pt-4 pb-1 text-xs font-bold uppercase tracking-wider text-muted-foreground border-b bg-muted/30", children: x }) }), n.map((o) => e.jsxs("tr", { className: "border-b border-border/50", children: [e.jsxs("td", { className: "px-2 py-1 text-xs text-muted-foreground sticky left-0 bg-background whitespace-nowrap", children: [o.label, o.unit ? e.jsxs("span", { className: "ml-1 opacity-60", children: ["(", o.unit, ")"] }) : null] }), t.map((u) => {
675    var r, p;
676    return e.jsx("td", { className: "px-1 py-0.5", children: o.type === "boolean" ? e.jsx("div", { className: "flex justify-center", children: e.jsx($t, { checked: ((r = R[u.id]) == null ? void 0 : r[o.key]) === "true", onCheckedChange: (w) => X(u.id, o.key, String(w)), "data-testid": `switch-${o.key}-${u.id}` }) }) : e.jsx(ye, { type: "number", step: o.decimals > 0 ? Math.pow(10, -o.decimals) : 1, value: ((p = R[u.id]) == null ? void 0 : p[o.key]) ?? "", onChange: (w) => X(u.id, o.key, w.target.value), placeholder: "-", className: "text-xs text-center px-1", "data-testid": `input-${o.key}-${u.id}` }) }, u.id);
677  })] }, o.key))] }, x)) })] }) }), e.jsxs(We, { className: "gap-2 pt-3 border-t flex-shrink-0", children: [e.jsxs($, { variant: "outline", onClick: () => A(!1), disabled: V, children: [e.jsx(qe, { className: "w-4 h-4 mr-1" }), "Cancel"] }), e.jsxs($, { onClick: H, disabled: V, "data-testid": "button-save-geometry", children: [V ? e.jsx(Z, { className: "w-4 h-4 mr-1 animate-spin" }) : e.jsx(ka, { className: "w-4 h-4 mr-1" }), "Save All Changes"] })] })] }) });
678}
679function us({ model: t, open: m, onOpenChange: h, onSave: T, isSaving: A }) {
680  var ge, Ne;
681  const { toast: N } = He(), f = d.useRef(null), [V, S] = d.useState(t.brand), [R, K] = d.useState(t.name), [Q, L] = d.useState(t.modelYear ? String(t.modelYear) : ""), [F, X] = d.useState(t.bikeType ?? ""), [H, k] = d.useState(t.sourceUrl ?? ""), [x, n] = d.useState(t.geometryImageUrl ?? ""), [o, u] = d.useState(t.fitStyle ?? ""), [r, p] = d.useState(t.sizingNotes ?? ""), [w, B] = d.useState(t.additionalWheelSizes ?? []), [q, M] = d.useState(t.specTireMm != null ? String(Math.round(t.specTireMm)) : ""), [E, U] = d.useState(((ge = t.frontTireRange) == null ? void 0 : ge.max) != null ? String(Math.round(t.frontTireRange.max)) : ""), [Y, ce] = d.useState(((Ne = t.rearTireRange) == null ? void 0 : Ne.max) != null ? String(Math.round(t.rearTireRange.max)) : ""), [C, ee] = d.useState(!1), [l, j] = d.useState(!1), [g, v] = d.useState(!1);
682  d.useEffect(() => {
683    var c, O;
684    S(t.brand), K(t.name), L(t.modelYear ? String(t.modelYear) : ""), X(t.bikeType ?? ""), k(t.sourceUrl ?? ""), n(t.geometryImageUrl ?? ""), u(t.fitStyle ?? ""), p(t.sizingNotes ?? ""), B(t.additionalWheelSizes ?? []), M(t.specTireMm != null ? String(Math.round(t.specTireMm)) : ""), U(((c = t.frontTireRange) == null ? void 0 : c.max) != null ? String(Math.round(t.frontTireRange.max)) : ""), ce(((O = t.rearTireRange) == null ? void 0 : O.max) != null ? String(Math.round(t.rearTireRange.max)) : "");
685  }, [t]);
686  const a = (c) => {
687    B((O) => O.includes(c) ? O.filter((G) => G !== c) : [...O, c]);
688  }, D = async (c) => {
689    if (!["image/png", "image/jpeg", "image/webp", "application/pdf"].includes(c.type)) {
690      N({ title: "Invalid File", description: "Only PNG, JPG, WebP, or PDF files are allowed.", variant: "destructive" });
691      return;
692    }
693    if (c.size > 20 * 1024 * 1024) {
694      N({ title: "File Too Large", description: "Maximum file size is 20MB.", variant: "destructive" });
695      return;
696    }
697    j(!0);
698    try {
699      const G = new FormData();
700      G.append("file", c);
701      const me = await fetch(`/api/admin/competitor-database/models/${t.id}/geometry-image`, { method: "POST", credentials: "include", body: G });
702      if (!me.ok) {
703        const W = await me.json().catch(() => ({ error: "Upload failed" }));
704        throw new Error(W.error || "Upload failed");
705      }
706      const le = await me.json();
707      n(le.geometryImageUrl), ne.invalidateQueries({ predicate: (W) => typeof W.queryKey[0] == "string" && W.queryKey[0].includes("competitor-database") }), N({ title: "Chart Uploaded", description: `${c.name} has been saved.` });
708    } catch (G) {
709      N({ title: "Upload Failed", description: G.message, variant: "destructive" });
710    } finally {
711      j(!1);
712    }
713  }, z = async () => {
714    j(!0);
715    try {
716      if (!(await fetch(`/api/admin/competitor-database/models/${t.id}/geometry-image`, { method: "DELETE", credentials: "include" })).ok) throw new Error("Failed to remove");
717      n(""), ne.invalidateQueries({ predicate: (O) => typeof O.queryKey[0] == "string" && O.queryKey[0].includes("competitor-database") }), N({ title: "Chart Removed" });
718    } catch (c) {
719      N({ title: "Error", description: c.message, variant: "destructive" });
720    } finally {
721      j(!1);
722    }
723  }, Me = (c) => {
724    c.preventDefault(), v(!1);
725    const O = c.dataTransfer.files[0];
726    O && D(O);
727  }, xe = (c) => {
728    const O = c.clipboardData.items;
729    for (let G = 0; G < O.length; G++) if (O[G].type.startsWith("image/") || O[G].type === "application/pdf") {
730      const me = O[G].getAsFile();
731      if (me) {
732        c.preventDefault(), D(me);
733        return;
734      }
735    }
736  }, Le = x.toLowerCase().endsWith(".pdf"), Ue = async () => {
737    var rt, it;
738    const c = {};
739    V !== t.brand && (c.brand = V), R !== t.name && (c.name = R);
740    const O = Q ? parseInt(Q, 10) : null;
741    O !== t.modelYear && (c.modelYear = O), F !== (t.bikeType ?? "") && (c.bikeType = F || null), H !== (t.sourceUrl ?? "") && (c.sourceUrl = H || null);
742    const G = t.fitStyle ?? "";
743    o !== G && (c.fitStyle = o || null);
744    const me = t.sizingNotes ?? "";
745    r !== me && (c.sizingNotes = r || null);
746    const le = t.additionalWheelSizes ?? [];
747    JSON.stringify([...w].sort()) !== JSON.stringify([...le].sort()) && (c.additionalWheelSizes = w);
748    const W = {}, Be = q !== "" ? parseFloat(q) : null, Ye = E !== "" ? parseFloat(E) : null, Te = Y !== "" ? parseFloat(Y) : null, Qe = t.specTireMm != null ? Math.round(t.specTireMm) : null, Je = ((rt = t.frontTireRange) == null ? void 0 : rt.max) != null ? Math.round(t.frontTireRange.max) : null, ct = ((it = t.rearTireRange) == null ? void 0 : it.max) != null ? Math.round(t.rearTireRange.max) : null;
749    if (Be !== Qe && (W.specificationTireMm = Be), Ye !== Je && (W.forkMaxTireClearance = Ye), Te !== ct && (W.maxRearTireSize = Te), Object.keys(c).length === 0 && Object.keys(W).length === 0) {
750      h(!1);
751      return;
752    }
753    if (Object.keys(c).length > 0 && T(c), Object.keys(W).length > 0) {
754      ee(!0);
755      try {
756        const ue = await fetch(`/api/admin/competitor-database/models/${t.id}/tire-sizes`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(W) });
757        if (!ue.ok) {
758          const ze = await ue.json().catch(() => ({ error: "Failed to update tire sizes" }));
759          throw new Error(ze.error || "Failed to update tire sizes");
760        }
761        ne.invalidateQueries({ predicate: (ze) => typeof ze.queryKey[0] == "string" && ze.queryKey[0].includes("competitor-database") }), N({ title: "Tire Sizes Updated", description: "Applied to all sizes and geometry recalculated." }), Object.keys(c).length === 0 && h(!1);
762      } catch (ue) {
763        N({ title: "Error", description: ue.message, variant: "destructive" });
764      } finally {
765        ee(!1);
766      }
767    }
768  };
769  return e.jsx(De, { open: m, onOpenChange: h, children: e.jsxs(Oe, { className: "max-w-md", onPaste: xe, children: [e.jsxs(Ie, { children: [e.jsx(Pe, { children: "Edit Model Details" }), e.jsxs(Ee, { children: [t.modelYear ? `${t.modelYear} ` : "", t.brand, " ", t.name] })] }), e.jsxs("div", { className: "space-y-4 py-2", children: [e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-brand", className: "text-sm font-medium", children: "Brand" }), e.jsx(ye, { id: "edit-model-brand", value: V, onChange: (c) => S(c.target.value), className: "mt-1", "data-testid": "input-edit-model-brand" })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-name", className: "text-sm font-medium", children: "Model Name" }), e.jsx(ye, { id: "edit-model-name", value: R, onChange: (c) => K(c.target.value), className: "mt-1", "data-testid": "input-edit-model-name" })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-year", className: "text-sm font-medium", children: "Model Year" }), e.jsx(ye, { id: "edit-model-year", type: "number", value: Q, onChange: (c) => L(c.target.value), placeholder: "e.g. 2025", className: "mt-1", "data-testid": "input-edit-model-year" })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-type", className: "text-sm font-medium", children: "Bike Type" }), e.jsx(ye, { id: "edit-model-type", value: F, onChange: (c) => X(c.target.value), placeholder: "e.g. Gravel, Road, MTB", className: "mt-1", "data-testid": "input-edit-model-type" })] }), e.jsxs("div", { children: [e.jsx(ie, { className: "text-sm font-medium", children: "Tire Sizes (mm)" }), e.jsx("p", { className: "text-xs text-muted-foreground mt-0.5 mb-2", children: "Applied to all sizes. Leave blank to auto-derive from trail calculation." }), e.jsxs("div", { className: "grid grid-cols-3 gap-2", children: [e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-ref-tire", className: "text-xs text-muted-foreground", children: "Ref Tire" }), e.jsx(ye, { id: "edit-model-ref-tire", type: "number", value: q, onChange: (c) => M(c.target.value), placeholder: "e.g. 25", className: "mt-0.5", "data-testid": "input-edit-model-ref-tire" })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-max-front", className: "text-xs text-muted-foreground", children: "Max Front" }), e.jsx(ye, { id: "edit-model-max-front", type: "number", value: E, onChange: (c) => U(c.target.value), placeholder: "e.g. 32", className: "mt-0.5", "data-testid": "input-edit-model-max-front" })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-max-rear", className: "text-xs text-muted-foreground", children: "Max Rear" }), e.jsx(ye, { id: "edit-model-max-rear", type: "number", value: Y, onChange: (c) => ce(c.target.value), placeholder: "e.g. 35", className: "mt-0.5", "data-testid": "input-edit-model-max-rear" })] })] })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-source-url", className: "text-sm font-medium", children: "Manufacturer Page URL" }), e.jsx(ye, { id: "edit-model-source-url", value: H, onChange: (c) => k(c.target.value), placeholder: "https://brand.com/bikes/model", className: "mt-1", "data-testid": "input-edit-model-source-url" })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-fit-style", className: "text-sm font-medium", children: "Fit Style" }), e.jsxs(St, { value: o || "unset", onValueChange: (c) => u(c === "unset" ? "" : c), children: [e.jsx(Mt, { id: "edit-model-fit-style", className: "mt-1", "data-testid": "select-edit-model-fit-style", children: e.jsx(Tt, { placeholder: "Select fit style..." }) }), e.jsxs(Rt, { children: [e.jsx(be, { value: "unset", children: "N
769ot specified" }), e.jsx(be, { value: "race", children: "Race" }), e.jsx(be, { value: "performance", children: "Performance" }), e.jsx(be, { value: "endurance", children: "Endurance" }), e.jsx(be, { value: "relaxed", children: "Relaxed" }), e.jsx(be, { value: "comfort", children: "Comfort" })] })] })] }), e.jsxs("div", { children: [e.jsx(ie, { htmlFor: "edit-model-sizing-notes", className: "text-sm font-medium", children: "Sizing Notes" }), e.jsx(pa, { id: "edit-model-sizing-notes", value: r, onChange: (c) => p(c.target.value), placeholder: "Manufacturer sizing guidance, e.g. 'Runs small \u2014 size up'", className: "mt-1 resize-none", rows: 2, "data-testid": "textarea-edit-model-sizing-notes" })] }), e.jsxs("div", { children: [e.jsx(ie, { className: "text-sm font-medium", children: "Dual Wheel Compatibility" }), e.jsx("p", { className: "text-xs text-muted-foreground mt-0.5 mb-2", children: "Additional wheel sizes this frame is designed to run (beyond its native size). Used for dirt jump and mullet-capable bikes." }), e.jsx("div", { className: "flex flex-wrap gap-3", children: ["700c", "650b", "26"].map((c) => e.jsxs("label", { className: "flex items-center gap-2 cursor-pointer select-none", "data-testid": `checkbox-additional-wheel-${c}`, children: [e.jsx("input", { type: "checkbox", checked: w.includes(c), onChange: () => a(c), className: "accent-primary" }), e.jsx("span", { className: "text-sm", children: c === "700c" ? '700c / 29"' : c === "650b" ? '650b / 27.5"' : '26"' })] }, c)) })] }), e.jsxs("div", { children: [e.jsx(ie, { className: "text-sm font-medium", children: "Geometry Chart" }), x ? e.jsxs("div", { className: "mt-1.5 space-y-2", children: [e.jsx("div", { className: "relative rounded border border-border overflow-hidden bg-muted/30", children: Le ? e.jsxs("div", { className: "flex items-center gap-2 p-3", children: [e.jsx(Ge, { className: "w-5 h-5 text-muted-foreground flex-shrink-0" }), e.jsx("span", { className: "text-sm truncate", children: x.split("/").pop() })] }) : e.jsx("img", { src: x, alt: "Geometry chart", className: "max-h-40 w-full object-contain", "data-testid": "img-edit-geometry-preview" }) }), e.jsxs("div", { className: "flex gap-2", children: [e.jsxs($, { size: "sm", variant: "outline", onClick: () => {
770    var c;
771    return (c = f.current) == null ? void 0 : c.click();
772  }, disabled: l, "data-testid": "button-replace-geometry-chart", children: [e.jsx(Ge, { className: "w-3 h-3 mr-1" }), "Replace"] }), e.jsxs($, { size: "sm", variant: "outline", onClick: z, disabled: l, className: "text-destructive", "data-testid": "button-remove-geometry-chart", children: [e.jsx(ba, { className: "w-3 h-3 mr-1" }), "Remove"] })] })] }) : e.jsx("div", { className: `mt-1.5 border-2 border-dashed rounded-md p-6 text-center cursor-pointer transition-colors ${g ? "border-primary bg-primary/5" : "border-border hover:border-muted-foreground/50"}`, onClick: () => {
773    var c;
774    return (c = f.current) == null ? void 0 : c.click();
775  }, onDragOver: (c) => {
776    c.preventDefault(), v(!0);
777  }, onDragLeave: () => v(!1), onDrop: Me, "data-testid": "dropzone-geometry-chart", children: l ? e.jsxs("div", { className: "flex flex-col items-center gap-2", children: [e.jsx(Z, { className: "w-6 h-6 animate-spin text-muted-foreground" }), e.jsx("span", { className: "text-sm text-muted-foreground", children: "Uploading..." })] }) : e.jsxs("div", { className: "flex flex-col items-center gap-2", children: [e.jsx(Ge, { className: "w-6 h-6 text-muted-foreground" }), e.jsx("span", { className: "text-sm text-muted-foreground", children: "Drop image/PDF here, paste from clipboard, or click to browse" }), e.jsx("span", { className: "text-xs text-muted-foreground/70", children: "PNG, JPG, WebP, or PDF up to 20MB" })] }) }), e.jsx("input", { ref: f, type: "file", accept: "image/png,image/jpeg,image/webp,application/pdf", className: "hidden", onChange: (c) => {
778    var G;
779    const O = (G = c.target.files) == null ? void 0 : G[0];
780    O && D(O), c.target.value = "";
781  }, "data-testid": "input-geometry-chart-file" })] })] }), e.jsxs(We, { className: "gap-2", children: [e.jsxs($, { variant: "outline", onClick: () => h(!1), disabled: A || C, children: [e.jsx(qe, { className: "w-4 h-4 mr-1" }), "Cancel"] }), e.jsxs($, { onClick: Ue, disabled: A || C, "data-testid": "button-save-model", children: [A || C ? e.jsx(Z, { className: "w-4 h-4 mr-1 animate-spin" }) : e.jsx(ka, { className: "w-4 h-4 mr-1" }), "Save Changes"] })] })] }) });
782}
783export { _s as default };

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.