@optifye/dashboard-core 6.10.6 → 6.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +21 -0
- package/dist/index.d.mts +26 -19
- package/dist/index.d.ts +26 -19
- package/dist/index.js +895 -538
- package/dist/index.mjs +897 -540
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3757,18 +3757,17 @@ var initializeCoreMixpanel = (token, debugOrOptions, trackPageViewArg) => {
|
|
|
3757
3757
|
track_pageview: trackPageView ?? true,
|
|
3758
3758
|
persistence: "localStorage"
|
|
3759
3759
|
};
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
initOptions.record_idle_timeout_ms = sessionOpts.recordIdleTimeoutMs;
|
|
3760
|
+
const recordSessionsPercent = sessionOpts.recordSessionsPercent ?? 0;
|
|
3761
|
+
initOptions.record_sessions_percent = recordSessionsPercent;
|
|
3762
|
+
const defaultIdleTimeoutMs = 10 * 60 * 1e3;
|
|
3763
|
+
const recordIdleTimeoutMs = sessionOpts.recordIdleTimeoutMs ?? (recordSessionsPercent > 0 ? defaultIdleTimeoutMs : void 0);
|
|
3764
|
+
if (recordIdleTimeoutMs !== void 0) {
|
|
3765
|
+
initOptions.record_idle_timeout_ms = recordIdleTimeoutMs;
|
|
3767
3766
|
}
|
|
3768
3767
|
if (sessionOpts.recordHeatmapData !== void 0) {
|
|
3769
3768
|
initOptions.record_heatmap_data = sessionOpts.recordHeatmapData;
|
|
3770
3769
|
} else {
|
|
3771
|
-
initOptions.record_heatmap_data =
|
|
3770
|
+
initOptions.record_heatmap_data = false;
|
|
3772
3771
|
}
|
|
3773
3772
|
if (sessionOpts.recordCanvas !== void 0) {
|
|
3774
3773
|
initOptions.record_canvas = sessionOpts.recordCanvas;
|
|
@@ -3786,7 +3785,9 @@ var initializeCoreMixpanel = (token, debugOrOptions, trackPageViewArg) => {
|
|
|
3786
3785
|
});
|
|
3787
3786
|
mixpanel__default.default.init(token, initOptions);
|
|
3788
3787
|
isMixpanelInitialized = true;
|
|
3789
|
-
|
|
3788
|
+
if (initOptions.debug) {
|
|
3789
|
+
console.log("Mixpanel initialized in dashboard-core.");
|
|
3790
|
+
}
|
|
3790
3791
|
};
|
|
3791
3792
|
var trackCorePageView = (pageName, properties) => {
|
|
3792
3793
|
if (!isMixpanelInitialized) return;
|
|
@@ -10330,6 +10331,41 @@ var useRealtimeLineMetrics = ({
|
|
|
10330
10331
|
refreshMetrics: fetchData
|
|
10331
10332
|
}), [metrics2, lineDetails, loading, error, fetchData]);
|
|
10332
10333
|
};
|
|
10334
|
+
var useLines = () => {
|
|
10335
|
+
const supabase = useSupabase();
|
|
10336
|
+
const entityConfig = useEntityConfig();
|
|
10337
|
+
const [lines, setLines] = React24.useState([]);
|
|
10338
|
+
const [loading, setLoading] = React24.useState(true);
|
|
10339
|
+
const [error, setError] = React24.useState(null);
|
|
10340
|
+
const fetchLines = React24.useCallback(async () => {
|
|
10341
|
+
if (!supabase) {
|
|
10342
|
+
setLoading(false);
|
|
10343
|
+
return;
|
|
10344
|
+
}
|
|
10345
|
+
try {
|
|
10346
|
+
setLoading(true);
|
|
10347
|
+
let query = supabase.from("lines").select("*").eq("enable", true);
|
|
10348
|
+
if (entityConfig.companyId) {
|
|
10349
|
+
query = query.eq("company_id", entityConfig.companyId);
|
|
10350
|
+
}
|
|
10351
|
+
const { data, error: error2 } = await query;
|
|
10352
|
+
if (error2) throw error2;
|
|
10353
|
+
const sortedLines = (data || []).sort(
|
|
10354
|
+
(a, b) => (a.line_name || "").localeCompare(b.line_name || "")
|
|
10355
|
+
);
|
|
10356
|
+
setLines(sortedLines);
|
|
10357
|
+
} catch (err) {
|
|
10358
|
+
console.error("Error fetching lines:", err);
|
|
10359
|
+
setError(err);
|
|
10360
|
+
} finally {
|
|
10361
|
+
setLoading(false);
|
|
10362
|
+
}
|
|
10363
|
+
}, [supabase, entityConfig.companyId]);
|
|
10364
|
+
React24.useEffect(() => {
|
|
10365
|
+
fetchLines();
|
|
10366
|
+
}, [fetchLines]);
|
|
10367
|
+
return { lines, loading, error, refetch: fetchLines };
|
|
10368
|
+
};
|
|
10333
10369
|
var useTargets = (options) => {
|
|
10334
10370
|
const { companyId } = useEntityConfig();
|
|
10335
10371
|
const supabase = useSupabase();
|
|
@@ -12463,41 +12499,6 @@ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput, options
|
|
|
12463
12499
|
counts
|
|
12464
12500
|
};
|
|
12465
12501
|
}
|
|
12466
|
-
var useLines = () => {
|
|
12467
|
-
const supabase = useSupabase();
|
|
12468
|
-
const entityConfig = useEntityConfig();
|
|
12469
|
-
const [lines, setLines] = React24.useState([]);
|
|
12470
|
-
const [loading, setLoading] = React24.useState(true);
|
|
12471
|
-
const [error, setError] = React24.useState(null);
|
|
12472
|
-
const fetchLines = React24.useCallback(async () => {
|
|
12473
|
-
if (!supabase) {
|
|
12474
|
-
setLoading(false);
|
|
12475
|
-
return;
|
|
12476
|
-
}
|
|
12477
|
-
try {
|
|
12478
|
-
setLoading(true);
|
|
12479
|
-
let query = supabase.from("lines").select("*").eq("enable", true);
|
|
12480
|
-
if (entityConfig.companyId) {
|
|
12481
|
-
query = query.eq("company_id", entityConfig.companyId);
|
|
12482
|
-
}
|
|
12483
|
-
const { data, error: error2 } = await query;
|
|
12484
|
-
if (error2) throw error2;
|
|
12485
|
-
const sortedLines = (data || []).sort(
|
|
12486
|
-
(a, b) => (a.line_name || "").localeCompare(b.line_name || "")
|
|
12487
|
-
);
|
|
12488
|
-
setLines(sortedLines);
|
|
12489
|
-
} catch (err) {
|
|
12490
|
-
console.error("Error fetching lines:", err);
|
|
12491
|
-
setError(err);
|
|
12492
|
-
} finally {
|
|
12493
|
-
setLoading(false);
|
|
12494
|
-
}
|
|
12495
|
-
}, [supabase, entityConfig.companyId]);
|
|
12496
|
-
React24.useEffect(() => {
|
|
12497
|
-
fetchLines();
|
|
12498
|
-
}, [fetchLines]);
|
|
12499
|
-
return { lines, loading, error, refetch: fetchLines };
|
|
12500
|
-
};
|
|
12501
12502
|
var MAX_RETRIES = 10;
|
|
12502
12503
|
var RETRY_DELAY = 500;
|
|
12503
12504
|
function useNavigation(customNavigate) {
|
|
@@ -25352,6 +25353,7 @@ var DEFAULT_BAR_RADIUS = [4, 4, 0, 0];
|
|
|
25352
25353
|
var BarChartComponent = ({
|
|
25353
25354
|
data,
|
|
25354
25355
|
bars,
|
|
25356
|
+
referenceLines,
|
|
25355
25357
|
xAxisDataKey = "name",
|
|
25356
25358
|
xAxisLabel,
|
|
25357
25359
|
yAxisLabel,
|
|
@@ -25435,6 +25437,22 @@ var BarChartComponent = ({
|
|
|
25435
25437
|
stroke: axisStrokeColor
|
|
25436
25438
|
}
|
|
25437
25439
|
),
|
|
25440
|
+
referenceLines?.map((line, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
25441
|
+
recharts.ReferenceLine,
|
|
25442
|
+
{
|
|
25443
|
+
y: line.y,
|
|
25444
|
+
stroke: line.stroke || axisStrokeColor,
|
|
25445
|
+
strokeDasharray: line.strokeDasharray || "4 4",
|
|
25446
|
+
strokeWidth: line.strokeWidth,
|
|
25447
|
+
label: line.label ? {
|
|
25448
|
+
value: line.label,
|
|
25449
|
+
position: "insideTopRight",
|
|
25450
|
+
fill: line.stroke || axisTickFillColor,
|
|
25451
|
+
fontSize: 12
|
|
25452
|
+
} : void 0
|
|
25453
|
+
},
|
|
25454
|
+
`ref-line-${idx}`
|
|
25455
|
+
)),
|
|
25438
25456
|
showTooltip && /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: tooltipFormatter || defaultTooltipFormatter, cursor: { fill: "transparent" } }),
|
|
25439
25457
|
showLegend && /* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { payload: legendPayload }),
|
|
25440
25458
|
bars.map((barConfig, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -25470,7 +25488,7 @@ var BarChartComponent = ({
|
|
|
25470
25488
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("w-full", className), children: chartContent });
|
|
25471
25489
|
};
|
|
25472
25490
|
var BarChart = React24__namespace.default.memo(BarChartComponent, (prevProps, nextProps) => {
|
|
25473
|
-
if (prevProps.xAxisDataKey !== nextProps.xAxisDataKey || prevProps.xAxisLabel !== nextProps.xAxisLabel || prevProps.yAxisLabel !== nextProps.yAxisLabel || prevProps.yAxisUnit !== nextProps.yAxisUnit || prevProps.layout !== nextProps.layout || prevProps.className !== nextProps.className || prevProps.showGrid !== nextProps.showGrid || prevProps.showLegend !== nextProps.showLegend || prevProps.showTooltip !== nextProps.showTooltip || prevProps.responsive !== nextProps.responsive || prevProps.aspect !== nextProps.aspect) {
|
|
25491
|
+
if (prevProps.xAxisDataKey !== nextProps.xAxisDataKey || prevProps.xAxisLabel !== nextProps.xAxisLabel || prevProps.yAxisLabel !== nextProps.yAxisLabel || prevProps.yAxisUnit !== nextProps.yAxisUnit || JSON.stringify(prevProps.referenceLines || []) !== JSON.stringify(nextProps.referenceLines || []) || prevProps.layout !== nextProps.layout || prevProps.className !== nextProps.className || prevProps.showGrid !== nextProps.showGrid || prevProps.showLegend !== nextProps.showLegend || prevProps.showTooltip !== nextProps.showTooltip || prevProps.responsive !== nextProps.responsive || prevProps.aspect !== nextProps.aspect) {
|
|
25474
25492
|
return false;
|
|
25475
25493
|
}
|
|
25476
25494
|
if (prevProps.data.length !== nextProps.data.length) {
|
|
@@ -29051,6 +29069,38 @@ var ERROR_MAPPING = {
|
|
|
29051
29069
|
canRetry: false
|
|
29052
29070
|
}
|
|
29053
29071
|
};
|
|
29072
|
+
var CLIP_ID_COMMENT_REGEX = /#\s*Clip ID:\s*([a-f0-9-]+)/i;
|
|
29073
|
+
var PLAYLIST_PROXY_REGEX = /\/api\/clips\/playlist\/([a-f0-9-]+)/i;
|
|
29074
|
+
var R2_FALLBACK_DETAILS = /* @__PURE__ */ new Set([
|
|
29075
|
+
"fragLoadError",
|
|
29076
|
+
"fragLoadHTTPError",
|
|
29077
|
+
"manifestLoadError",
|
|
29078
|
+
"levelLoadError",
|
|
29079
|
+
"keyLoadError"
|
|
29080
|
+
]);
|
|
29081
|
+
var extractClipIdFromSource = (source) => {
|
|
29082
|
+
if (!source) return null;
|
|
29083
|
+
const commentMatch = source.match(CLIP_ID_COMMENT_REGEX);
|
|
29084
|
+
if (commentMatch) return commentMatch[1];
|
|
29085
|
+
const urlMatch = source.match(PLAYLIST_PROXY_REGEX);
|
|
29086
|
+
if (urlMatch) return urlMatch[1];
|
|
29087
|
+
return null;
|
|
29088
|
+
};
|
|
29089
|
+
var ensureClipIdComment = (playlist, clipId) => {
|
|
29090
|
+
if (!playlist || playlist.includes(`# Clip ID: ${clipId}`) || CLIP_ID_COMMENT_REGEX.test(playlist)) {
|
|
29091
|
+
return playlist;
|
|
29092
|
+
}
|
|
29093
|
+
const lines = playlist.split("\n");
|
|
29094
|
+
if (lines.length === 0) return playlist;
|
|
29095
|
+
return [lines[0], `# Clip ID: ${clipId}`, ...lines.slice(1)].join("\n");
|
|
29096
|
+
};
|
|
29097
|
+
var getHlsErrorUrl = (data) => {
|
|
29098
|
+
return data?.frag?.url || data?.response?.url || data?.context?.url || data?.networkDetails?.response?.url || data?.url;
|
|
29099
|
+
};
|
|
29100
|
+
var getHlsErrorStatus = (data) => {
|
|
29101
|
+
const status = data?.response?.code ?? data?.response?.status ?? data?.networkDetails?.status ?? data?.networkDetails?.response?.status;
|
|
29102
|
+
return typeof status === "number" ? status : void 0;
|
|
29103
|
+
};
|
|
29054
29104
|
var hlsVideoPlayerStyles = `
|
|
29055
29105
|
.hls-video-player-container {
|
|
29056
29106
|
width: 100%;
|
|
@@ -29152,8 +29202,12 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29152
29202
|
const videoRef = React24.useRef(null);
|
|
29153
29203
|
const hlsRef = React24.useRef(null);
|
|
29154
29204
|
const blobUrlRef = React24.useRef(null);
|
|
29205
|
+
const clipIdRef = React24.useRef(null);
|
|
29206
|
+
const r2FallbackAttemptedRef = React24.useRef(false);
|
|
29155
29207
|
const [isReady, setIsReady] = React24.useState(false);
|
|
29156
29208
|
const [isLoading, setIsLoading] = React24.useState(true);
|
|
29209
|
+
const [overrideSource, setOverrideSource] = React24.useState(null);
|
|
29210
|
+
const effectiveSrc = overrideSource && overrideSource.baseSrc === src ? overrideSource.value : src;
|
|
29157
29211
|
const [showControls, setShowControls] = React24.useState(true);
|
|
29158
29212
|
const [controlsPinned, setControlsPinned] = React24.useState(false);
|
|
29159
29213
|
const [isPlaying, setIsPlaying] = React24.useState(false);
|
|
@@ -29213,10 +29267,16 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29213
29267
|
onSeeked,
|
|
29214
29268
|
onLoadingChange
|
|
29215
29269
|
]);
|
|
29270
|
+
React24.useEffect(() => {
|
|
29271
|
+
clipIdRef.current = extractClipIdFromSource(src);
|
|
29272
|
+
r2FallbackAttemptedRef.current = false;
|
|
29273
|
+
setOverrideSource(null);
|
|
29274
|
+
}, [src]);
|
|
29216
29275
|
const stableHlsConfigRef = React24.useRef(hlsConfig);
|
|
29217
29276
|
const stableOptionsRef = React24.useRef(options);
|
|
29218
29277
|
const configSignatureRef = React24.useRef("");
|
|
29219
29278
|
const [configVersion, setConfigVersion] = React24.useState(0);
|
|
29279
|
+
const r2WorkerDomain = process.env.NEXT_PUBLIC_R2_WORKER_DOMAIN || "https://r2-stream-proxy.optifye-r2.workers.dev";
|
|
29220
29280
|
React24.useEffect(() => {
|
|
29221
29281
|
const serialized = JSON.stringify({
|
|
29222
29282
|
hlsConfig: hlsConfig || null,
|
|
@@ -29235,6 +29295,50 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29235
29295
|
setConfigVersion((prev) => prev + 1);
|
|
29236
29296
|
}
|
|
29237
29297
|
}, [hlsConfig, options]);
|
|
29298
|
+
const attemptS3Fallback = React24.useCallback(async (mode, reason, errorUrl) => {
|
|
29299
|
+
if (r2FallbackAttemptedRef.current) return false;
|
|
29300
|
+
const clipId = clipIdRef.current || extractClipIdFromSource(effectiveSrc);
|
|
29301
|
+
if (!clipId) {
|
|
29302
|
+
console.warn(`[HlsVideoPlayer] R2 fallback skipped - no clip ID (${reason})`);
|
|
29303
|
+
return false;
|
|
29304
|
+
}
|
|
29305
|
+
r2FallbackAttemptedRef.current = true;
|
|
29306
|
+
if (mode === "url") {
|
|
29307
|
+
const fallbackUrl = `/api/clips/playlist/${clipId}?source=s3`;
|
|
29308
|
+
console.warn(`[HlsVideoPlayer] Switching to S3 playlist URL (${reason})`, { errorUrl, clipId });
|
|
29309
|
+
setOverrideSource({ baseSrc: src, value: fallbackUrl });
|
|
29310
|
+
return true;
|
|
29311
|
+
}
|
|
29312
|
+
try {
|
|
29313
|
+
console.warn(`[HlsVideoPlayer] Fetching S3 playlist (${reason})`, { errorUrl, clipId });
|
|
29314
|
+
const response = await fetch(`/api/clips/playlist/${clipId}?source=s3`);
|
|
29315
|
+
if (!response.ok) {
|
|
29316
|
+
console.warn("[HlsVideoPlayer] S3 playlist fetch failed", { status: response.status, clipId });
|
|
29317
|
+
return false;
|
|
29318
|
+
}
|
|
29319
|
+
let playlistText = await response.text();
|
|
29320
|
+
playlistText = ensureClipIdComment(playlistText, clipId);
|
|
29321
|
+
setOverrideSource({ baseSrc: src, value: playlistText });
|
|
29322
|
+
return true;
|
|
29323
|
+
} catch (error) {
|
|
29324
|
+
console.error("[HlsVideoPlayer] S3 fallback failed", error);
|
|
29325
|
+
return false;
|
|
29326
|
+
}
|
|
29327
|
+
}, [effectiveSrc, src]);
|
|
29328
|
+
const maybeHandleR2Fallback = React24.useCallback((data) => {
|
|
29329
|
+
const errorUrl = getHlsErrorUrl(data);
|
|
29330
|
+
if (!errorUrl || !isR2WorkerUrl(errorUrl, r2WorkerDomain)) {
|
|
29331
|
+
return false;
|
|
29332
|
+
}
|
|
29333
|
+
const status = getHlsErrorStatus(data);
|
|
29334
|
+
const details = data?.details;
|
|
29335
|
+
const shouldFallback = R2_FALLBACK_DETAILS.has(details) || typeof status === "number" && status >= 400 && status < 500;
|
|
29336
|
+
if (!shouldFallback) {
|
|
29337
|
+
return false;
|
|
29338
|
+
}
|
|
29339
|
+
attemptS3Fallback("playlist", `HLS.js ${details || data?.type || "error"}`, errorUrl);
|
|
29340
|
+
return true;
|
|
29341
|
+
}, [attemptS3Fallback, r2WorkerDomain]);
|
|
29238
29342
|
const cleanupBlobUrl = React24.useCallback(() => {
|
|
29239
29343
|
if (blobUrlRef.current) {
|
|
29240
29344
|
URL.revokeObjectURL(blobUrlRef.current);
|
|
@@ -29276,70 +29380,55 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29276
29380
|
};
|
|
29277
29381
|
}, [dispose]);
|
|
29278
29382
|
const initializePlayer = React24.useCallback(async () => {
|
|
29279
|
-
if (!videoRef.current || !
|
|
29383
|
+
if (!videoRef.current || !effectiveSrc) return;
|
|
29280
29384
|
const video = videoRef.current;
|
|
29281
29385
|
const player = playerLikeObject();
|
|
29282
29386
|
let authToken = null;
|
|
29283
29387
|
try {
|
|
29284
29388
|
authToken = await getAuthTokenForHls(supabase);
|
|
29285
|
-
if (authToken) {
|
|
29286
|
-
console.log("[HLS Auth] Retrieved token for R2 streaming");
|
|
29287
|
-
} else {
|
|
29389
|
+
if (!authToken) {
|
|
29288
29390
|
console.warn("[HLS Auth] No active session - R2 streaming may fail");
|
|
29289
29391
|
}
|
|
29290
29392
|
} catch (error) {
|
|
29291
|
-
console.error("[HLS Auth] Error
|
|
29393
|
+
console.error("[HLS Auth] Error getting auth token:", error);
|
|
29292
29394
|
}
|
|
29293
|
-
const r2WorkerDomain = "https://r2-stream-proxy.optifye-r2.workers.dev";
|
|
29294
29395
|
const mergedHlsConfig = {
|
|
29295
29396
|
...BASE_HLS_CONFIG,
|
|
29296
29397
|
...stableHlsConfigRef.current || {},
|
|
29297
29398
|
...stableOptionsRef.current || {},
|
|
29298
|
-
// Modern HLS.js uses Fetch API - override fetch to add Authorization header
|
|
29399
|
+
// Modern HLS.js uses Fetch API - override fetch to add Authorization header for R2 Worker requests
|
|
29299
29400
|
fetchSetup: function(context, initParams) {
|
|
29300
29401
|
const url = context.url;
|
|
29301
|
-
|
|
29302
|
-
|
|
29303
|
-
|
|
29304
|
-
|
|
29305
|
-
|
|
29306
|
-
if (isR2) {
|
|
29307
|
-
if (authToken) {
|
|
29308
|
-
initParams.headers = {
|
|
29309
|
-
...initParams.headers,
|
|
29310
|
-
"Authorization": `Bearer ${authToken}`
|
|
29311
|
-
};
|
|
29312
|
-
console.log("[HLS Auth] \u2705 Injected JWT for R2 request:", url);
|
|
29313
|
-
} else {
|
|
29314
|
-
console.warn("[HLS Auth] \u26A0\uFE0F No token available for R2 request:", url);
|
|
29315
|
-
}
|
|
29316
|
-
} else {
|
|
29317
|
-
console.log("[HLS Auth] CloudFront URL - no auth needed:", url);
|
|
29402
|
+
if (isR2WorkerUrl(url, r2WorkerDomain) && authToken) {
|
|
29403
|
+
initParams.headers = {
|
|
29404
|
+
...initParams.headers,
|
|
29405
|
+
"Authorization": `Bearer ${authToken}`
|
|
29406
|
+
};
|
|
29318
29407
|
}
|
|
29319
29408
|
return new Request(url, initParams);
|
|
29320
29409
|
}
|
|
29321
29410
|
};
|
|
29322
29411
|
cleanupBlobUrl();
|
|
29323
|
-
const isHLS =
|
|
29412
|
+
const isHLS = effectiveSrc.endsWith(".m3u8") || effectiveSrc.startsWith("#EXTM3U");
|
|
29324
29413
|
if (isHLS) {
|
|
29325
|
-
if (
|
|
29414
|
+
if (effectiveSrc.startsWith("#EXTM3U")) {
|
|
29326
29415
|
const safariMode = isSafari();
|
|
29327
29416
|
const browserName = getBrowserName();
|
|
29328
29417
|
if (safariMode) {
|
|
29329
|
-
const clipIdMatch =
|
|
29418
|
+
const clipIdMatch = effectiveSrc.match(/# Clip ID: ([a-f0-9-]+)/i);
|
|
29330
29419
|
if (clipIdMatch) {
|
|
29331
29420
|
const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
|
|
29332
29421
|
console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
|
|
29333
29422
|
video.src = proxyUrl;
|
|
29334
29423
|
} else {
|
|
29335
29424
|
console.warn("[HlsVideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
|
|
29336
|
-
const blob = new Blob([
|
|
29425
|
+
const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
|
|
29337
29426
|
const blobUrl = URL.createObjectURL(blob);
|
|
29338
29427
|
blobUrlRef.current = blobUrl;
|
|
29339
29428
|
video.src = blobUrl;
|
|
29340
29429
|
}
|
|
29341
29430
|
} else if (Hls3__default.default.isSupported()) {
|
|
29342
|
-
const blob = new Blob([
|
|
29431
|
+
const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
|
|
29343
29432
|
const blobUrl = URL.createObjectURL(blob);
|
|
29344
29433
|
blobUrlRef.current = blobUrl;
|
|
29345
29434
|
console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
|
|
@@ -29352,24 +29441,16 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29352
29441
|
});
|
|
29353
29442
|
hls.on(Hls3.Events.ERROR, (event, data) => {
|
|
29354
29443
|
console.error("[HlsVideoPlayer] HLS.js error:", data);
|
|
29444
|
+
if (maybeHandleR2Fallback(data)) {
|
|
29445
|
+
return;
|
|
29446
|
+
}
|
|
29355
29447
|
if (data.fatal) {
|
|
29356
29448
|
let errorInfo;
|
|
29357
29449
|
switch (data.type) {
|
|
29358
29450
|
case Hls3.ErrorTypes.NETWORK_ERROR:
|
|
29359
|
-
|
|
29360
|
-
|
|
29361
|
-
|
|
29362
|
-
type: "recoverable",
|
|
29363
|
-
message: "Authentication expired. Please refresh the page.",
|
|
29364
|
-
canRetry: true,
|
|
29365
|
-
details: "JWT_EXPIRED"
|
|
29366
|
-
};
|
|
29367
|
-
console.error("[HLS Auth] 401 Unauthorized - token may be expired");
|
|
29368
|
-
} else {
|
|
29369
|
-
errorInfo = ERROR_MAPPING.networkError;
|
|
29370
|
-
console.log("[HlsVideoPlayer] Attempting to recover from network error");
|
|
29371
|
-
hls.startLoad();
|
|
29372
|
-
}
|
|
29451
|
+
errorInfo = ERROR_MAPPING.networkError;
|
|
29452
|
+
console.log("[HlsVideoPlayer] Attempting to recover from network error");
|
|
29453
|
+
hls.startLoad();
|
|
29373
29454
|
break;
|
|
29374
29455
|
case Hls3.ErrorTypes.MEDIA_ERROR:
|
|
29375
29456
|
errorInfo = ERROR_MAPPING.mediaError;
|
|
@@ -29410,23 +29491,15 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29410
29491
|
});
|
|
29411
29492
|
hls.on(Hls3.Events.ERROR, (event, data) => {
|
|
29412
29493
|
console.error("[HlsVideoPlayer] HLS.js error:", data);
|
|
29494
|
+
if (maybeHandleR2Fallback(data)) {
|
|
29495
|
+
return;
|
|
29496
|
+
}
|
|
29413
29497
|
if (data.fatal) {
|
|
29414
29498
|
let errorInfo;
|
|
29415
29499
|
switch (data.type) {
|
|
29416
29500
|
case Hls3.ErrorTypes.NETWORK_ERROR:
|
|
29417
|
-
|
|
29418
|
-
|
|
29419
|
-
code: 401,
|
|
29420
|
-
type: "recoverable",
|
|
29421
|
-
message: "Authentication expired. Please refresh the page.",
|
|
29422
|
-
canRetry: true,
|
|
29423
|
-
details: "JWT_EXPIRED"
|
|
29424
|
-
};
|
|
29425
|
-
console.error("[HLS Auth] 401 Unauthorized - token may be expired");
|
|
29426
|
-
} else {
|
|
29427
|
-
errorInfo = ERROR_MAPPING.networkError;
|
|
29428
|
-
hls.startLoad();
|
|
29429
|
-
}
|
|
29501
|
+
errorInfo = ERROR_MAPPING.networkError;
|
|
29502
|
+
hls.startLoad();
|
|
29430
29503
|
break;
|
|
29431
29504
|
case Hls3.ErrorTypes.MEDIA_ERROR:
|
|
29432
29505
|
errorInfo = ERROR_MAPPING.mediaError;
|
|
@@ -29440,14 +29513,14 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29440
29513
|
eventCallbacksRef.current.onError?.(player, errorInfo);
|
|
29441
29514
|
}
|
|
29442
29515
|
});
|
|
29443
|
-
hls.loadSource(
|
|
29516
|
+
hls.loadSource(effectiveSrc);
|
|
29444
29517
|
hls.attachMedia(video);
|
|
29445
29518
|
} else {
|
|
29446
|
-
video.src =
|
|
29519
|
+
video.src = effectiveSrc;
|
|
29447
29520
|
}
|
|
29448
29521
|
}
|
|
29449
29522
|
} else {
|
|
29450
|
-
video.src =
|
|
29523
|
+
video.src = effectiveSrc;
|
|
29451
29524
|
}
|
|
29452
29525
|
const handleCanPlay = () => {
|
|
29453
29526
|
if (!hlsRef.current) {
|
|
@@ -29523,6 +29596,11 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29523
29596
|
eventCallbacksRef.current.onSeeked?.(player);
|
|
29524
29597
|
};
|
|
29525
29598
|
const handleError = () => {
|
|
29599
|
+
const currentSrc = video.currentSrc || video.src;
|
|
29600
|
+
if (isSafari() && currentSrc.includes("/api/clips/playlist/") && !currentSrc.includes("source=s3") && !r2FallbackAttemptedRef.current) {
|
|
29601
|
+
attemptS3Fallback("url", "native playback error", currentSrc);
|
|
29602
|
+
return;
|
|
29603
|
+
}
|
|
29526
29604
|
const error = video.error;
|
|
29527
29605
|
if (error) {
|
|
29528
29606
|
const errorInfo = {
|
|
@@ -29571,10 +29649,14 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29571
29649
|
video.removeEventListener("ratechange", handlePlaybackRateChange2);
|
|
29572
29650
|
};
|
|
29573
29651
|
}, [
|
|
29574
|
-
|
|
29652
|
+
effectiveSrc,
|
|
29575
29653
|
cleanupBlobUrl,
|
|
29576
29654
|
playerLikeObject,
|
|
29577
|
-
configVersion
|
|
29655
|
+
configVersion,
|
|
29656
|
+
supabase,
|
|
29657
|
+
attemptS3Fallback,
|
|
29658
|
+
maybeHandleR2Fallback,
|
|
29659
|
+
r2WorkerDomain
|
|
29578
29660
|
]);
|
|
29579
29661
|
React24.useEffect(() => {
|
|
29580
29662
|
let cleanup;
|
|
@@ -29590,7 +29672,7 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29590
29672
|
cleanupBlobUrl();
|
|
29591
29673
|
setIsReady(false);
|
|
29592
29674
|
};
|
|
29593
|
-
}, [
|
|
29675
|
+
}, [effectiveSrc, initializePlayer, cleanupBlobUrl]);
|
|
29594
29676
|
React24.useEffect(() => {
|
|
29595
29677
|
if (videoRef.current) {
|
|
29596
29678
|
if (autoplay) {
|
|
@@ -36420,7 +36502,16 @@ var STATIC_COLORS = {
|
|
|
36420
36502
|
"Operator Idle": "#8b5cf6"
|
|
36421
36503
|
// violet-500 - Low Priority/Behavioral
|
|
36422
36504
|
};
|
|
36505
|
+
var PRODUCTIVE_COLOR = "#00AB45";
|
|
36506
|
+
var IDLE_COLOR = "#e5e7eb";
|
|
36423
36507
|
var getColorForEntry = (name, index) => {
|
|
36508
|
+
const normalized = name.trim().toLowerCase();
|
|
36509
|
+
if (normalized === "productive" || normalized === "productive time") {
|
|
36510
|
+
return PRODUCTIVE_COLOR;
|
|
36511
|
+
}
|
|
36512
|
+
if (normalized === "idle" || normalized === "idle time") {
|
|
36513
|
+
return IDLE_COLOR;
|
|
36514
|
+
}
|
|
36424
36515
|
if (STATIC_COLORS[name]) {
|
|
36425
36516
|
return STATIC_COLORS[name];
|
|
36426
36517
|
}
|
|
@@ -36564,6 +36655,7 @@ var IdleTimeReasonChart = ({
|
|
|
36564
36655
|
)
|
|
36565
36656
|
] });
|
|
36566
36657
|
};
|
|
36658
|
+
var IdleTimeReasonChart_default = IdleTimeReasonChart;
|
|
36567
36659
|
var DEFAULT_PERFORMANCE_DATA = {
|
|
36568
36660
|
avg_efficiency: 0,
|
|
36569
36661
|
underperforming_workspaces: 0,
|
|
@@ -37373,7 +37465,7 @@ var LinePdfGenerator = ({
|
|
|
37373
37465
|
};
|
|
37374
37466
|
const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
|
|
37375
37467
|
const shiftDuration = hourlyTimeRanges.length || 11;
|
|
37376
|
-
const targetOutputPerHour = Math.round(lineInfo.metrics.
|
|
37468
|
+
const targetOutputPerHour = Math.round(lineInfo.metrics.threshold_pph ?? 0);
|
|
37377
37469
|
let hourlyActualOutput = [];
|
|
37378
37470
|
if (lineInfo.metrics.output_hourly && Object.keys(lineInfo.metrics.output_hourly).length > 0) {
|
|
37379
37471
|
const [startHourStr, startMinuteStr] = (lineInfo.metrics.shift_start || "6:00").split(":");
|
|
@@ -41481,6 +41573,17 @@ var SideNavBar = React24.memo(({
|
|
|
41481
41573
|
});
|
|
41482
41574
|
onMobileMenuClose?.();
|
|
41483
41575
|
}, [navigate, onMobileMenuClose]);
|
|
41576
|
+
const handleImprovementClick = React24.useCallback(() => {
|
|
41577
|
+
navigate("/improvement-center", {
|
|
41578
|
+
trackingEvent: {
|
|
41579
|
+
name: "Improvement Center Clicked",
|
|
41580
|
+
properties: {
|
|
41581
|
+
source: "side_nav"
|
|
41582
|
+
}
|
|
41583
|
+
}
|
|
41584
|
+
});
|
|
41585
|
+
onMobileMenuClose?.();
|
|
41586
|
+
}, [navigate, onMobileMenuClose]);
|
|
41484
41587
|
const handleTargetsClick = React24.useCallback(() => {
|
|
41485
41588
|
navigate("/targets", {
|
|
41486
41589
|
trackingEvent: {
|
|
@@ -41598,6 +41701,7 @@ var SideNavBar = React24.memo(({
|
|
|
41598
41701
|
const homeButtonClasses = React24.useMemo(() => getButtonClasses("/"), [getButtonClasses, pathname]);
|
|
41599
41702
|
const leaderboardButtonClasses = React24.useMemo(() => getButtonClasses("/leaderboard"), [getButtonClasses, pathname]);
|
|
41600
41703
|
const kpisButtonClasses = React24.useMemo(() => getButtonClasses("/kpis"), [getButtonClasses, pathname]);
|
|
41704
|
+
const improvementButtonClasses = React24.useMemo(() => getButtonClasses("/improvement-center"), [getButtonClasses, pathname]);
|
|
41601
41705
|
const targetsButtonClasses = React24.useMemo(() => getButtonClasses("/targets"), [getButtonClasses, pathname]);
|
|
41602
41706
|
const shiftsButtonClasses = React24.useMemo(() => getButtonClasses("/shifts"), [getButtonClasses, pathname]);
|
|
41603
41707
|
const teamManagementButtonClasses = React24.useMemo(() => getButtonClasses("/team-management"), [getButtonClasses, pathname]);
|
|
@@ -41665,6 +41769,22 @@ var SideNavBar = React24.memo(({
|
|
|
41665
41769
|
]
|
|
41666
41770
|
}
|
|
41667
41771
|
),
|
|
41772
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41773
|
+
"button",
|
|
41774
|
+
{
|
|
41775
|
+
onClick: handleImprovementClick,
|
|
41776
|
+
className: improvementButtonClasses,
|
|
41777
|
+
"aria-label": "Improvement Center",
|
|
41778
|
+
tabIndex: 0,
|
|
41779
|
+
role: "tab",
|
|
41780
|
+
"aria-selected": pathname === "/improvement-center" || pathname.startsWith("/improvement-center/"),
|
|
41781
|
+
children: [
|
|
41782
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.LightBulbIcon, { className: "w-5 h-5 mb-1" }),
|
|
41783
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight text-center", children: "Improvement" }),
|
|
41784
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px] font-semibold text-orange-500 uppercase tracking-wide bg-orange-50 px-1.5 py-0.5 rounded", children: "Beta" })
|
|
41785
|
+
]
|
|
41786
|
+
}
|
|
41787
|
+
),
|
|
41668
41788
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41669
41789
|
"button",
|
|
41670
41790
|
{
|
|
@@ -41865,6 +41985,19 @@ var SideNavBar = React24.memo(({
|
|
|
41865
41985
|
]
|
|
41866
41986
|
}
|
|
41867
41987
|
),
|
|
41988
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41989
|
+
"button",
|
|
41990
|
+
{
|
|
41991
|
+
onClick: handleMobileNavClick(handleImprovementClick),
|
|
41992
|
+
className: getMobileButtonClass("/improvement-center"),
|
|
41993
|
+
"aria-label": "Improvement Center",
|
|
41994
|
+
children: [
|
|
41995
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.LightBulbIcon, { className: getIconClass("/improvement-center") }),
|
|
41996
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-medium", children: "Improvement" }),
|
|
41997
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-[10px] font-semibold text-orange-500 uppercase tracking-wide bg-orange-50 px-1.5 py-0.5 rounded", children: "Beta" })
|
|
41998
|
+
]
|
|
41999
|
+
}
|
|
42000
|
+
),
|
|
41868
42001
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41869
42002
|
"button",
|
|
41870
42003
|
{
|
|
@@ -57679,135 +57812,258 @@ Please ensure:
|
|
|
57679
57812
|
}
|
|
57680
57813
|
var AuthenticatedTicketsView = withAuth(React24__namespace.default.memo(TicketsView));
|
|
57681
57814
|
var TicketsView_default = TicketsView;
|
|
57682
|
-
|
|
57683
|
-
|
|
57684
|
-
|
|
57685
|
-
|
|
57686
|
-
|
|
57687
|
-
|
|
57688
|
-
|
|
57689
|
-
|
|
57690
|
-
evidence: {
|
|
57691
|
-
type: "video",
|
|
57692
|
-
label: "Operator Cycle Recording",
|
|
57693
|
-
videoUrls: [
|
|
57694
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
|
|
57695
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
|
|
57696
|
-
// Mock duplicate for demo
|
|
57697
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
|
|
57698
|
-
// Mock duplicate for demo
|
|
57699
|
-
]
|
|
57700
|
-
},
|
|
57701
|
-
impact: "Potential 50% efficiency gain or standard adjustment needed.",
|
|
57702
|
-
date: "Today",
|
|
57703
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
57704
|
-
},
|
|
57705
|
-
{
|
|
57706
|
-
id: "rec-2",
|
|
57707
|
-
type: "efficiency",
|
|
57708
|
-
title: "Consistent Low Efficiency Period",
|
|
57709
|
-
location: "Line 2",
|
|
57710
|
-
line: "Line 2",
|
|
57711
|
-
description: "Efficiency from 7pm - 8pm is consistently lower than standard. This pattern has been observed for the last 3 days.",
|
|
57712
|
-
evidence: {
|
|
57713
|
-
type: "chart",
|
|
57714
|
-
label: "7pm - 8pm Efficiency Trend",
|
|
57715
|
-
chartData: [
|
|
57716
|
-
{ label: "7:00", value: 45, standard: 85 },
|
|
57717
|
-
{ label: "7:15", value: 42, standard: 85 },
|
|
57718
|
-
{ label: "7:30", value: 40, standard: 85 },
|
|
57719
|
-
{ label: "7:45", value: 48, standard: 85 },
|
|
57720
|
-
{ label: "8:00", value: 50, standard: 85 }
|
|
57721
|
-
]
|
|
57722
|
-
},
|
|
57723
|
-
impact: "Loss of approx. 40 units per hour during shift changeover.",
|
|
57724
|
-
date: "Today",
|
|
57725
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
57726
|
-
},
|
|
57727
|
-
{
|
|
57728
|
-
id: "rec-3",
|
|
57729
|
-
type: "downtime",
|
|
57730
|
-
title: "Recurring Micro-stoppages",
|
|
57731
|
-
location: "Unit 3: Conveyor",
|
|
57732
|
-
line: "Line 1",
|
|
57733
|
-
description: "Frequent micro-stoppages detected every 15 minutes. This suggests a potential jam or sensor issue.",
|
|
57734
|
-
evidence: {
|
|
57735
|
-
type: "chart",
|
|
57736
|
-
label: "Downtime Events",
|
|
57737
|
-
chartData: [
|
|
57738
|
-
{ label: "9:00", value: 2, standard: 0 },
|
|
57739
|
-
{ label: "9:15", value: 3, standard: 0 },
|
|
57740
|
-
{ label: "9:30", value: 1, standard: 0 },
|
|
57741
|
-
{ label: "9:45", value: 4, standard: 0 },
|
|
57742
|
-
{ label: "10:00", value: 2, standard: 0 }
|
|
57743
|
-
]
|
|
57744
|
-
},
|
|
57745
|
-
impact: "Cumulative downtime of 45 mins per shift.",
|
|
57746
|
-
date: "Yesterday",
|
|
57747
|
-
timestamp: new Date(Date.now() - 864e5).toISOString()
|
|
57748
|
-
},
|
|
57749
|
-
{
|
|
57750
|
-
id: "rec-4",
|
|
57751
|
-
type: "efficiency",
|
|
57752
|
-
title: "High Idle Time Detected",
|
|
57753
|
-
location: "Packaging Station",
|
|
57754
|
-
line: "Line 2",
|
|
57755
|
-
description: "Operator idle time exceeds 20% of shift duration. Consider rebalancing workload.",
|
|
57756
|
-
evidence: {
|
|
57757
|
-
type: "video",
|
|
57758
|
-
label: "Idle Time Observation",
|
|
57759
|
-
videoUrls: [
|
|
57760
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
|
|
57761
|
-
]
|
|
57762
|
-
},
|
|
57763
|
-
impact: "Potential to reassign 1 FTE to other tasks.",
|
|
57764
|
-
date: "Yesterday",
|
|
57765
|
-
timestamp: new Date(Date.now() - 864e5).toISOString()
|
|
57766
|
-
},
|
|
57767
|
-
{
|
|
57768
|
-
id: "rec-5",
|
|
57769
|
-
type: "cycle_time",
|
|
57770
|
-
title: "Slow Setup Time",
|
|
57771
|
-
location: "Machine 5",
|
|
57772
|
-
line: "Line 1",
|
|
57773
|
-
description: "Setup time for product changeover took 45 mins, standard is 20 mins.",
|
|
57774
|
-
evidence: {
|
|
57775
|
-
type: "chart",
|
|
57776
|
-
label: "Changeover Duration",
|
|
57777
|
-
chartData: [
|
|
57778
|
-
{ label: "Prev 1", value: 25, standard: 20 },
|
|
57779
|
-
{ label: "Prev 2", value: 22, standard: 20 },
|
|
57780
|
-
{ label: "Current", value: 45, standard: 20 },
|
|
57781
|
-
{ label: "Avg", value: 30, standard: 20 },
|
|
57782
|
-
{ label: "Target", value: 20, standard: 20 }
|
|
57783
|
-
]
|
|
57784
|
-
},
|
|
57785
|
-
impact: "25 mins of lost production time.",
|
|
57786
|
-
date: "2 Days Ago",
|
|
57787
|
-
timestamp: new Date(Date.now() - 1728e5).toISOString()
|
|
57815
|
+
|
|
57816
|
+
// src/lib/api/s3-clips-parser.ts
|
|
57817
|
+
function parseS3Uri(s3Uri, sopCategories) {
|
|
57818
|
+
const path = new URL(s3Uri).pathname;
|
|
57819
|
+
const parts = path.split("/").filter((p) => p);
|
|
57820
|
+
if (s3Uri.includes("missed_qchecks")) {
|
|
57821
|
+
console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
|
|
57822
|
+
return null;
|
|
57788
57823
|
}
|
|
57824
|
+
if (parts.length < 8) {
|
|
57825
|
+
console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
|
|
57826
|
+
return null;
|
|
57827
|
+
}
|
|
57828
|
+
try {
|
|
57829
|
+
const datePart = parts[2];
|
|
57830
|
+
const shiftPart = parts[3];
|
|
57831
|
+
const violationType = parts[4];
|
|
57832
|
+
let folderName = "";
|
|
57833
|
+
let timestamp = "";
|
|
57834
|
+
for (let i = 5; i < parts.length; i++) {
|
|
57835
|
+
const part = parts[i];
|
|
57836
|
+
if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
|
|
57837
|
+
folderName = part;
|
|
57838
|
+
const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
|
|
57839
|
+
if (timeMatch) {
|
|
57840
|
+
timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
|
|
57841
|
+
break;
|
|
57842
|
+
}
|
|
57843
|
+
}
|
|
57844
|
+
}
|
|
57845
|
+
if (!timestamp) {
|
|
57846
|
+
console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
|
|
57847
|
+
timestamp = "00:00:00";
|
|
57848
|
+
}
|
|
57849
|
+
let severity = "low";
|
|
57850
|
+
let type = "bottleneck";
|
|
57851
|
+
let description = "Analysis Clip";
|
|
57852
|
+
const normalizedViolationType = violationType.toLowerCase().trim();
|
|
57853
|
+
if (sopCategories && sopCategories.length > 0) {
|
|
57854
|
+
const matchedCategory = sopCategories.find((category) => {
|
|
57855
|
+
const categoryId = category.id.toLowerCase();
|
|
57856
|
+
const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
|
|
57857
|
+
return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
|
|
57858
|
+
normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
|
|
57859
|
+
});
|
|
57860
|
+
if (matchedCategory) {
|
|
57861
|
+
type = matchedCategory.id;
|
|
57862
|
+
description = matchedCategory.description || matchedCategory.label;
|
|
57863
|
+
if (matchedCategory.color.includes("red")) {
|
|
57864
|
+
severity = "high";
|
|
57865
|
+
} else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
|
|
57866
|
+
severity = "medium";
|
|
57867
|
+
} else {
|
|
57868
|
+
severity = "low";
|
|
57869
|
+
}
|
|
57870
|
+
console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
|
|
57871
|
+
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
57872
|
+
}
|
|
57873
|
+
}
|
|
57874
|
+
switch (normalizedViolationType) {
|
|
57875
|
+
case "idle_time":
|
|
57876
|
+
case "idle":
|
|
57877
|
+
case "low_value":
|
|
57878
|
+
case "low value":
|
|
57879
|
+
case "low_value_moment":
|
|
57880
|
+
case "low_value_moments":
|
|
57881
|
+
case "low value moment":
|
|
57882
|
+
case "low value moments":
|
|
57883
|
+
type = "low_value";
|
|
57884
|
+
severity = "low";
|
|
57885
|
+
description = "Idle Time Detected";
|
|
57886
|
+
break;
|
|
57887
|
+
case "sop_deviation":
|
|
57888
|
+
type = "missing_quality_check";
|
|
57889
|
+
severity = "high";
|
|
57890
|
+
description = "SOP Deviations";
|
|
57891
|
+
break;
|
|
57892
|
+
case "long_cycle_time":
|
|
57893
|
+
severity = "high";
|
|
57894
|
+
type = "long_cycle_time";
|
|
57895
|
+
description = "Long Cycle Time Detected";
|
|
57896
|
+
break;
|
|
57897
|
+
case "best_cycle_time":
|
|
57898
|
+
type = "best_cycle_time";
|
|
57899
|
+
severity = "low";
|
|
57900
|
+
description = "Best Cycle Time Performance";
|
|
57901
|
+
break;
|
|
57902
|
+
case "worst_cycle_time":
|
|
57903
|
+
type = "worst_cycle_time";
|
|
57904
|
+
severity = "high";
|
|
57905
|
+
description = "Worst Cycle Time Performance";
|
|
57906
|
+
break;
|
|
57907
|
+
case "cycle_completion":
|
|
57908
|
+
case "completed_cycles":
|
|
57909
|
+
case "completed_cycle":
|
|
57910
|
+
type = "cycle_completion";
|
|
57911
|
+
severity = "low";
|
|
57912
|
+
description = "Cycle Completion";
|
|
57913
|
+
break;
|
|
57914
|
+
case "running_cycle":
|
|
57915
|
+
case "active_cycle":
|
|
57916
|
+
case "production_cycle":
|
|
57917
|
+
type = "running_cycle";
|
|
57918
|
+
severity = "low";
|
|
57919
|
+
description = "Active Production Cycle";
|
|
57920
|
+
break;
|
|
57921
|
+
case "setup_state":
|
|
57922
|
+
case "machine_setup":
|
|
57923
|
+
case "line_setup":
|
|
57924
|
+
type = "setup_state";
|
|
57925
|
+
severity = "medium";
|
|
57926
|
+
description = "Machine Setup Activity";
|
|
57927
|
+
break;
|
|
57928
|
+
case "medium_bottleneck":
|
|
57929
|
+
severity = "medium";
|
|
57930
|
+
description = "Medium Bottleneck Identified";
|
|
57931
|
+
break;
|
|
57932
|
+
case "minor_bottleneck":
|
|
57933
|
+
case "mild_bottleneck":
|
|
57934
|
+
severity = "low";
|
|
57935
|
+
description = "Minor Bottleneck Identified";
|
|
57936
|
+
break;
|
|
57937
|
+
default:
|
|
57938
|
+
if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
|
|
57939
|
+
type = "missing_quality_check";
|
|
57940
|
+
severity = "high";
|
|
57941
|
+
description = "SOP Deviations";
|
|
57942
|
+
} else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
|
|
57943
|
+
type = "worst_cycle_time";
|
|
57944
|
+
severity = "high";
|
|
57945
|
+
description = "Worst Cycle Time Performance";
|
|
57946
|
+
} else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
|
|
57947
|
+
type = "best_cycle_time";
|
|
57948
|
+
severity = "low";
|
|
57949
|
+
description = "Best Cycle Time Performance";
|
|
57950
|
+
} else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
|
|
57951
|
+
type = "long_cycle_time";
|
|
57952
|
+
severity = "high";
|
|
57953
|
+
description = "Long Cycle Time Detected";
|
|
57954
|
+
} else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
|
|
57955
|
+
type = "cycle_completion";
|
|
57956
|
+
severity = "low";
|
|
57957
|
+
description = "Cycle Completion";
|
|
57958
|
+
} else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
|
|
57959
|
+
type = "running_cycle";
|
|
57960
|
+
severity = "low";
|
|
57961
|
+
description = "Active Production Cycle";
|
|
57962
|
+
} else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
|
|
57963
|
+
type = "setup_state";
|
|
57964
|
+
severity = "medium";
|
|
57965
|
+
description = "Machine Setup Activity";
|
|
57966
|
+
} else {
|
|
57967
|
+
description = `Clip type: ${violationType.replace(/_/g, " ")}`;
|
|
57968
|
+
console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
|
|
57969
|
+
}
|
|
57970
|
+
break;
|
|
57971
|
+
}
|
|
57972
|
+
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
57973
|
+
} catch (error) {
|
|
57974
|
+
console.error(`Error parsing S3 URI: ${s3Uri}`, error);
|
|
57975
|
+
return null;
|
|
57976
|
+
}
|
|
57977
|
+
}
|
|
57978
|
+
function shuffleArray(array) {
|
|
57979
|
+
const shuffled = [...array];
|
|
57980
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
57981
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
57982
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
57983
|
+
}
|
|
57984
|
+
return shuffled;
|
|
57985
|
+
}
|
|
57986
|
+
var CATEGORY_OPTIONS = [
|
|
57987
|
+
{ id: "all", label: "All" },
|
|
57988
|
+
{ id: "cycle_time", label: "Cycle Time" },
|
|
57989
|
+
{ id: "efficiency", label: "Efficiency" },
|
|
57990
|
+
{ id: "downtime", label: "Downtime" }
|
|
57789
57991
|
];
|
|
57790
|
-
var
|
|
57992
|
+
var ClipVideoCarousel = ({ clips, clipsService }) => {
|
|
57791
57993
|
const [currentIndex, setCurrentIndex] = React24.useState(0);
|
|
57994
|
+
const [videos, setVideos] = React24.useState([]);
|
|
57995
|
+
const [loading, setLoading] = React24.useState(false);
|
|
57996
|
+
const [error, setError] = React24.useState(null);
|
|
57997
|
+
React24.useEffect(() => {
|
|
57998
|
+
let cancelled = false;
|
|
57999
|
+
const load = async () => {
|
|
58000
|
+
if (!clipsService) {
|
|
58001
|
+
setError("Clips are not configured for this dashboard");
|
|
58002
|
+
return;
|
|
58003
|
+
}
|
|
58004
|
+
if (!clips || clips.length === 0) return;
|
|
58005
|
+
setLoading(true);
|
|
58006
|
+
setError(null);
|
|
58007
|
+
setVideos([]);
|
|
58008
|
+
try {
|
|
58009
|
+
const resolved = await Promise.all(
|
|
58010
|
+
clips.map(async (c) => {
|
|
58011
|
+
const video = await clipsService.getClipById(c.clip_id);
|
|
58012
|
+
if (!video) return null;
|
|
58013
|
+
const cycleTime = typeof c.cycle_time_seconds === "number" ? c.cycle_time_seconds : typeof c.cycle_sec === "number" ? c.cycle_sec : void 0;
|
|
58014
|
+
return {
|
|
58015
|
+
...video,
|
|
58016
|
+
cycle_time_seconds: cycleTime ?? video.cycle_time_seconds
|
|
58017
|
+
};
|
|
58018
|
+
})
|
|
58019
|
+
);
|
|
58020
|
+
if (cancelled) return;
|
|
58021
|
+
setVideos(resolved.filter(Boolean) || []);
|
|
58022
|
+
} catch (err) {
|
|
58023
|
+
if (cancelled) return;
|
|
58024
|
+
setError(err?.message || "Failed to load clips");
|
|
58025
|
+
setVideos([]);
|
|
58026
|
+
} finally {
|
|
58027
|
+
if (!cancelled) setLoading(false);
|
|
58028
|
+
}
|
|
58029
|
+
};
|
|
58030
|
+
load();
|
|
58031
|
+
return () => {
|
|
58032
|
+
cancelled = true;
|
|
58033
|
+
};
|
|
58034
|
+
}, [clipsService, clips]);
|
|
58035
|
+
React24.useEffect(() => {
|
|
58036
|
+
if (videos.length > 0 && currentIndex >= videos.length) {
|
|
58037
|
+
setCurrentIndex(0);
|
|
58038
|
+
}
|
|
58039
|
+
}, [videos.length, currentIndex]);
|
|
57792
58040
|
const nextVideo = () => {
|
|
57793
|
-
setCurrentIndex((prev) => (prev + 1) %
|
|
58041
|
+
setCurrentIndex((prev) => (prev + 1) % Math.max(videos.length, 1));
|
|
57794
58042
|
};
|
|
57795
58043
|
const prevVideo = () => {
|
|
57796
|
-
setCurrentIndex((prev) => (prev - 1 +
|
|
58044
|
+
setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
|
|
57797
58045
|
};
|
|
57798
|
-
if (!
|
|
58046
|
+
if (!clips || clips.length === 0) return null;
|
|
58047
|
+
const currentVideo = videos[currentIndex];
|
|
57799
58048
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group bg-gray-900 rounded-lg overflow-hidden aspect-video", children: [
|
|
57800
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57801
|
-
|
|
58049
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: "Loading clips\u2026" }),
|
|
58050
|
+
!loading && error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: error }),
|
|
58051
|
+
!loading && !error && currentVideo?.src && /* @__PURE__ */ jsxRuntime.jsx(
|
|
58052
|
+
VideoPlayer,
|
|
57802
58053
|
{
|
|
57803
|
-
src:
|
|
57804
|
-
className: "w-full h-full object-cover opacity-
|
|
57805
|
-
controls: true
|
|
58054
|
+
src: currentVideo.src,
|
|
58055
|
+
className: "w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity",
|
|
58056
|
+
controls: true,
|
|
58057
|
+
playsInline: true
|
|
57806
58058
|
},
|
|
57807
|
-
|
|
58059
|
+
currentVideo.src
|
|
57808
58060
|
),
|
|
57809
|
-
/* @__PURE__ */ jsxRuntime.
|
|
57810
|
-
|
|
58061
|
+
!loading && !error && typeof currentVideo?.cycle_time_seconds === "number" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-3 left-3 bg-black/60 text-white text-xs px-2 py-1 rounded-full font-medium pointer-events-none", children: [
|
|
58062
|
+
currentVideo.cycle_time_seconds.toFixed(1),
|
|
58063
|
+
"s"
|
|
58064
|
+
] }),
|
|
58065
|
+
!loading && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none group-hover:opacity-0 transition-opacity", children: /* @__PURE__ */ jsxRuntime.jsx(outline.PlayCircleIcon, { className: "w-16 h-16 text-white opacity-80" }) }),
|
|
58066
|
+
videos.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
57811
58067
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57812
58068
|
"button",
|
|
57813
58069
|
{
|
|
@@ -57835,70 +58091,273 @@ var VideoCarousel = ({ urls }) => {
|
|
|
57835
58091
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-3 right-3 bg-black/60 text-white text-xs px-2 py-1 rounded-full font-medium pointer-events-none", children: [
|
|
57836
58092
|
currentIndex + 1,
|
|
57837
58093
|
" / ",
|
|
57838
|
-
|
|
58094
|
+
videos.length
|
|
57839
58095
|
] })
|
|
57840
58096
|
] })
|
|
57841
58097
|
] });
|
|
57842
58098
|
};
|
|
57843
|
-
var
|
|
57844
|
-
const
|
|
57845
|
-
|
|
57846
|
-
|
|
57847
|
-
|
|
57848
|
-
|
|
58099
|
+
var BarChartEvidence = ({ x, y, unit, data, series, referenceLines }) => {
|
|
58100
|
+
const scale2 = unit === "fraction" ? 100 : 1;
|
|
58101
|
+
const yAxisUnit = unit === "fraction" ? "%" : "";
|
|
58102
|
+
const targetLineIndex = referenceLines?.findIndex(
|
|
58103
|
+
(line) => (line.label || "").toLowerCase().includes("target")
|
|
58104
|
+
) ?? -1;
|
|
58105
|
+
const resolvedTargetLineIndex = targetLineIndex >= 0 ? targetLineIndex : referenceLines?.length === 1 ? 0 : -1;
|
|
58106
|
+
const unitLabel = unit === "fraction" ? "%" : unit === "pieces" ? "PPH" : unit === "ratio" ? "" : unit || "";
|
|
58107
|
+
const formatTargetValue = (value) => {
|
|
58108
|
+
if (unit === "fraction") return value.toFixed(0);
|
|
58109
|
+
if (Number.isInteger(value)) return value.toString();
|
|
58110
|
+
return value.toFixed(1);
|
|
58111
|
+
};
|
|
58112
|
+
const formatTargetLabel = (label, value) => {
|
|
58113
|
+
const base = label || "Target";
|
|
58114
|
+
const suffix = unitLabel ? unitLabel === "%" ? "%" : ` ${unitLabel}` : "";
|
|
58115
|
+
return `${base}: ${formatTargetValue(value)}${suffix}`;
|
|
58116
|
+
};
|
|
58117
|
+
const isMultiSeries = Array.isArray(series) && series.length > 0 && Array.isArray(data);
|
|
58118
|
+
const chartData = isMultiSeries ? (data || []).map((row) => {
|
|
58119
|
+
const scaled = { ...row };
|
|
58120
|
+
series?.forEach((s) => {
|
|
58121
|
+
const raw = row[s.dataKey];
|
|
58122
|
+
if (typeof raw === "number") {
|
|
58123
|
+
scaled[s.dataKey] = raw * scale2;
|
|
58124
|
+
}
|
|
58125
|
+
});
|
|
58126
|
+
return scaled;
|
|
58127
|
+
}) : (x || []).map((xv, idx) => {
|
|
58128
|
+
const yv = y?.[idx];
|
|
58129
|
+
if (yv === null || yv === void 0) return null;
|
|
58130
|
+
const label = typeof xv === "number" ? `${xv.toString().padStart(2, "0")}:00` : String(xv);
|
|
58131
|
+
return { name: label, value: yv * scale2 };
|
|
58132
|
+
}).filter(Boolean);
|
|
58133
|
+
const referenceLineConfigs = (referenceLines || []).map((line, idx) => {
|
|
58134
|
+
const scaledValue = line.y * scale2;
|
|
58135
|
+
const isTarget = idx === resolvedTargetLineIndex;
|
|
58136
|
+
return {
|
|
58137
|
+
...line,
|
|
58138
|
+
y: scaledValue,
|
|
58139
|
+
label: isTarget ? formatTargetLabel(line.label, scaledValue) : line.label,
|
|
58140
|
+
stroke: isTarget ? "#E34329" : line.stroke,
|
|
58141
|
+
strokeDasharray: isTarget ? "5 5" : line.strokeDasharray,
|
|
58142
|
+
strokeWidth: isTarget ? 2 : line.strokeWidth
|
|
58143
|
+
};
|
|
58144
|
+
});
|
|
58145
|
+
const targetLine = resolvedTargetLineIndex >= 0 ? (referenceLines || [])[resolvedTargetLineIndex] : void 0;
|
|
58146
|
+
const targetValue = targetLine ? targetLine.y * scale2 : null;
|
|
58147
|
+
const targetLabel = targetLine && targetValue !== null ? formatTargetLabel(targetLine.label, targetValue) : null;
|
|
58148
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2 relative", children: [
|
|
58149
|
+
targetLabel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-2 right-2 z-10 pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 rounded-md border border-gray-200 bg-white/90 px-2 py-1 text-[11px] text-gray-700 shadow-sm", children: [
|
|
58150
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block w-6 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
|
|
58151
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: targetLabel })
|
|
58152
|
+
] }) }),
|
|
58153
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58154
|
+
BarChart,
|
|
58155
|
+
{
|
|
58156
|
+
data: chartData,
|
|
58157
|
+
bars: isMultiSeries ? (series || []).map((s) => ({
|
|
58158
|
+
dataKey: s.dataKey,
|
|
58159
|
+
name: s.name,
|
|
58160
|
+
fill: s.fill,
|
|
58161
|
+
stackId: s.stackId,
|
|
58162
|
+
labelList: false
|
|
58163
|
+
})) : [
|
|
58164
|
+
{
|
|
58165
|
+
dataKey: "value",
|
|
58166
|
+
name: "Value",
|
|
58167
|
+
fill: "#f87171",
|
|
58168
|
+
// red-400
|
|
58169
|
+
labelList: false
|
|
58170
|
+
}
|
|
58171
|
+
],
|
|
58172
|
+
xAxisDataKey: "name",
|
|
58173
|
+
showLegend: isMultiSeries && (series || []).length > 1,
|
|
58174
|
+
showGrid: true,
|
|
58175
|
+
aspect: 2.5,
|
|
58176
|
+
yAxisUnit,
|
|
58177
|
+
referenceLines: referenceLineConfigs,
|
|
58178
|
+
className: "h-full"
|
|
58179
|
+
}
|
|
58180
|
+
)
|
|
58181
|
+
] });
|
|
58182
|
+
};
|
|
58183
|
+
var PieChartEvidence = ({ data }) => {
|
|
58184
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2", children: /* @__PURE__ */ jsxRuntime.jsx(IdleTimeReasonChart_default, { data }) });
|
|
58185
|
+
};
|
|
58186
|
+
var LineChartEvidence = ({ x, y, unit }) => {
|
|
58187
|
+
const scale2 = unit === "fraction" ? 100 : 1;
|
|
58188
|
+
const yAxisUnit = unit === "fraction" ? "%" : "";
|
|
58189
|
+
const chartData = x.map((xv, idx) => {
|
|
58190
|
+
const yv = y[idx];
|
|
58191
|
+
if (yv === null || yv === void 0) return null;
|
|
58192
|
+
return { name: String(xv), value: yv * scale2 };
|
|
58193
|
+
}).filter(Boolean);
|
|
57849
58194
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
57850
|
-
|
|
58195
|
+
LineChart,
|
|
57851
58196
|
{
|
|
57852
58197
|
data: chartData,
|
|
57853
|
-
|
|
58198
|
+
lines: [
|
|
57854
58199
|
{
|
|
57855
58200
|
dataKey: "value",
|
|
57856
|
-
name: "
|
|
57857
|
-
|
|
57858
|
-
|
|
57859
|
-
|
|
58201
|
+
name: "Value",
|
|
58202
|
+
stroke: "#f87171",
|
|
58203
|
+
strokeWidth: 2,
|
|
58204
|
+
dot: true
|
|
57860
58205
|
}
|
|
57861
58206
|
],
|
|
57862
58207
|
xAxisDataKey: "name",
|
|
57863
58208
|
showLegend: false,
|
|
57864
58209
|
showGrid: true,
|
|
57865
58210
|
aspect: 2.5,
|
|
58211
|
+
yAxisUnit,
|
|
57866
58212
|
className: "h-full"
|
|
57867
58213
|
}
|
|
57868
58214
|
) });
|
|
57869
58215
|
};
|
|
58216
|
+
var TableEvidence = ({ columns, rows }) => {
|
|
58217
|
+
const formatValue = (value) => {
|
|
58218
|
+
if (typeof value === "number") {
|
|
58219
|
+
if (Number.isInteger(value)) return value.toString();
|
|
58220
|
+
return value.toFixed(3);
|
|
58221
|
+
}
|
|
58222
|
+
if (value === null || value === void 0) return "-";
|
|
58223
|
+
return String(value);
|
|
58224
|
+
};
|
|
58225
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-white border border-gray-100 rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full text-xs", children: [
|
|
58226
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50 border-b border-gray-100", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
58227
|
+
"th",
|
|
58228
|
+
{
|
|
58229
|
+
className: "px-3 py-2 text-left font-semibold text-gray-600 uppercase tracking-wide",
|
|
58230
|
+
children: col.replace(/_/g, " ")
|
|
58231
|
+
},
|
|
58232
|
+
col
|
|
58233
|
+
)) }) }),
|
|
58234
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row, idx) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-gray-100 last:border-b-0", children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2 text-gray-700 whitespace-nowrap", children: formatValue(row[col]) }, col)) }, idx)) })
|
|
58235
|
+
] }) }) });
|
|
58236
|
+
};
|
|
57870
58237
|
var ImprovementCenterView = () => {
|
|
57871
58238
|
const { navigate } = useNavigation();
|
|
57872
|
-
const
|
|
57873
|
-
const
|
|
57874
|
-
const
|
|
57875
|
-
const
|
|
57876
|
-
|
|
57877
|
-
|
|
57878
|
-
|
|
57879
|
-
const
|
|
57880
|
-
|
|
57881
|
-
|
|
57882
|
-
|
|
57883
|
-
|
|
57884
|
-
|
|
57885
|
-
|
|
57886
|
-
|
|
58239
|
+
const supabase = useSupabase();
|
|
58240
|
+
const { user } = useAuth();
|
|
58241
|
+
const dashboardConfig = useDashboardConfig();
|
|
58242
|
+
const entityConfig = useEntityConfig();
|
|
58243
|
+
const [currentDate, setCurrentDate] = React24.useState(/* @__PURE__ */ new Date());
|
|
58244
|
+
const [selectedCategory, setSelectedCategory] = React24.useState("all");
|
|
58245
|
+
const [selectedLineId, setSelectedLineId] = React24.useState("all");
|
|
58246
|
+
const [selectedStatus, setSelectedStatus] = React24.useState("all");
|
|
58247
|
+
const [recommendations, setRecommendations] = React24.useState([]);
|
|
58248
|
+
const [isLoading, setIsLoading] = React24.useState(false);
|
|
58249
|
+
const [loadError, setLoadError] = React24.useState(null);
|
|
58250
|
+
const computeWeeksOpen = (firstSeenAt) => {
|
|
58251
|
+
if (!firstSeenAt) return void 0;
|
|
58252
|
+
const firstSeen = new Date(firstSeenAt);
|
|
58253
|
+
if (Number.isNaN(firstSeen.getTime())) return void 0;
|
|
58254
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
58255
|
+
const diffDays = Math.max(0, Math.floor((now2.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
|
|
58256
|
+
return Math.max(1, Math.ceil(diffDays / 7));
|
|
58257
|
+
};
|
|
58258
|
+
const configuredLines = React24.useMemo(() => {
|
|
58259
|
+
return entityConfig.lines || entityConfig.lineNames || {};
|
|
58260
|
+
}, [entityConfig.lines, entityConfig.lineNames]);
|
|
58261
|
+
const configuredLineIds = React24.useMemo(() => Object.keys(configuredLines), [configuredLines]);
|
|
58262
|
+
const { accessibleLineIds } = useUserLineAccess(configuredLineIds);
|
|
58263
|
+
const scopeLineIds = React24.useMemo(() => {
|
|
58264
|
+
return accessibleLineIds.length > 0 ? accessibleLineIds : configuredLineIds;
|
|
58265
|
+
}, [accessibleLineIds, configuredLineIds]);
|
|
58266
|
+
const scopeLineIdsKey = React24.useMemo(() => scopeLineIds.join(","), [scopeLineIds]);
|
|
58267
|
+
const companyId = user?.company_id || entityConfig.companyId;
|
|
58268
|
+
const clipsService = React24.useMemo(() => {
|
|
58269
|
+
try {
|
|
58270
|
+
return new S3ClipsSupabaseService(dashboardConfig);
|
|
58271
|
+
} catch (err) {
|
|
58272
|
+
return null;
|
|
58273
|
+
}
|
|
58274
|
+
}, [dashboardConfig]);
|
|
58275
|
+
const nextMonth = () => {
|
|
58276
|
+
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1));
|
|
58277
|
+
};
|
|
58278
|
+
const prevMonth = () => {
|
|
58279
|
+
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1));
|
|
58280
|
+
};
|
|
58281
|
+
const formatMonth = (date) => {
|
|
58282
|
+
return date.toLocaleDateString("en-US", { month: "long", year: "numeric" });
|
|
58283
|
+
};
|
|
58284
|
+
React24.useEffect(() => {
|
|
58285
|
+
let cancelled = false;
|
|
58286
|
+
const fetchRecommendations = async () => {
|
|
58287
|
+
if (!supabase || !companyId) return;
|
|
58288
|
+
setIsLoading(true);
|
|
58289
|
+
setLoadError(null);
|
|
58290
|
+
try {
|
|
58291
|
+
if (scopeLineIds.length === 0) {
|
|
58292
|
+
setRecommendations([]);
|
|
58293
|
+
return;
|
|
58294
|
+
}
|
|
58295
|
+
const params = new URLSearchParams({
|
|
58296
|
+
company_id: companyId,
|
|
58297
|
+
status: "all",
|
|
58298
|
+
limit: "500"
|
|
58299
|
+
});
|
|
58300
|
+
params.set("month", `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, "0")}`);
|
|
58301
|
+
if (scopeLineIds.length > 0) {
|
|
58302
|
+
params.set("line_ids", scopeLineIds.join(","));
|
|
58303
|
+
}
|
|
58304
|
+
const res = await fetchBackendJson(supabase, `/api/improvement-center/recommendations?${params.toString()}`);
|
|
58305
|
+
if (cancelled) return;
|
|
58306
|
+
let recs = res.recommendations || [];
|
|
58307
|
+
recs = recs.map((r2) => ({
|
|
58308
|
+
...r2,
|
|
58309
|
+
ticket_status: r2.issue_status === "resolved" ? "solved" : "active",
|
|
58310
|
+
weeks_open: typeof r2.weeks_open === "number" ? r2.weeks_open : computeWeeksOpen(r2.first_seen_at) ?? 1
|
|
58311
|
+
}));
|
|
58312
|
+
const allowed = new Set(scopeLineIds);
|
|
58313
|
+
setRecommendations(recs.filter((r2) => !r2.line_id || allowed.has(r2.line_id)));
|
|
58314
|
+
} catch (err) {
|
|
58315
|
+
if (cancelled) return;
|
|
58316
|
+
setLoadError(err?.message || "Failed to load recommendations");
|
|
58317
|
+
setRecommendations([]);
|
|
58318
|
+
} finally {
|
|
58319
|
+
if (!cancelled) setIsLoading(false);
|
|
57887
58320
|
}
|
|
58321
|
+
};
|
|
58322
|
+
fetchRecommendations();
|
|
58323
|
+
return () => {
|
|
58324
|
+
cancelled = true;
|
|
58325
|
+
};
|
|
58326
|
+
}, [supabase, companyId, scopeLineIdsKey, currentDate]);
|
|
58327
|
+
const filteredRecommendations = React24.useMemo(() => {
|
|
58328
|
+
return recommendations.filter((rec) => {
|
|
58329
|
+
if (selectedCategory !== "all" && rec.type !== selectedCategory) return false;
|
|
58330
|
+
if (selectedLineId !== "all" && rec.line_id !== selectedLineId) return false;
|
|
58331
|
+
if (selectedStatus === "resolved" && rec.ticket_status !== "solved") return false;
|
|
58332
|
+
if (selectedStatus === "unresolved" && rec.ticket_status === "solved") return false;
|
|
57888
58333
|
return true;
|
|
57889
|
-
});
|
|
57890
|
-
}, [
|
|
57891
|
-
const
|
|
57892
|
-
|
|
57893
|
-
|
|
58334
|
+
}).sort((a, b) => (b.weeks_open || 0) - (a.weeks_open || 0));
|
|
58335
|
+
}, [recommendations, selectedCategory, selectedLineId, selectedStatus]);
|
|
58336
|
+
const stats = React24.useMemo(() => {
|
|
58337
|
+
const total = recommendations.length;
|
|
58338
|
+
const resolved = recommendations.filter((r2) => r2.ticket_status === "solved").length;
|
|
58339
|
+
return {
|
|
58340
|
+
all: total,
|
|
58341
|
+
resolved,
|
|
58342
|
+
unresolved: total - resolved
|
|
58343
|
+
};
|
|
58344
|
+
}, [recommendations]);
|
|
57894
58345
|
const clearFilters = () => {
|
|
57895
|
-
|
|
57896
|
-
|
|
57897
|
-
|
|
57898
|
-
|
|
58346
|
+
setSelectedCategory("all");
|
|
58347
|
+
setSelectedLineId("all");
|
|
58348
|
+
setSelectedStatus("all");
|
|
58349
|
+
};
|
|
58350
|
+
const lineOptions = React24.useMemo(() => {
|
|
58351
|
+
const options = [{ id: "all", label: "All Lines" }];
|
|
58352
|
+
scopeLineIds.forEach((lineId) => {
|
|
58353
|
+
const lineName = configuredLines[lineId] || lineId;
|
|
58354
|
+
options.push({ id: lineId, label: lineName });
|
|
58355
|
+
});
|
|
58356
|
+
return options;
|
|
58357
|
+
}, [scopeLineIds, configuredLines]);
|
|
57899
58358
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
57900
58359
|
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
57901
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
58360
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4 min-w-[120px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
57902
58361
|
BackButtonMinimal,
|
|
57903
58362
|
{
|
|
57904
58363
|
onClick: () => navigate("/"),
|
|
@@ -57906,121 +58365,190 @@ var ImprovementCenterView = () => {
|
|
|
57906
58365
|
}
|
|
57907
58366
|
) }),
|
|
57908
58367
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
57909
|
-
/* @__PURE__ */ jsxRuntime.
|
|
57910
|
-
|
|
57911
|
-
"Improvement Center"
|
|
57912
|
-
] }),
|
|
57913
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: "Daily AI-driven recommendations to optimize your line performance" })
|
|
58368
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "Improvement Center" }),
|
|
58369
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4 hidden sm:block", children: "Track and resolve persistent issues across your production lines" })
|
|
57914
58370
|
] }),
|
|
57915
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-
|
|
58371
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-[120px]" })
|
|
57916
58372
|
] }) }) }),
|
|
57917
|
-
/* @__PURE__ */ jsxRuntime.
|
|
57918
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
58373
|
+
/* @__PURE__ */ jsxRuntime.jsxs("main", { className: "flex-1 p-4 sm:p-6 max-w-7xl mx-auto w-full", children: [
|
|
58374
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 bg-white rounded-full px-4 py-2 border border-gray-200 shadow-sm", children: [
|
|
57919
58375
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57920
|
-
"input",
|
|
57921
|
-
{
|
|
57922
|
-
ref: dateInputRef,
|
|
57923
|
-
type: "date",
|
|
57924
|
-
value: selectedDate,
|
|
57925
|
-
onChange: (e) => setSelectedDate(e.target.value),
|
|
57926
|
-
className: "sr-only"
|
|
57927
|
-
}
|
|
57928
|
-
),
|
|
57929
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
57930
58376
|
"button",
|
|
57931
58377
|
{
|
|
57932
|
-
onClick:
|
|
57933
|
-
className:
|
|
57934
|
-
children:
|
|
57935
|
-
/* @__PURE__ */ jsxRuntime.jsx(outline.CalendarIcon, { className: "w-4 h-4" }),
|
|
57936
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: selectedDate || "All Dates" })
|
|
57937
|
-
]
|
|
58378
|
+
onClick: prevMonth,
|
|
58379
|
+
className: "p-1 rounded-full hover:bg-gray-100 text-gray-500 transition-colors",
|
|
58380
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(outline.ChevronLeftIcon, { className: "w-5 h-5" })
|
|
57938
58381
|
}
|
|
57939
|
-
)
|
|
57940
|
-
|
|
57941
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
57942
|
-
"select",
|
|
57943
|
-
{
|
|
57944
|
-
value: selectedLine,
|
|
57945
|
-
onChange: (e) => setSelectedLine(e.target.value),
|
|
57946
|
-
className: `px-3 py-1.5 text-sm border rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer ${selectedLine ? "bg-blue-50 border-blue-200 text-blue-700" : "border-gray-300 text-gray-700"}`,
|
|
57947
|
-
children: [
|
|
57948
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All Lines" }),
|
|
57949
|
-
uniqueLines.map((line) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: line, children: line }, line))
|
|
57950
|
-
]
|
|
57951
|
-
}
|
|
57952
|
-
),
|
|
57953
|
-
hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsx(
|
|
57954
|
-
"button",
|
|
57955
|
-
{
|
|
57956
|
-
onClick: clearFilters,
|
|
57957
|
-
className: "p-1.5 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors",
|
|
57958
|
-
title: "Clear filters",
|
|
57959
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(outline.XMarkIcon, { className: "w-5 h-5" })
|
|
57960
|
-
}
|
|
57961
|
-
)
|
|
57962
|
-
] }) }) }),
|
|
57963
|
-
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 p-4 sm:p-6 max-w-7xl mx-auto w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
|
|
57964
|
-
filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
57965
|
-
motion.div,
|
|
57966
|
-
{
|
|
57967
|
-
initial: { opacity: 0, y: 20 },
|
|
57968
|
-
animate: { opacity: 1, y: 0 },
|
|
57969
|
-
transition: { delay: index * 0.1 },
|
|
57970
|
-
className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden",
|
|
57971
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
|
|
57972
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-4", children: [
|
|
57973
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
57974
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${rec.type === "cycle_time" ? "bg-blue-100 text-blue-800" : rec.type === "efficiency" ? "bg-orange-100 text-orange-800" : "bg-gray-100 text-gray-800"}`, children: rec.type.replace("_", " ").toUpperCase() }),
|
|
57975
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 mt-2", children: rec.title }),
|
|
57976
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-500", children: [
|
|
57977
|
-
rec.location,
|
|
57978
|
-
" \u2022 ",
|
|
57979
|
-
rec.line,
|
|
57980
|
-
" \u2022 ",
|
|
57981
|
-
rec.date
|
|
57982
|
-
] })
|
|
57983
|
-
] }) }),
|
|
57984
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: rec.description }) }),
|
|
57985
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-amber-50 border border-amber-100 rounded-lg p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-amber-800 font-medium flex items-start gap-2", children: [
|
|
57986
|
-
/* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
|
|
57987
|
-
"Impact: ",
|
|
57988
|
-
rec.impact
|
|
57989
|
-
] }) })
|
|
57990
|
-
] }),
|
|
57991
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full md:w-1/2 lg:w-5/12 bg-gray-50 rounded-lg p-4 border border-gray-100", children: [
|
|
57992
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
57993
|
-
/* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
|
|
57994
|
-
rec.evidence.type === "video" ? /* @__PURE__ */ jsxRuntime.jsx(outline.PlayCircleIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-4 h-4" }),
|
|
57995
|
-
"Evidence: ",
|
|
57996
|
-
rec.evidence.label
|
|
57997
|
-
] }),
|
|
57998
|
-
rec.evidence.type === "video" && rec.evidence.videoUrls && rec.evidence.videoUrls.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] font-medium text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded", children: [
|
|
57999
|
-
rec.evidence.videoUrls.length,
|
|
58000
|
-
" clips"
|
|
58001
|
-
] })
|
|
58002
|
-
] }),
|
|
58003
|
-
rec.evidence.type === "video" && rec.evidence.videoUrls && /* @__PURE__ */ jsxRuntime.jsx(VideoCarousel, { urls: rec.evidence.videoUrls }),
|
|
58004
|
-
rec.evidence.type === "chart" && rec.evidence.chartData && /* @__PURE__ */ jsxRuntime.jsx(EvidenceChart, { data: rec.evidence.chartData })
|
|
58005
|
-
] })
|
|
58006
|
-
] }) })
|
|
58007
|
-
},
|
|
58008
|
-
rec.id
|
|
58009
|
-
)) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-dashed border-gray-300", children: [
|
|
58010
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex items-center justify-center w-12 h-12 rounded-full bg-gray-100 mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(outline.FunnelIcon, { className: "w-6 h-6 text-gray-400" }) }),
|
|
58011
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-gray-900", children: "No recommendations found" }),
|
|
58012
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Try adjusting your filters to see more results." }),
|
|
58382
|
+
),
|
|
58383
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-semibold text-gray-900 min-w-[160px] text-center", children: formatMonth(currentDate) }),
|
|
58013
58384
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58014
58385
|
"button",
|
|
58015
58386
|
{
|
|
58016
|
-
onClick:
|
|
58017
|
-
className: "
|
|
58018
|
-
children: "
|
|
58387
|
+
onClick: nextMonth,
|
|
58388
|
+
className: "p-1 rounded-full hover:bg-gray-100 text-gray-500 transition-colors",
|
|
58389
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(outline.ChevronRightIcon, { className: "w-5 h-5" })
|
|
58019
58390
|
}
|
|
58020
58391
|
)
|
|
58392
|
+
] }) }),
|
|
58393
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row items-center justify-between gap-4 mb-6", children: [
|
|
58394
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
58395
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
58396
|
+
"button",
|
|
58397
|
+
{
|
|
58398
|
+
onClick: () => setSelectedStatus("all"),
|
|
58399
|
+
className: `px-4 py-2 rounded-lg text-sm font-medium transition-all cursor-pointer ${selectedStatus === "all" ? "bg-white text-gray-900 shadow-sm ring-1 ring-black/5" : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"}`,
|
|
58400
|
+
children: [
|
|
58401
|
+
"All ",
|
|
58402
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1.5 bg-gray-100 px-2 py-0.5 rounded-full text-xs text-gray-600", children: stats.all })
|
|
58403
|
+
]
|
|
58404
|
+
}
|
|
58405
|
+
),
|
|
58406
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-px bg-gray-300 mx-1" }),
|
|
58407
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
58408
|
+
"button",
|
|
58409
|
+
{
|
|
58410
|
+
onClick: () => setSelectedStatus("resolved"),
|
|
58411
|
+
className: `flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm transition-all cursor-pointer ${selectedStatus === "resolved" ? "bg-green-50 text-green-700 ring-1 ring-green-200" : "text-gray-600 hover:bg-gray-100"}`,
|
|
58412
|
+
children: [
|
|
58413
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.CheckCircleIcon, { className: "w-4 h-4 text-green-500" }),
|
|
58414
|
+
"Resolved ",
|
|
58415
|
+
stats.resolved
|
|
58416
|
+
]
|
|
58417
|
+
}
|
|
58418
|
+
),
|
|
58419
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
58420
|
+
"button",
|
|
58421
|
+
{
|
|
58422
|
+
onClick: () => setSelectedStatus("unresolved"),
|
|
58423
|
+
className: `flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm transition-all cursor-pointer ${selectedStatus === "unresolved" ? "bg-red-50 text-red-700 ring-1 ring-red-200" : "text-gray-600 hover:bg-gray-100"}`,
|
|
58424
|
+
children: [
|
|
58425
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.XCircleIcon, { className: "w-4 h-4 text-red-500" }),
|
|
58426
|
+
"Unresolved ",
|
|
58427
|
+
stats.unresolved
|
|
58428
|
+
]
|
|
58429
|
+
}
|
|
58430
|
+
)
|
|
58431
|
+
] }),
|
|
58432
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 w-full md:w-auto", children: [
|
|
58433
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58434
|
+
"select",
|
|
58435
|
+
{
|
|
58436
|
+
value: selectedCategory,
|
|
58437
|
+
onChange: (e) => setSelectedCategory(e.target.value),
|
|
58438
|
+
className: "w-full md:w-auto appearance-none pl-3 pr-8 py-1.5 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer text-gray-700 shadow-sm",
|
|
58439
|
+
style: { backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`, backgroundPosition: `right 0.5rem center`, backgroundRepeat: `no-repeat`, backgroundSize: `1.5em 1.5em` },
|
|
58440
|
+
children: CATEGORY_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.id, children: opt.label }, opt.id))
|
|
58441
|
+
}
|
|
58442
|
+
),
|
|
58443
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58444
|
+
"select",
|
|
58445
|
+
{
|
|
58446
|
+
value: selectedLineId,
|
|
58447
|
+
onChange: (e) => setSelectedLineId(e.target.value),
|
|
58448
|
+
className: "w-full md:w-auto appearance-none pl-3 pr-8 py-1.5 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer text-gray-700 shadow-sm",
|
|
58449
|
+
style: { backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`, backgroundPosition: `right 0.5rem center`, backgroundRepeat: `no-repeat`, backgroundSize: `1.5em 1.5em` },
|
|
58450
|
+
children: lineOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.id, children: opt.label }, opt.id))
|
|
58451
|
+
}
|
|
58452
|
+
)
|
|
58453
|
+
] })
|
|
58021
58454
|
] }),
|
|
58022
|
-
|
|
58023
|
-
|
|
58455
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
|
|
58456
|
+
filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
58457
|
+
motion.div,
|
|
58458
|
+
{
|
|
58459
|
+
initial: { opacity: 0, y: 20 },
|
|
58460
|
+
animate: { opacity: 1, y: 0 },
|
|
58461
|
+
transition: { delay: index * 0.1 },
|
|
58462
|
+
className: `bg-white rounded-xl shadow-sm border overflow-hidden ${rec.ticket_status === "solved" ? "border-green-200" : "border-gray-200"}`,
|
|
58463
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
|
|
58464
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-4", children: [
|
|
58465
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 w-full", children: [
|
|
58466
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
58467
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${rec.type === "cycle_time" ? "bg-blue-100 text-blue-800" : rec.type === "efficiency" ? "bg-orange-100 text-orange-800" : "bg-gray-100 text-gray-800"}`, children: rec.type.replace("_", " ").toUpperCase() }),
|
|
58468
|
+
(rec.shift_label || rec.shift_id !== void 0) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-700", children: rec.shift_label || `Shift ${rec.shift_id}` }),
|
|
58469
|
+
rec.ticket_status === "solved" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800", children: [
|
|
58470
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.CheckCircleIcon, { className: "w-3.5 h-3.5" }),
|
|
58471
|
+
"Resolved"
|
|
58472
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium border ${(rec.weeks_open || 0) > 2 ? "bg-red-50 text-red-700 border-red-200" : "bg-amber-50 text-amber-700 border-amber-200"}`, children: [
|
|
58473
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ClockIcon, { className: "w-3.5 h-3.5" }),
|
|
58474
|
+
"Open for ",
|
|
58475
|
+
rec.weeks_open || 1,
|
|
58476
|
+
" week",
|
|
58477
|
+
(rec.weeks_open || 1) !== 1 ? "s" : ""
|
|
58478
|
+
] })
|
|
58479
|
+
] }),
|
|
58480
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
58481
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: rec.title }),
|
|
58482
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-500 mt-1", children: [
|
|
58483
|
+
rec.location,
|
|
58484
|
+
" \u2022 ",
|
|
58485
|
+
rec.line
|
|
58486
|
+
] })
|
|
58487
|
+
] })
|
|
58488
|
+
] }) }),
|
|
58489
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: rec.description }) }),
|
|
58490
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-amber-50 border border-amber-100 rounded-lg p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-amber-800 font-medium flex items-start gap-2", children: [
|
|
58491
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
|
|
58492
|
+
"Impact: ",
|
|
58493
|
+
rec.impact
|
|
58494
|
+
] }) })
|
|
58495
|
+
] }),
|
|
58496
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full md:w-1/2 lg:w-5/12 bg-gray-50 rounded-lg p-4 border border-gray-100", children: Array.isArray(rec.evidence) && rec.evidence.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: rec.evidence.map((ev, idx) => {
|
|
58497
|
+
const isVideo = ev.type === "video_gallery";
|
|
58498
|
+
const isChart = ev.type === "bar_chart" || ev.type === "timeseries" || ev.type === "pie_chart";
|
|
58499
|
+
const title = ev.title || "Evidence";
|
|
58500
|
+
const clips = Array.isArray(ev.clips) ? ev.clips : [];
|
|
58501
|
+
const clipCount = clips.length;
|
|
58502
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
58503
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
58504
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
|
|
58505
|
+
isVideo ? /* @__PURE__ */ jsxRuntime.jsx(outline.PlayCircleIcon, { className: "w-4 h-4" }) : isChart ? /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-4 h-4" }),
|
|
58506
|
+
title
|
|
58507
|
+
] }),
|
|
58508
|
+
isVideo && clipCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] font-medium text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded", children: [
|
|
58509
|
+
clipCount,
|
|
58510
|
+
" clips"
|
|
58511
|
+
] })
|
|
58512
|
+
] }),
|
|
58513
|
+
ev.type === "video_gallery" && /* @__PURE__ */ jsxRuntime.jsx(ClipVideoCarousel, { clips, clipsService }),
|
|
58514
|
+
ev.type === "bar_chart" && (Array.isArray(ev.x) && Array.isArray(ev.y) || Array.isArray(ev.data) && Array.isArray(ev.series)) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
58515
|
+
BarChartEvidence,
|
|
58516
|
+
{
|
|
58517
|
+
x: ev.x,
|
|
58518
|
+
y: ev.y,
|
|
58519
|
+
data: ev.data,
|
|
58520
|
+
series: ev.series,
|
|
58521
|
+
unit: ev.unit,
|
|
58522
|
+
referenceLines: Array.isArray(ev.reference_lines) ? ev.reference_lines : void 0
|
|
58523
|
+
}
|
|
58524
|
+
),
|
|
58525
|
+
ev.type === "pie_chart" && Array.isArray(ev.data) && /* @__PURE__ */ jsxRuntime.jsx(PieChartEvidence, { data: ev.data }),
|
|
58526
|
+
ev.type === "timeseries" && Array.isArray(ev.x) && Array.isArray(ev.y) && /* @__PURE__ */ jsxRuntime.jsx(LineChartEvidence, { x: ev.x, y: ev.y, unit: ev.unit }),
|
|
58527
|
+
ev.type === "table" && Array.isArray(ev.columns) && Array.isArray(ev.rows) && /* @__PURE__ */ jsxRuntime.jsx(TableEvidence, { columns: ev.columns, rows: ev.rows })
|
|
58528
|
+
] }, `${rec.id}-ev-${idx}`);
|
|
58529
|
+
}) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: isLoading ? "Loading evidence\u2026" : loadError ? loadError : "No evidence available" }) })
|
|
58530
|
+
] }) })
|
|
58531
|
+
},
|
|
58532
|
+
rec.id
|
|
58533
|
+
)) : (
|
|
58534
|
+
// Success State (Zero Tickets)
|
|
58535
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-gray-200 shadow-sm", children: [
|
|
58536
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-50 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(outline.CheckCircleIcon, { className: "w-8 h-8 text-green-500" }) }),
|
|
58537
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "No tickets found" }),
|
|
58538
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-gray-500 max-w-md mx-auto", children: selectedCategory !== "all" || selectedLineId !== "all" || selectedStatus !== "all" ? "Try adjusting your filters to see more results." : "All monitored long-term patterns were evaluated and remained within limits. This is a success state!" }),
|
|
58539
|
+
(selectedCategory !== "all" || selectedLineId !== "all" || selectedStatus !== "all") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
58540
|
+
"button",
|
|
58541
|
+
{
|
|
58542
|
+
onClick: clearFilters,
|
|
58543
|
+
className: "mt-6 text-sm text-blue-600 hover:text-blue-800 font-medium bg-blue-50 px-4 py-2 rounded-lg hover:bg-blue-100 transition-colors",
|
|
58544
|
+
children: "Clear filters"
|
|
58545
|
+
}
|
|
58546
|
+
)
|
|
58547
|
+
] })
|
|
58548
|
+
),
|
|
58549
|
+
filteredRecommendations.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-sm", children: "More recommendations will appear here as we analyze more data." }) })
|
|
58550
|
+
] })
|
|
58551
|
+
] })
|
|
58024
58552
|
] });
|
|
58025
58553
|
};
|
|
58026
58554
|
var ImprovementCenterView_default = ImprovementCenterView;
|
|
@@ -58479,177 +59007,6 @@ var streamProxyConfig = {
|
|
|
58479
59007
|
}
|
|
58480
59008
|
};
|
|
58481
59009
|
|
|
58482
|
-
// src/lib/api/s3-clips-parser.ts
|
|
58483
|
-
function parseS3Uri(s3Uri, sopCategories) {
|
|
58484
|
-
const path = new URL(s3Uri).pathname;
|
|
58485
|
-
const parts = path.split("/").filter((p) => p);
|
|
58486
|
-
if (s3Uri.includes("missed_qchecks")) {
|
|
58487
|
-
console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
|
|
58488
|
-
return null;
|
|
58489
|
-
}
|
|
58490
|
-
if (parts.length < 8) {
|
|
58491
|
-
console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
|
|
58492
|
-
return null;
|
|
58493
|
-
}
|
|
58494
|
-
try {
|
|
58495
|
-
const datePart = parts[2];
|
|
58496
|
-
const shiftPart = parts[3];
|
|
58497
|
-
const violationType = parts[4];
|
|
58498
|
-
let folderName = "";
|
|
58499
|
-
let timestamp = "";
|
|
58500
|
-
for (let i = 5; i < parts.length; i++) {
|
|
58501
|
-
const part = parts[i];
|
|
58502
|
-
if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
|
|
58503
|
-
folderName = part;
|
|
58504
|
-
const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
|
|
58505
|
-
if (timeMatch) {
|
|
58506
|
-
timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
|
|
58507
|
-
break;
|
|
58508
|
-
}
|
|
58509
|
-
}
|
|
58510
|
-
}
|
|
58511
|
-
if (!timestamp) {
|
|
58512
|
-
console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
|
|
58513
|
-
timestamp = "00:00:00";
|
|
58514
|
-
}
|
|
58515
|
-
let severity = "low";
|
|
58516
|
-
let type = "bottleneck";
|
|
58517
|
-
let description = "Analysis Clip";
|
|
58518
|
-
const normalizedViolationType = violationType.toLowerCase().trim();
|
|
58519
|
-
if (sopCategories && sopCategories.length > 0) {
|
|
58520
|
-
const matchedCategory = sopCategories.find((category) => {
|
|
58521
|
-
const categoryId = category.id.toLowerCase();
|
|
58522
|
-
const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
|
|
58523
|
-
return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
|
|
58524
|
-
normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
|
|
58525
|
-
});
|
|
58526
|
-
if (matchedCategory) {
|
|
58527
|
-
type = matchedCategory.id;
|
|
58528
|
-
description = matchedCategory.description || matchedCategory.label;
|
|
58529
|
-
if (matchedCategory.color.includes("red")) {
|
|
58530
|
-
severity = "high";
|
|
58531
|
-
} else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
|
|
58532
|
-
severity = "medium";
|
|
58533
|
-
} else {
|
|
58534
|
-
severity = "low";
|
|
58535
|
-
}
|
|
58536
|
-
console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
|
|
58537
|
-
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
58538
|
-
}
|
|
58539
|
-
}
|
|
58540
|
-
switch (normalizedViolationType) {
|
|
58541
|
-
case "idle_time":
|
|
58542
|
-
case "idle":
|
|
58543
|
-
case "low_value":
|
|
58544
|
-
case "low value":
|
|
58545
|
-
case "low_value_moment":
|
|
58546
|
-
case "low_value_moments":
|
|
58547
|
-
case "low value moment":
|
|
58548
|
-
case "low value moments":
|
|
58549
|
-
type = "low_value";
|
|
58550
|
-
severity = "low";
|
|
58551
|
-
description = "Idle Time Detected";
|
|
58552
|
-
break;
|
|
58553
|
-
case "sop_deviation":
|
|
58554
|
-
type = "missing_quality_check";
|
|
58555
|
-
severity = "high";
|
|
58556
|
-
description = "SOP Deviations";
|
|
58557
|
-
break;
|
|
58558
|
-
case "long_cycle_time":
|
|
58559
|
-
severity = "high";
|
|
58560
|
-
type = "long_cycle_time";
|
|
58561
|
-
description = "Long Cycle Time Detected";
|
|
58562
|
-
break;
|
|
58563
|
-
case "best_cycle_time":
|
|
58564
|
-
type = "best_cycle_time";
|
|
58565
|
-
severity = "low";
|
|
58566
|
-
description = "Best Cycle Time Performance";
|
|
58567
|
-
break;
|
|
58568
|
-
case "worst_cycle_time":
|
|
58569
|
-
type = "worst_cycle_time";
|
|
58570
|
-
severity = "high";
|
|
58571
|
-
description = "Worst Cycle Time Performance";
|
|
58572
|
-
break;
|
|
58573
|
-
case "cycle_completion":
|
|
58574
|
-
case "completed_cycles":
|
|
58575
|
-
case "completed_cycle":
|
|
58576
|
-
type = "cycle_completion";
|
|
58577
|
-
severity = "low";
|
|
58578
|
-
description = "Cycle Completion";
|
|
58579
|
-
break;
|
|
58580
|
-
case "running_cycle":
|
|
58581
|
-
case "active_cycle":
|
|
58582
|
-
case "production_cycle":
|
|
58583
|
-
type = "running_cycle";
|
|
58584
|
-
severity = "low";
|
|
58585
|
-
description = "Active Production Cycle";
|
|
58586
|
-
break;
|
|
58587
|
-
case "setup_state":
|
|
58588
|
-
case "machine_setup":
|
|
58589
|
-
case "line_setup":
|
|
58590
|
-
type = "setup_state";
|
|
58591
|
-
severity = "medium";
|
|
58592
|
-
description = "Machine Setup Activity";
|
|
58593
|
-
break;
|
|
58594
|
-
case "medium_bottleneck":
|
|
58595
|
-
severity = "medium";
|
|
58596
|
-
description = "Medium Bottleneck Identified";
|
|
58597
|
-
break;
|
|
58598
|
-
case "minor_bottleneck":
|
|
58599
|
-
case "mild_bottleneck":
|
|
58600
|
-
severity = "low";
|
|
58601
|
-
description = "Minor Bottleneck Identified";
|
|
58602
|
-
break;
|
|
58603
|
-
default:
|
|
58604
|
-
if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
|
|
58605
|
-
type = "missing_quality_check";
|
|
58606
|
-
severity = "high";
|
|
58607
|
-
description = "SOP Deviations";
|
|
58608
|
-
} else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
|
|
58609
|
-
type = "worst_cycle_time";
|
|
58610
|
-
severity = "high";
|
|
58611
|
-
description = "Worst Cycle Time Performance";
|
|
58612
|
-
} else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
|
|
58613
|
-
type = "best_cycle_time";
|
|
58614
|
-
severity = "low";
|
|
58615
|
-
description = "Best Cycle Time Performance";
|
|
58616
|
-
} else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
|
|
58617
|
-
type = "long_cycle_time";
|
|
58618
|
-
severity = "high";
|
|
58619
|
-
description = "Long Cycle Time Detected";
|
|
58620
|
-
} else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
|
|
58621
|
-
type = "cycle_completion";
|
|
58622
|
-
severity = "low";
|
|
58623
|
-
description = "Cycle Completion";
|
|
58624
|
-
} else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
|
|
58625
|
-
type = "running_cycle";
|
|
58626
|
-
severity = "low";
|
|
58627
|
-
description = "Active Production Cycle";
|
|
58628
|
-
} else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
|
|
58629
|
-
type = "setup_state";
|
|
58630
|
-
severity = "medium";
|
|
58631
|
-
description = "Machine Setup Activity";
|
|
58632
|
-
} else {
|
|
58633
|
-
description = `Clip type: ${violationType.replace(/_/g, " ")}`;
|
|
58634
|
-
console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
|
|
58635
|
-
}
|
|
58636
|
-
break;
|
|
58637
|
-
}
|
|
58638
|
-
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
58639
|
-
} catch (error) {
|
|
58640
|
-
console.error(`Error parsing S3 URI: ${s3Uri}`, error);
|
|
58641
|
-
return null;
|
|
58642
|
-
}
|
|
58643
|
-
}
|
|
58644
|
-
function shuffleArray(array) {
|
|
58645
|
-
const shuffled = [...array];
|
|
58646
|
-
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
58647
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
58648
|
-
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
58649
|
-
}
|
|
58650
|
-
return shuffled;
|
|
58651
|
-
}
|
|
58652
|
-
|
|
58653
59010
|
exports.ACTION_NAMES = ACTION_NAMES;
|
|
58654
59011
|
exports.AIAgentView = AIAgentView_default;
|
|
58655
59012
|
exports.AcceptInvite = AcceptInvite;
|