@optifye/dashboard-core 6.10.6 → 6.10.7

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.js CHANGED
@@ -3757,18 +3757,17 @@ var initializeCoreMixpanel = (token, debugOrOptions, trackPageViewArg) => {
3757
3757
  track_pageview: trackPageView ?? true,
3758
3758
  persistence: "localStorage"
3759
3759
  };
3760
- if (sessionOpts.recordSessionsPercent !== void 0) {
3761
- initOptions.record_sessions_percent = sessionOpts.recordSessionsPercent;
3762
- } else {
3763
- initOptions.record_sessions_percent = 1;
3764
- }
3765
- if (sessionOpts.recordIdleTimeoutMs !== void 0) {
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 = true;
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
- console.log("Mixpanel initialized in dashboard-core with Session Replay support.");
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 || !src) return;
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 retrieving token:", 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
- console.log("[HLS fetchSetup] Request URL:", url);
29302
- console.log("[HLS fetchSetup] R2 Worker domain:", r2WorkerDomain);
29303
- console.log("[HLS fetchSetup] Token available:", !!authToken);
29304
- const isR2 = isR2WorkerUrl(url, r2WorkerDomain);
29305
- console.log("[HLS fetchSetup] Is R2 Worker URL:", isR2);
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 = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
29412
+ const isHLS = effectiveSrc.endsWith(".m3u8") || effectiveSrc.startsWith("#EXTM3U");
29324
29413
  if (isHLS) {
29325
- if (src.startsWith("#EXTM3U")) {
29414
+ if (effectiveSrc.startsWith("#EXTM3U")) {
29326
29415
  const safariMode = isSafari();
29327
29416
  const browserName = getBrowserName();
29328
29417
  if (safariMode) {
29329
- const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
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([src], { type: "application/vnd.apple.mpegurl" });
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([src], { type: "application/vnd.apple.mpegurl" });
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
- if (data.response?.code === 401) {
29360
- errorInfo = {
29361
- code: 401,
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
- if (data.response?.code === 401) {
29418
- errorInfo = {
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(src);
29516
+ hls.loadSource(effectiveSrc);
29444
29517
  hls.attachMedia(video);
29445
29518
  } else {
29446
- video.src = src;
29519
+ video.src = effectiveSrc;
29447
29520
  }
29448
29521
  }
29449
29522
  } else {
29450
- video.src = 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
- src,
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
- }, [src, initializePlayer, cleanupBlobUrl]);
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,
@@ -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
- var MOCK_RECOMMENDATIONS = [
57683
- {
57684
- id: "rec-1",
57685
- type: "cycle_time",
57686
- title: "Cycle Time Deviation Detected",
57687
- location: "Unit 1: Station 7",
57688
- line: "Line 1",
57689
- description: "Cycle time observed was 90s but the standard set was 180s. This indicates a potential process deviation or standard mismatch.",
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 VideoCarousel = ({ urls }) => {
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) % urls.length);
58041
+ setCurrentIndex((prev) => (prev + 1) % Math.max(videos.length, 1));
57794
58042
  };
57795
58043
  const prevVideo = () => {
57796
- setCurrentIndex((prev) => (prev - 1 + urls.length) % urls.length);
58044
+ setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
57797
58045
  };
57798
- if (!urls || urls.length === 0) return null;
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
- "video",
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: urls[currentIndex],
57804
- className: "w-full h-full object-cover opacity-80 group-hover:opacity-100 transition-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
- urls[currentIndex]
58059
+ currentVideo.src
57808
58060
  ),
57809
- /* @__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" }) }),
57810
- urls.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
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
- urls.length
58094
+ videos.length
57839
58095
  ] })
57840
58096
  ] })
57841
58097
  ] });
57842
58098
  };
57843
- var EvidenceChart = ({ data }) => {
57844
- const chartData = data.map((point) => ({
57845
- name: point.label,
57846
- value: point.value,
57847
- standard: point.standard
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
- BarChart,
58195
+ LineChart,
57851
58196
  {
57852
58197
  data: chartData,
57853
- bars: [
58198
+ lines: [
57854
58199
  {
57855
58200
  dataKey: "value",
57856
- name: "Actual",
57857
- fill: "#f87171",
57858
- // red-400
57859
- labelList: true
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 [selectedDate, setSelectedDate] = React24.useState("");
57873
- const [selectedLine, setSelectedLine] = React24.useState("");
57874
- const dateInputRef = React24.useRef(null);
57875
- const uniqueLines = React24.useMemo(() => {
57876
- const lines = new Set(MOCK_RECOMMENDATIONS.map((r2) => r2.line));
57877
- return Array.from(lines);
57878
- }, []);
57879
- const filteredRecommendations = React24.useMemo(() => {
57880
- return MOCK_RECOMMENDATIONS.filter((rec) => {
57881
- if (selectedDate) {
57882
- const recDate = new Date(rec.timestamp).toISOString().split("T")[0];
57883
- if (recDate !== selectedDate) return false;
57884
- }
57885
- if (selectedLine && rec.line !== selectedLine) {
57886
- return false;
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
- }, [selectedDate, selectedLine]);
57891
- const handleDateClick = () => {
57892
- dateInputRef.current?.showPicker?.();
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
- setSelectedDate("");
57896
- setSelectedLine("");
57897
- };
57898
- const hasActiveFilters = selectedDate || selectedLine;
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.jsxs("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center flex items-center gap-2", children: [
57910
- /* @__PURE__ */ jsxRuntime.jsx(outline.LightBulbIcon, { className: "w-7 h-7 text-amber-500" }),
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-20" })
58371
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-[120px]" })
57916
58372
  ] }) }) }),
57917
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-gray-50 px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-7xl mx-auto flex items-center justify-end gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
57918
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
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: handleDateClick,
57933
- className: `flex items-center gap-2 px-3 py-1.5 text-sm border rounded-lg transition-colors whitespace-nowrap ${selectedDate ? "bg-blue-50 border-blue-200 text-blue-700" : "bg-white border-gray-300 text-gray-700 hover:bg-gray-50"}`,
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: clearFilters,
58017
- className: "mt-4 text-sm text-blue-600 hover:text-blue-800 font-medium",
58018
- children: "Clear all filters"
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
- 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." }) })
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;