@optifye/dashboard-core 6.10.5 → 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) {
@@ -28998,6 +29016,33 @@ var VideoControls = ({
28998
29016
  }
28999
29017
  );
29000
29018
  };
29019
+
29020
+ // src/lib/utils/r2Detection.ts
29021
+ function isR2WorkerUrl(url, r2WorkerDomain) {
29022
+ if (!url || !r2WorkerDomain) return false;
29023
+ try {
29024
+ const workerDomain = new URL(r2WorkerDomain).hostname;
29025
+ return url.includes(workerDomain);
29026
+ } catch {
29027
+ return url.includes(r2WorkerDomain.replace(/https?:\/\//, ""));
29028
+ }
29029
+ }
29030
+
29031
+ // src/lib/services/hlsAuthService.ts
29032
+ async function getAuthTokenForHls(supabase) {
29033
+ try {
29034
+ const { data: { session } } = await supabase.auth.getSession();
29035
+ if (!session?.access_token) {
29036
+ console.warn("[HLS Auth] No active session, R2 streaming may fail");
29037
+ return null;
29038
+ }
29039
+ console.log("[HLS Auth] Retrieved token for HLS.js requests");
29040
+ return session.access_token;
29041
+ } catch (error) {
29042
+ console.error("[HLS Auth] Error getting auth token:", error);
29043
+ return null;
29044
+ }
29045
+ }
29001
29046
  var ERROR_MAPPING = {
29002
29047
  "networkError": {
29003
29048
  code: 2,
@@ -29024,6 +29069,38 @@ var ERROR_MAPPING = {
29024
29069
  canRetry: false
29025
29070
  }
29026
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
+ };
29027
29104
  var hlsVideoPlayerStyles = `
29028
29105
  .hls-video-player-container {
29029
29106
  width: 100%;
@@ -29120,12 +29197,17 @@ var HlsVideoPlayer = React24.forwardRef(({
29120
29197
  onSeeked,
29121
29198
  onClick
29122
29199
  }, ref) => {
29200
+ const supabase = useSupabase();
29123
29201
  const videoContainerRef = React24.useRef(null);
29124
29202
  const videoRef = React24.useRef(null);
29125
29203
  const hlsRef = React24.useRef(null);
29126
29204
  const blobUrlRef = React24.useRef(null);
29205
+ const clipIdRef = React24.useRef(null);
29206
+ const r2FallbackAttemptedRef = React24.useRef(false);
29127
29207
  const [isReady, setIsReady] = React24.useState(false);
29128
29208
  const [isLoading, setIsLoading] = React24.useState(true);
29209
+ const [overrideSource, setOverrideSource] = React24.useState(null);
29210
+ const effectiveSrc = overrideSource && overrideSource.baseSrc === src ? overrideSource.value : src;
29129
29211
  const [showControls, setShowControls] = React24.useState(true);
29130
29212
  const [controlsPinned, setControlsPinned] = React24.useState(false);
29131
29213
  const [isPlaying, setIsPlaying] = React24.useState(false);
@@ -29185,10 +29267,16 @@ var HlsVideoPlayer = React24.forwardRef(({
29185
29267
  onSeeked,
29186
29268
  onLoadingChange
29187
29269
  ]);
29270
+ React24.useEffect(() => {
29271
+ clipIdRef.current = extractClipIdFromSource(src);
29272
+ r2FallbackAttemptedRef.current = false;
29273
+ setOverrideSource(null);
29274
+ }, [src]);
29188
29275
  const stableHlsConfigRef = React24.useRef(hlsConfig);
29189
29276
  const stableOptionsRef = React24.useRef(options);
29190
29277
  const configSignatureRef = React24.useRef("");
29191
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";
29192
29280
  React24.useEffect(() => {
29193
29281
  const serialized = JSON.stringify({
29194
29282
  hlsConfig: hlsConfig || null,
@@ -29207,6 +29295,50 @@ var HlsVideoPlayer = React24.forwardRef(({
29207
29295
  setConfigVersion((prev) => prev + 1);
29208
29296
  }
29209
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]);
29210
29342
  const cleanupBlobUrl = React24.useCallback(() => {
29211
29343
  if (blobUrlRef.current) {
29212
29344
  URL.revokeObjectURL(blobUrlRef.current);
@@ -29247,36 +29379,56 @@ var HlsVideoPlayer = React24.forwardRef(({
29247
29379
  dispose: () => dispose()
29248
29380
  };
29249
29381
  }, [dispose]);
29250
- const initializePlayer = React24.useCallback(() => {
29251
- if (!videoRef.current || !src) return;
29382
+ const initializePlayer = React24.useCallback(async () => {
29383
+ if (!videoRef.current || !effectiveSrc) return;
29252
29384
  const video = videoRef.current;
29253
29385
  const player = playerLikeObject();
29386
+ let authToken = null;
29387
+ try {
29388
+ authToken = await getAuthTokenForHls(supabase);
29389
+ if (!authToken) {
29390
+ console.warn("[HLS Auth] No active session - R2 streaming may fail");
29391
+ }
29392
+ } catch (error) {
29393
+ console.error("[HLS Auth] Error getting auth token:", error);
29394
+ }
29254
29395
  const mergedHlsConfig = {
29255
29396
  ...BASE_HLS_CONFIG,
29256
29397
  ...stableHlsConfigRef.current || {},
29257
- ...stableOptionsRef.current || {}
29398
+ ...stableOptionsRef.current || {},
29399
+ // Modern HLS.js uses Fetch API - override fetch to add Authorization header for R2 Worker requests
29400
+ fetchSetup: function(context, initParams) {
29401
+ const url = context.url;
29402
+ if (isR2WorkerUrl(url, r2WorkerDomain) && authToken) {
29403
+ initParams.headers = {
29404
+ ...initParams.headers,
29405
+ "Authorization": `Bearer ${authToken}`
29406
+ };
29407
+ }
29408
+ return new Request(url, initParams);
29409
+ }
29258
29410
  };
29259
29411
  cleanupBlobUrl();
29260
- const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
29412
+ const isHLS = effectiveSrc.endsWith(".m3u8") || effectiveSrc.startsWith("#EXTM3U");
29261
29413
  if (isHLS) {
29262
- if (src.startsWith("#EXTM3U")) {
29414
+ if (effectiveSrc.startsWith("#EXTM3U")) {
29263
29415
  const safariMode = isSafari();
29264
29416
  const browserName = getBrowserName();
29265
29417
  if (safariMode) {
29266
- const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
29418
+ const clipIdMatch = effectiveSrc.match(/# Clip ID: ([a-f0-9-]+)/i);
29267
29419
  if (clipIdMatch) {
29268
29420
  const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
29269
29421
  console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
29270
29422
  video.src = proxyUrl;
29271
29423
  } else {
29272
29424
  console.warn("[HlsVideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
29273
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
29425
+ const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
29274
29426
  const blobUrl = URL.createObjectURL(blob);
29275
29427
  blobUrlRef.current = blobUrl;
29276
29428
  video.src = blobUrl;
29277
29429
  }
29278
29430
  } else if (Hls3__default.default.isSupported()) {
29279
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
29431
+ const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
29280
29432
  const blobUrl = URL.createObjectURL(blob);
29281
29433
  blobUrlRef.current = blobUrl;
29282
29434
  console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
@@ -29289,6 +29441,9 @@ var HlsVideoPlayer = React24.forwardRef(({
29289
29441
  });
29290
29442
  hls.on(Hls3.Events.ERROR, (event, data) => {
29291
29443
  console.error("[HlsVideoPlayer] HLS.js error:", data);
29444
+ if (maybeHandleR2Fallback(data)) {
29445
+ return;
29446
+ }
29292
29447
  if (data.fatal) {
29293
29448
  let errorInfo;
29294
29449
  switch (data.type) {
@@ -29336,6 +29491,9 @@ var HlsVideoPlayer = React24.forwardRef(({
29336
29491
  });
29337
29492
  hls.on(Hls3.Events.ERROR, (event, data) => {
29338
29493
  console.error("[HlsVideoPlayer] HLS.js error:", data);
29494
+ if (maybeHandleR2Fallback(data)) {
29495
+ return;
29496
+ }
29339
29497
  if (data.fatal) {
29340
29498
  let errorInfo;
29341
29499
  switch (data.type) {
@@ -29355,14 +29513,14 @@ var HlsVideoPlayer = React24.forwardRef(({
29355
29513
  eventCallbacksRef.current.onError?.(player, errorInfo);
29356
29514
  }
29357
29515
  });
29358
- hls.loadSource(src);
29516
+ hls.loadSource(effectiveSrc);
29359
29517
  hls.attachMedia(video);
29360
29518
  } else {
29361
- video.src = src;
29519
+ video.src = effectiveSrc;
29362
29520
  }
29363
29521
  }
29364
29522
  } else {
29365
- video.src = src;
29523
+ video.src = effectiveSrc;
29366
29524
  }
29367
29525
  const handleCanPlay = () => {
29368
29526
  if (!hlsRef.current) {
@@ -29438,6 +29596,11 @@ var HlsVideoPlayer = React24.forwardRef(({
29438
29596
  eventCallbacksRef.current.onSeeked?.(player);
29439
29597
  };
29440
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
+ }
29441
29604
  const error = video.error;
29442
29605
  if (error) {
29443
29606
  const errorInfo = {
@@ -29486,13 +29649,20 @@ var HlsVideoPlayer = React24.forwardRef(({
29486
29649
  video.removeEventListener("ratechange", handlePlaybackRateChange2);
29487
29650
  };
29488
29651
  }, [
29489
- src,
29652
+ effectiveSrc,
29490
29653
  cleanupBlobUrl,
29491
29654
  playerLikeObject,
29492
- configVersion
29655
+ configVersion,
29656
+ supabase,
29657
+ attemptS3Fallback,
29658
+ maybeHandleR2Fallback,
29659
+ r2WorkerDomain
29493
29660
  ]);
29494
29661
  React24.useEffect(() => {
29495
- const cleanup = initializePlayer();
29662
+ let cleanup;
29663
+ initializePlayer().then((cleanupFn) => {
29664
+ cleanup = cleanupFn;
29665
+ });
29496
29666
  return () => {
29497
29667
  cleanup?.();
29498
29668
  if (hlsRef.current) {
@@ -29502,7 +29672,7 @@ var HlsVideoPlayer = React24.forwardRef(({
29502
29672
  cleanupBlobUrl();
29503
29673
  setIsReady(false);
29504
29674
  };
29505
- }, [src, initializePlayer, cleanupBlobUrl]);
29675
+ }, [effectiveSrc, initializePlayer, cleanupBlobUrl]);
29506
29676
  React24.useEffect(() => {
29507
29677
  if (videoRef.current) {
29508
29678
  if (autoplay) {
@@ -31699,6 +31869,30 @@ var FileManagerFilters = ({
31699
31869
  if (node.type === "category" || node.type === "percentile-category") {
31700
31870
  toggleExpanded(node.id);
31701
31871
  onFilterChange(node.id);
31872
+ if (node.id === "fast-cycles") {
31873
+ trackCoreEvent("Fast Clips Clicked", {
31874
+ workspaceId,
31875
+ date,
31876
+ shift,
31877
+ count: node.count || 0,
31878
+ percentile: filterState.percentile
31879
+ });
31880
+ } else if (node.id === "slow-cycles") {
31881
+ trackCoreEvent("Slow Clips Clicked", {
31882
+ workspaceId,
31883
+ date,
31884
+ shift,
31885
+ count: node.count || 0,
31886
+ percentile: filterState.percentile
31887
+ });
31888
+ } else if (node.id === "cycle_completion") {
31889
+ trackCoreEvent("Cycle Completions Clicked", {
31890
+ workspaceId,
31891
+ date,
31892
+ shift,
31893
+ count: node.count || 0
31894
+ });
31895
+ }
31702
31896
  if (node.id !== "idle_time" && idleLabelFilter) {
31703
31897
  setIdleLabelFilter(null);
31704
31898
  }
@@ -36308,7 +36502,16 @@ var STATIC_COLORS = {
36308
36502
  "Operator Idle": "#8b5cf6"
36309
36503
  // violet-500 - Low Priority/Behavioral
36310
36504
  };
36505
+ var PRODUCTIVE_COLOR = "#00AB45";
36506
+ var IDLE_COLOR = "#e5e7eb";
36311
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
+ }
36312
36515
  if (STATIC_COLORS[name]) {
36313
36516
  return STATIC_COLORS[name];
36314
36517
  }
@@ -36452,6 +36655,7 @@ var IdleTimeReasonChart = ({
36452
36655
  )
36453
36656
  ] });
36454
36657
  };
36658
+ var IdleTimeReasonChart_default = IdleTimeReasonChart;
36455
36659
  var DEFAULT_PERFORMANCE_DATA = {
36456
36660
  avg_efficiency: 0,
36457
36661
  underperforming_workspaces: 0,
@@ -37213,11 +37417,6 @@ var LinePdfGenerator = ({
37213
37417
  doc.setLineWidth(0.8);
37214
37418
  doc.line(20, 118, 190, 118);
37215
37419
  const hourlyOverviewStartY = 123;
37216
- doc.setFontSize(18);
37217
- doc.setFont("helvetica", "bold");
37218
- doc.setTextColor(40, 40, 40);
37219
- doc.text("Hourly Output Overview", 20, 133);
37220
- doc.setTextColor(0, 0, 0);
37221
37420
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
37222
37421
  const [hours, minutes] = startTimeStr.split(":");
37223
37422
  const startHour = parseInt(hours);
@@ -37408,23 +37607,43 @@ var LinePdfGenerator = ({
37408
37607
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
37409
37608
  });
37410
37609
  }
37411
- const tableHeaderY = 143;
37412
- const tableStartY = 151;
37610
+ const tableHeaderY = 146;
37611
+ const tableStartY = 153;
37413
37612
  const rowSpacing = 8;
37414
37613
  const bottomPadding = 8;
37415
37614
  const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
37416
37615
  const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
37417
37616
  doc.setFillColor(245, 245, 245);
37418
37617
  doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
37618
+ doc.setFontSize(18);
37619
+ doc.setFont("helvetica", "bold");
37620
+ doc.setTextColor(40, 40, 40);
37621
+ doc.text("Hourly Performance", 20, 133);
37622
+ doc.setTextColor(0, 0, 0);
37623
+ const gridTopY = 139;
37624
+ const headerBottomY = 148;
37625
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
37626
+ const totalRows = hourlyTimeRanges.length;
37627
+ const gridBottomY = headerBottomY + totalRows * rowSpacing;
37628
+ const tableHeight = gridBottomY - gridTopY;
37629
+ doc.setFillColor(255, 255, 255);
37630
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "F");
37631
+ doc.setDrawColor(230, 230, 230);
37632
+ doc.setLineWidth(0.2);
37633
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "S");
37634
+ doc.setDrawColor(200, 200, 200);
37635
+ doc.setLineWidth(0.3);
37636
+ doc.line(20, headerBottomY, 190, headerBottomY);
37637
+ colBoundaries.slice(1, -1).forEach((x) => {
37638
+ doc.line(x, gridTopY, x, gridBottomY);
37639
+ });
37419
37640
  doc.setFontSize(11);
37420
37641
  doc.setFont("helvetica", "bold");
37421
37642
  doc.text("Time Range", 25, tableHeaderY);
37422
- doc.text("Output", 80, tableHeaderY);
37423
- doc.text("Target", 125, tableHeaderY);
37424
- doc.text("Status", 170, tableHeaderY);
37425
- doc.setLineWidth(0.3);
37426
- doc.setDrawColor(200, 200, 200);
37427
- doc.line(20, 146, 190, 146);
37643
+ doc.text("Output", 75, tableHeaderY);
37644
+ doc.text("Target", 105, tableHeaderY);
37645
+ doc.text("Status", 135, tableHeaderY);
37646
+ doc.text("Remarks", 160, tableHeaderY);
37428
37647
  doc.setFont("helvetica", "normal");
37429
37648
  let yPos = tableStartY;
37430
37649
  const lineDateForTable = new Date(lineInfo.date);
@@ -37446,20 +37665,25 @@ var LinePdfGenerator = ({
37446
37665
  const dataCollected = !isTodayForTable || hourNumber < currentHour;
37447
37666
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
37448
37667
  const targetStr = targetOutputPerHour.toString();
37668
+ if (index < totalRows - 1) {
37669
+ const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
37670
+ doc.setDrawColor(200, 200, 200);
37671
+ doc.line(20, rowBottomY, 190, rowBottomY);
37672
+ }
37449
37673
  doc.text(timeRange, 25, yPos);
37450
- doc.text(outputStr, 80, yPos);
37451
- doc.text(targetStr, 125, yPos);
37674
+ doc.text(outputStr, 75, yPos);
37675
+ doc.text(targetStr, 105, yPos);
37452
37676
  if (!dataCollected) {
37453
37677
  doc.setTextColor(100, 100, 100);
37454
- doc.text("-", 170, yPos);
37678
+ doc.text("-", 135, yPos);
37455
37679
  } else if (actualOutput >= targetOutputPerHour) {
37456
37680
  doc.setTextColor(0, 171, 69);
37457
37681
  doc.setFont("ZapfDingbats", "normal");
37458
- doc.text("4", 170, yPos);
37682
+ doc.text("4", 135, yPos);
37459
37683
  doc.setFont("helvetica", "normal");
37460
37684
  } else {
37461
37685
  doc.setTextColor(227, 67, 41);
37462
- doc.text("\xD7", 170, yPos);
37686
+ doc.text("\xD7", 135, yPos);
37463
37687
  }
37464
37688
  doc.setTextColor(0, 0, 0);
37465
37689
  yPos += rowSpacing;
@@ -37467,7 +37691,7 @@ var LinePdfGenerator = ({
37467
37691
  doc.addPage();
37468
37692
  yPos = addHeaderPage2();
37469
37693
  const workspaceSectionStartY = yPos;
37470
- const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
37694
+ const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 3);
37471
37695
  const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
37472
37696
  const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
37473
37697
  doc.setFillColor(245, 245, 245);
@@ -37475,28 +37699,45 @@ var LinePdfGenerator = ({
37475
37699
  doc.setFontSize(18);
37476
37700
  doc.setFont("helvetica", "bold");
37477
37701
  doc.setTextColor(40, 40, 40);
37478
- doc.text("Poorest Performing Workspaces", 20, yPos);
37702
+ doc.text("Poorest Performing Workspaces", 20, yPos + 10);
37479
37703
  doc.setTextColor(0, 0, 0);
37480
- yPos += 10;
37704
+ yPos += 20;
37705
+ const wsGridTopY = yPos - 5;
37706
+ const wsHeaderBottomY = wsGridTopY + 8;
37707
+ const wsColBoundaries = [20, 80, 140, 190];
37708
+ const wsGridBottomY = wsHeaderBottomY + wsRowCount * 8;
37709
+ const wsTableTotalHeight = wsGridBottomY - wsGridTopY;
37710
+ doc.setFillColor(255, 255, 255);
37711
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "F");
37712
+ doc.setDrawColor(230, 230, 230);
37713
+ doc.setLineWidth(0.2);
37714
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "S");
37715
+ doc.setDrawColor(200, 200, 200);
37716
+ doc.setLineWidth(0.3);
37717
+ doc.line(20, wsHeaderBottomY, 190, wsHeaderBottomY);
37718
+ wsColBoundaries.slice(1, -1).forEach((x) => {
37719
+ doc.line(x, wsGridTopY, x, wsGridBottomY);
37720
+ });
37481
37721
  doc.setFontSize(11);
37482
37722
  doc.setFont("helvetica", "bold");
37483
- yPos += 5;
37723
+ yPos = wsGridTopY + 5.5;
37484
37724
  doc.text("Workspace", 25, yPos);
37485
37725
  doc.text("Current/Target", 85, yPos);
37486
37726
  doc.text("Efficiency", 145, yPos);
37487
- yPos += 3;
37488
- doc.setLineWidth(0.3);
37489
- doc.setDrawColor(200, 200, 200);
37490
- doc.line(20, yPos, 190, yPos);
37491
37727
  doc.setFont("helvetica", "normal");
37492
- yPos += 7;
37728
+ yPos = wsHeaderBottomY + 5.5;
37493
37729
  if (sortedWorkspaces.length === 0) {
37494
37730
  doc.text("No workspace data available", 25, yPos);
37495
- yPos += 10;
37731
+ yPos += 8;
37496
37732
  } else {
37497
37733
  sortedWorkspaces.forEach((ws, index) => {
37498
37734
  const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
37499
37735
  const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
37736
+ if (index < wsRowCount - 1) {
37737
+ const rowBottomY = wsHeaderBottomY + (index + 1) * 8;
37738
+ doc.setDrawColor(200, 200, 200);
37739
+ doc.line(20, rowBottomY, 190, rowBottomY);
37740
+ }
37500
37741
  doc.text(truncatedName, 25, yPos);
37501
37742
  doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
37502
37743
  doc.text(`${(ws.efficiency || 0).toFixed(1)}%`, 145, yPos);
@@ -38839,18 +39080,27 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38839
39080
  const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38840
39081
  doc.text("Hourly Performance", 20, hourlyTitleY);
38841
39082
  doc.setTextColor(0, 0, 0);
38842
- doc.setFontSize(headerFontSize);
38843
- doc.setFont("helvetica", "bold");
38844
39083
  const baseHeaderY = hasIdleTimeReason ? 208 : 198;
38845
39084
  const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
38846
- doc.text("Time Range", 25, headerY);
38847
- doc.text("Output", 95, headerY);
38848
- doc.text("Target", 135, headerY);
38849
- doc.text("Status", 170, headerY);
38850
- doc.setLineWidth(0.3);
39085
+ const gridTopY = headerY - 5;
39086
+ const headerBottomY = gridTopY + 8;
39087
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
39088
+ const totalRows = hourlyData.length;
39089
+ const gridBottomY = headerBottomY + totalRows * rowHeight;
38851
39090
  doc.setDrawColor(200, 200, 200);
38852
- const separatorY = headerY + 3;
38853
- doc.line(20, separatorY, 190, separatorY);
39091
+ doc.setLineWidth(0.3);
39092
+ doc.line(20, gridTopY, 190, gridTopY);
39093
+ doc.line(20, headerBottomY, 190, headerBottomY);
39094
+ colBoundaries.forEach((x) => {
39095
+ doc.line(x, gridTopY, x, gridBottomY);
39096
+ });
39097
+ doc.setFontSize(headerFontSize);
39098
+ doc.setFont("helvetica", "bold");
39099
+ doc.text("Time Range", 25, headerY);
39100
+ doc.text("Output", 75, headerY);
39101
+ doc.text("Target", 105, headerY);
39102
+ doc.text("Status", 135, headerY);
39103
+ doc.text("Remarks", 160, headerY);
38854
39104
  doc.setFontSize(contentFontSize);
38855
39105
  doc.setFont("helvetica", "normal");
38856
39106
  let yPos = tableStartY;
@@ -38881,20 +39131,23 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38881
39131
  const dataCollected = !isToday2 || hourNumber < currentHour;
38882
39132
  const outputStr = dataCollected ? output.toString() : "TBD";
38883
39133
  const targetStr = hourlyTarget.toString();
39134
+ const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39135
+ doc.setDrawColor(200, 200, 200);
39136
+ doc.line(20, rowBottomY, 190, rowBottomY);
38884
39137
  doc.text(timeRange, 25, yPos);
38885
- doc.text(outputStr, 95, yPos);
38886
- doc.text(targetStr, 135, yPos);
39138
+ doc.text(outputStr, 75, yPos);
39139
+ doc.text(targetStr, 105, yPos);
38887
39140
  if (!dataCollected) {
38888
39141
  doc.setTextColor(100, 100, 100);
38889
- doc.text("-", 170, yPos);
39142
+ doc.text("-", 135, yPos);
38890
39143
  } else if (output >= hourlyTarget) {
38891
39144
  doc.setTextColor(0, 171, 69);
38892
39145
  doc.setFont("ZapfDingbats", "normal");
38893
- doc.text("4", 170, yPos);
39146
+ doc.text("4", 135, yPos);
38894
39147
  doc.setFont("helvetica", "normal");
38895
39148
  } else {
38896
39149
  doc.setTextColor(227, 67, 41);
38897
- doc.text("\xD7", 170, yPos);
39150
+ doc.text("\xD7", 135, yPos);
38898
39151
  }
38899
39152
  doc.setTextColor(0, 0, 0);
38900
39153
  yPos += rowHeight;
@@ -39700,14 +39953,16 @@ var KPICard = ({
39700
39953
  "uppercase tracking-wide sm:tracking-wider"
39701
39954
  ), children: title }),
39702
39955
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-0.5 sm:gap-2 flex-wrap", children: [
39703
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39704
- "font-bold text-gray-900 dark:text-gray-50",
39705
- "text-base sm:text-xl md:text-2xl"
39706
- ), children: isLoading ? "\u2014" : formattedValue }),
39707
- suffix && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39708
- "font-medium text-gray-600 dark:text-gray-300",
39709
- style.compact ? "text-base" : "text-lg"
39710
- ), children: suffix }),
39956
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-5 sm:h-6 md:h-7 w-12 sm:w-16 md:w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39957
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39958
+ "font-bold text-gray-900 dark:text-gray-50",
39959
+ "text-base sm:text-xl md:text-2xl"
39960
+ ), children: formattedValue }),
39961
+ suffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39962
+ "font-medium text-gray-600 dark:text-gray-300",
39963
+ style.compact ? "text-base" : "text-lg"
39964
+ ), children: suffix })
39965
+ ] }),
39711
39966
  !isLoading && trendInfo.shouldShowTrend && trendInfo.trendValue !== "neutral" && title !== "Output" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: trendInfo.colorClass, children: /* @__PURE__ */ jsxRuntime.jsx(
39712
39967
  trendInfo.Icon,
39713
39968
  {
@@ -39866,6 +40121,7 @@ var KPIHeader = ({
39866
40121
  };
39867
40122
  var KPISection = React24.memo(({
39868
40123
  kpis,
40124
+ isLoading = false,
39869
40125
  className,
39870
40126
  layout: layout2 = "row",
39871
40127
  gridColumns,
@@ -39874,7 +40130,8 @@ var KPISection = React24.memo(({
39874
40130
  compactCards = false,
39875
40131
  useSrcLayout = false
39876
40132
  }) => {
39877
- const outputDifference = kpis.outputProgress.current - kpis.outputProgress.idealOutput;
40133
+ const showSkeleton = isLoading || !kpis;
40134
+ const outputDifference = kpis ? kpis.outputProgress.current - kpis.outputProgress.idealOutput : 0;
39878
40135
  const outputIsOnTarget = outputDifference >= 0;
39879
40136
  if (useSrcLayout) {
39880
40137
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -39891,27 +40148,30 @@ var KPISection = React24.memo(({
39891
40148
  KPICard,
39892
40149
  {
39893
40150
  title: "Underperforming",
39894
- value: "2/3",
39895
- change: 0
40151
+ value: showSkeleton ? "" : "2/3",
40152
+ change: 0,
40153
+ isLoading: showSkeleton
39896
40154
  }
39897
40155
  ) }),
39898
40156
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
39899
40157
  KPICard,
39900
40158
  {
39901
40159
  title: "Efficiency",
39902
- value: kpis.efficiency.value,
39903
- change: kpis.efficiency.change,
39904
- suffix: "%"
40160
+ value: showSkeleton ? "" : kpis.efficiency.value,
40161
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40162
+ suffix: "%",
40163
+ isLoading: showSkeleton
39905
40164
  }
39906
40165
  ) }),
39907
40166
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
39908
40167
  KPICard,
39909
40168
  {
39910
40169
  title: "Output Progress",
39911
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39912
- change: kpis.outputProgress.change,
40170
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40171
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
39913
40172
  outputDifference,
39914
- showOutputDetails: true
40173
+ showOutputDetails: !showSkeleton,
40174
+ isLoading: showSkeleton
39915
40175
  }
39916
40176
  ) })
39917
40177
  ]
@@ -39922,34 +40182,30 @@ var KPISection = React24.memo(({
39922
40182
  {
39923
40183
  key: "underperforming",
39924
40184
  title: "Underperforming",
39925
- value: `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
39926
- change: kpis.underperformingWorkers.change
40185
+ value: showSkeleton ? "" : `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
40186
+ change: showSkeleton ? 0 : kpis.underperformingWorkers.change,
40187
+ suffix: void 0,
40188
+ status: void 0
39927
40189
  },
39928
40190
  {
39929
40191
  key: "efficiency",
39930
40192
  title: "Efficiency",
39931
- value: kpis.efficiency.value,
39932
- change: kpis.efficiency.change,
39933
- suffix: "%"
40193
+ value: showSkeleton ? "" : kpis.efficiency.value,
40194
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40195
+ suffix: "%",
40196
+ status: void 0
39934
40197
  },
39935
40198
  {
39936
40199
  key: "outputProgress",
39937
40200
  title: "Output Progress",
39938
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39939
- change: kpis.outputProgress.change,
39940
- status: {
40201
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40202
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
40203
+ suffix: void 0,
40204
+ status: showSkeleton ? void 0 : {
39941
40205
  tooltipText: outputIsOnTarget ? "On Target" : "Off Target",
39942
40206
  positive: outputIsOnTarget
39943
40207
  }
39944
40208
  }
39945
- // Only include these additional KPIs if not using the src layout
39946
- // ...(kpis.qualityCompliance ? [{
39947
- // key: 'qualityCompliance',
39948
- // title: 'Quality Compliance',
39949
- // value: kpis.qualityCompliance.value,
39950
- // change: kpis.qualityCompliance.change,
39951
- // suffix: '%',
39952
- // }] : []),
39953
40209
  ];
39954
40210
  return /* @__PURE__ */ jsxRuntime.jsx(
39955
40211
  KPIGrid,
@@ -39966,7 +40222,8 @@ var KPISection = React24.memo(({
39966
40222
  change: kpi.change,
39967
40223
  suffix: kpi.suffix,
39968
40224
  status: kpi.status,
39969
- style: { variant: cardVariant, compact: compactCards }
40225
+ style: { variant: cardVariant, compact: compactCards },
40226
+ isLoading: showSkeleton
39970
40227
  },
39971
40228
  kpi.key
39972
40229
  ))
@@ -39975,6 +40232,9 @@ var KPISection = React24.memo(({
39975
40232
  }, (prevProps, nextProps) => {
39976
40233
  const prevKpis = prevProps.kpis;
39977
40234
  const nextKpis = nextProps.kpis;
40235
+ if (prevProps.isLoading !== nextProps.isLoading) return false;
40236
+ if (!prevKpis && !nextKpis) return true;
40237
+ if (!prevKpis || !nextKpis) return false;
39978
40238
  if (prevKpis === nextKpis) return true;
39979
40239
  if (Math.abs(prevKpis.efficiency.value - nextKpis.efficiency.value) >= 0.5) return false;
39980
40240
  if (prevKpis.underperformingWorkers.current !== nextKpis.underperformingWorkers.current || prevKpis.underperformingWorkers.total !== nextKpis.underperformingWorkers.total) return false;
@@ -41313,6 +41573,17 @@ var SideNavBar = React24.memo(({
41313
41573
  });
41314
41574
  onMobileMenuClose?.();
41315
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]);
41316
41587
  const handleTargetsClick = React24.useCallback(() => {
41317
41588
  navigate("/targets", {
41318
41589
  trackingEvent: {
@@ -41430,6 +41701,7 @@ var SideNavBar = React24.memo(({
41430
41701
  const homeButtonClasses = React24.useMemo(() => getButtonClasses("/"), [getButtonClasses, pathname]);
41431
41702
  const leaderboardButtonClasses = React24.useMemo(() => getButtonClasses("/leaderboard"), [getButtonClasses, pathname]);
41432
41703
  const kpisButtonClasses = React24.useMemo(() => getButtonClasses("/kpis"), [getButtonClasses, pathname]);
41704
+ const improvementButtonClasses = React24.useMemo(() => getButtonClasses("/improvement-center"), [getButtonClasses, pathname]);
41433
41705
  const targetsButtonClasses = React24.useMemo(() => getButtonClasses("/targets"), [getButtonClasses, pathname]);
41434
41706
  const shiftsButtonClasses = React24.useMemo(() => getButtonClasses("/shifts"), [getButtonClasses, pathname]);
41435
41707
  const teamManagementButtonClasses = React24.useMemo(() => getButtonClasses("/team-management"), [getButtonClasses, pathname]);
@@ -41497,6 +41769,22 @@ var SideNavBar = React24.memo(({
41497
41769
  ]
41498
41770
  }
41499
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
+ ),
41500
41788
  /* @__PURE__ */ jsxRuntime.jsxs(
41501
41789
  "button",
41502
41790
  {
@@ -41697,6 +41985,19 @@ var SideNavBar = React24.memo(({
41697
41985
  ]
41698
41986
  }
41699
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
+ ),
41700
42001
  /* @__PURE__ */ jsxRuntime.jsxs(
41701
42002
  "button",
41702
42003
  {
@@ -45727,7 +46028,8 @@ var TeamUsagePdfGenerator = ({
45727
46028
  usageData,
45728
46029
  daysInRange = 1,
45729
46030
  title = "Team Usage Report",
45730
- className
46031
+ className,
46032
+ iconOnly = false
45731
46033
  }) => {
45732
46034
  const [isGenerating, setIsGenerating] = React24.useState(false);
45733
46035
  const generatePDF = async () => {
@@ -45907,10 +46209,11 @@ var TeamUsagePdfGenerator = ({
45907
46209
  {
45908
46210
  onClick: generatePDF,
45909
46211
  disabled: isGenerating || !usageData,
45910
- className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
46212
+ className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 ${iconOnly ? "p-2" : "px-4 py-2"} text-sm font-medium text-gray-700 hover:bg-gray-200 active:bg-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
46213
+ "aria-label": iconOnly ? isGenerating ? "Generating PDF..." : "Export PDF" : void 0,
45911
46214
  children: [
45912
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" }),
45913
- isGenerating ? "Generating..." : "Export PDF"
46215
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: iconOnly ? "h-5 w-5" : "h-4 w-4" }),
46216
+ !iconOnly && (isGenerating ? "Generating..." : "Export PDF")
45914
46217
  ]
45915
46218
  }
45916
46219
  );
@@ -48679,10 +48982,19 @@ function HomeView({
48679
48982
  const [errorMessage, setErrorMessage] = React24.useState(null);
48680
48983
  const [displayNamesInitialized, setDisplayNamesInitialized] = React24.useState(false);
48681
48984
  const [hasInitialDataLoaded, setHasInitialDataLoaded] = React24.useState(false);
48985
+ const KPI_CACHE_KEY = "optifye_kpi_cache";
48682
48986
  const dashboardConfig = useDashboardConfig();
48683
48987
  const entityConfig = useEntityConfig();
48684
48988
  const supabaseClient = useSupabaseClient();
48685
48989
  const { user } = useAuth();
48990
+ const { lines: dbLines } = useLines();
48991
+ const mergedLineNames = React24.useMemo(() => {
48992
+ const merged = { ...lineNames };
48993
+ dbLines.forEach((line) => {
48994
+ merged[line.id] = line.line_name;
48995
+ });
48996
+ return merged;
48997
+ }, [lineNames, dbLines]);
48686
48998
  const isSupervisor = user?.role_level === "supervisor";
48687
48999
  const hasMultipleLines = allLineIds.length > 1;
48688
49000
  const availableLineIds = React24.useMemo(() => {
@@ -48789,6 +49101,42 @@ function HomeView({
48789
49101
  }
48790
49102
  return buildKPIsFromLineMetricsRow(row);
48791
49103
  }, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
49104
+ const [cachedKpis, setCachedKpis] = React24.useState(() => {
49105
+ if (typeof window === "undefined") return null;
49106
+ try {
49107
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
49108
+ if (cached) {
49109
+ const parsed = JSON.parse(cached);
49110
+ return parsed[selectedLineId] || parsed[factoryViewId] || null;
49111
+ }
49112
+ } catch (e) {
49113
+ }
49114
+ return null;
49115
+ });
49116
+ React24.useEffect(() => {
49117
+ if (kpis && typeof window !== "undefined") {
49118
+ try {
49119
+ const existing = localStorage.getItem(KPI_CACHE_KEY);
49120
+ const cache = existing ? JSON.parse(existing) : {};
49121
+ cache[selectedLineId] = kpis;
49122
+ localStorage.setItem(KPI_CACHE_KEY, JSON.stringify(cache));
49123
+ setCachedKpis(kpis);
49124
+ } catch (e) {
49125
+ }
49126
+ }
49127
+ }, [kpis, selectedLineId, KPI_CACHE_KEY]);
49128
+ React24.useEffect(() => {
49129
+ if (typeof window === "undefined") return;
49130
+ try {
49131
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
49132
+ if (cached) {
49133
+ const parsed = JSON.parse(cached);
49134
+ setCachedKpis(parsed[selectedLineId] || null);
49135
+ }
49136
+ } catch (e) {
49137
+ }
49138
+ }, [selectedLineId, KPI_CACHE_KEY]);
49139
+ const displayKpis = kpis || cachedKpis;
48792
49140
  const {
48793
49141
  activeBreaks: allActiveBreaks,
48794
49142
  isLoading: breaksLoading,
@@ -49090,16 +49438,16 @@ function HomeView({
49090
49438
  // Use stable string representation instead of spreading array
49091
49439
  JSON.stringify(workspaceMetrics.map((w) => `${w.workspace_uuid}-${Math.round(w.efficiency)}-${w.trend}`))
49092
49440
  ]);
49093
- const memoizedKPIs = React24.useMemo(() => kpis, [
49441
+ const memoizedKPIs = React24.useMemo(() => displayKpis, [
49094
49442
  // Only update reference when values change by at least 1%
49095
- kpis?.efficiency?.value ? Math.round(kpis.efficiency.value) : null,
49096
- kpis?.underperformingWorkers?.current,
49097
- kpis?.underperformingWorkers?.total,
49098
- kpis?.outputProgress?.current,
49099
- kpis?.outputProgress?.target,
49100
- kpis?.avgCycleTime?.value ? Math.round(kpis.avgCycleTime.value * 10) / 10 : null,
49443
+ displayKpis?.efficiency?.value ? Math.round(displayKpis.efficiency.value) : null,
49444
+ displayKpis?.underperformingWorkers?.current,
49445
+ displayKpis?.underperformingWorkers?.total,
49446
+ displayKpis?.outputProgress?.current,
49447
+ displayKpis?.outputProgress?.target,
49448
+ displayKpis?.avgCycleTime?.value ? Math.round(displayKpis.avgCycleTime.value * 10) / 10 : null,
49101
49449
  // Round to 1 decimal
49102
- kpis?.qualityCompliance?.value ? Math.round(kpis.qualityCompliance.value) : null
49450
+ displayKpis?.qualityCompliance?.value ? Math.round(displayKpis.qualityCompliance.value) : null
49103
49451
  ]);
49104
49452
  React24.useEffect(() => {
49105
49453
  setIsHydrated(true);
@@ -49117,7 +49465,7 @@ function HomeView({
49117
49465
  trackCoreEvent("Home Line Filter Changed", {
49118
49466
  previous_line_id: selectedLineId,
49119
49467
  new_line_id: value,
49120
- line_name: lineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49468
+ line_name: mergedLineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49121
49469
  });
49122
49470
  try {
49123
49471
  sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
@@ -49151,9 +49499,9 @@ function HomeView({
49151
49499
  }
49152
49500
  return /* @__PURE__ */ jsxRuntime.jsxs(Select, { onValueChange: handleLineChange, defaultValue: selectedLineId, children: [
49153
49501
  /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[200px] bg-white border border-gray-200 shadow-sm rounded-md h-9 sm:h-9 text-xs sm:text-sm px-2 sm:px-3", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Select a line" }) }),
49154
- /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: lineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49502
+ /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: mergedLineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49155
49503
  ] });
49156
- }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
49504
+ }, [availableLineIds, handleLineChange, selectedLineId, mergedLineNames, factoryViewId, allLineIds.length]);
49157
49505
  const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
49158
49506
  const isDataLoading = metricsLoading;
49159
49507
  if (isInitialLoading) {
@@ -49182,7 +49530,7 @@ function HomeView({
49182
49530
  lineTitle,
49183
49531
  lineId: selectedLineId === factoryViewId ? allLineIds[0] : selectedLineId,
49184
49532
  className: "w-full",
49185
- headerControls: memoizedKPIs ? /* @__PURE__ */ jsxRuntime.jsx(KPISection2, { kpis: memoizedKPIs, className: "w-full sm:w-auto" }) : null
49533
+ headerControls: /* @__PURE__ */ jsxRuntime.jsx(KPISection2, { kpis: memoizedKPIs, isLoading: !memoizedKPIs, className: "w-full sm:w-auto" })
49186
49534
  }
49187
49535
  ) }) }),
49188
49536
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative", children: [
@@ -56471,35 +56819,62 @@ var TeamManagementView = ({
56471
56819
  ) }) });
56472
56820
  }
56473
56821
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("min-h-screen bg-slate-50", className), children: [
56474
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", 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: [
56475
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56476
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56477
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56478
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56479
- ] }),
56480
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
56481
- canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
56482
- TeamUsagePdfGenerator,
56483
- {
56484
- users,
56485
- usageData,
56486
- daysInRange: usageDateRange.daysElapsed,
56487
- title: "Team Usage Report"
56488
- }
56489
- ),
56490
- canAddUsers && /* @__PURE__ */ jsxRuntime.jsxs(
56491
- "button",
56492
- {
56493
- onClick: () => setIsAddUserDialogOpen(true),
56494
- className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56495
- children: [
56496
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-4 h-4" }),
56497
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add User" })
56498
- ]
56499
- }
56500
- )
56822
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-6 lg:px-8 py-3 sm:py-4", children: [
56823
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
56824
+ /* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }),
56825
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900 truncate px-2", children: pageTitle }) }),
56826
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
56827
+ canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
56828
+ TeamUsagePdfGenerator,
56829
+ {
56830
+ users,
56831
+ usageData,
56832
+ daysInRange: usageDateRange.daysElapsed,
56833
+ title: "Team Usage Report",
56834
+ iconOnly: true
56835
+ }
56836
+ ),
56837
+ canAddUsers && /* @__PURE__ */ jsxRuntime.jsx(
56838
+ "button",
56839
+ {
56840
+ onClick: () => setIsAddUserDialogOpen(true),
56841
+ className: "p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 active:bg-blue-800 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56842
+ "aria-label": "Add User",
56843
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-5 h-5" })
56844
+ }
56845
+ )
56846
+ ] })
56847
+ ] }) }),
56848
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden sm:flex items-center justify-between", children: [
56849
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56850
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56851
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56852
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56853
+ ] }),
56854
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
56855
+ canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
56856
+ TeamUsagePdfGenerator,
56857
+ {
56858
+ users,
56859
+ usageData,
56860
+ daysInRange: usageDateRange.daysElapsed,
56861
+ title: "Team Usage Report"
56862
+ }
56863
+ ),
56864
+ canAddUsers && /* @__PURE__ */ jsxRuntime.jsxs(
56865
+ "button",
56866
+ {
56867
+ onClick: () => setIsAddUserDialogOpen(true),
56868
+ className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56869
+ children: [
56870
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-4 h-4" }),
56871
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add User" })
56872
+ ]
56873
+ }
56874
+ )
56875
+ ] })
56501
56876
  ] })
56502
- ] }) }) }),
56877
+ ] }) }),
56503
56878
  /* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-4 sm:px-6 lg:px-8 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(
56504
56879
  UserManagementTable,
56505
56880
  {
@@ -57437,135 +57812,258 @@ Please ensure:
57437
57812
  }
57438
57813
  var AuthenticatedTicketsView = withAuth(React24__namespace.default.memo(TicketsView));
57439
57814
  var TicketsView_default = TicketsView;
57440
- var MOCK_RECOMMENDATIONS = [
57441
- {
57442
- id: "rec-1",
57443
- type: "cycle_time",
57444
- title: "Cycle Time Deviation Detected",
57445
- location: "Unit 1: Station 7",
57446
- line: "Line 1",
57447
- description: "Cycle time observed was 90s but the standard set was 180s. This indicates a potential process deviation or standard mismatch.",
57448
- evidence: {
57449
- type: "video",
57450
- label: "Operator Cycle Recording",
57451
- videoUrls: [
57452
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
57453
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
57454
- // Mock duplicate for demo
57455
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
57456
- // Mock duplicate for demo
57457
- ]
57458
- },
57459
- impact: "Potential 50% efficiency gain or standard adjustment needed.",
57460
- date: "Today",
57461
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
57462
- },
57463
- {
57464
- id: "rec-2",
57465
- type: "efficiency",
57466
- title: "Consistent Low Efficiency Period",
57467
- location: "Line 2",
57468
- line: "Line 2",
57469
- description: "Efficiency from 7pm - 8pm is consistently lower than standard. This pattern has been observed for the last 3 days.",
57470
- evidence: {
57471
- type: "chart",
57472
- label: "7pm - 8pm Efficiency Trend",
57473
- chartData: [
57474
- { label: "7:00", value: 45, standard: 85 },
57475
- { label: "7:15", value: 42, standard: 85 },
57476
- { label: "7:30", value: 40, standard: 85 },
57477
- { label: "7:45", value: 48, standard: 85 },
57478
- { label: "8:00", value: 50, standard: 85 }
57479
- ]
57480
- },
57481
- impact: "Loss of approx. 40 units per hour during shift changeover.",
57482
- date: "Today",
57483
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
57484
- },
57485
- {
57486
- id: "rec-3",
57487
- type: "downtime",
57488
- title: "Recurring Micro-stoppages",
57489
- location: "Unit 3: Conveyor",
57490
- line: "Line 1",
57491
- description: "Frequent micro-stoppages detected every 15 minutes. This suggests a potential jam or sensor issue.",
57492
- evidence: {
57493
- type: "chart",
57494
- label: "Downtime Events",
57495
- chartData: [
57496
- { label: "9:00", value: 2, standard: 0 },
57497
- { label: "9:15", value: 3, standard: 0 },
57498
- { label: "9:30", value: 1, standard: 0 },
57499
- { label: "9:45", value: 4, standard: 0 },
57500
- { label: "10:00", value: 2, standard: 0 }
57501
- ]
57502
- },
57503
- impact: "Cumulative downtime of 45 mins per shift.",
57504
- date: "Yesterday",
57505
- timestamp: new Date(Date.now() - 864e5).toISOString()
57506
- },
57507
- {
57508
- id: "rec-4",
57509
- type: "efficiency",
57510
- title: "High Idle Time Detected",
57511
- location: "Packaging Station",
57512
- line: "Line 2",
57513
- description: "Operator idle time exceeds 20% of shift duration. Consider rebalancing workload.",
57514
- evidence: {
57515
- type: "video",
57516
- label: "Idle Time Observation",
57517
- videoUrls: [
57518
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
57519
- ]
57520
- },
57521
- impact: "Potential to reassign 1 FTE to other tasks.",
57522
- date: "Yesterday",
57523
- timestamp: new Date(Date.now() - 864e5).toISOString()
57524
- },
57525
- {
57526
- id: "rec-5",
57527
- type: "cycle_time",
57528
- title: "Slow Setup Time",
57529
- location: "Machine 5",
57530
- line: "Line 1",
57531
- description: "Setup time for product changeover took 45 mins, standard is 20 mins.",
57532
- evidence: {
57533
- type: "chart",
57534
- label: "Changeover Duration",
57535
- chartData: [
57536
- { label: "Prev 1", value: 25, standard: 20 },
57537
- { label: "Prev 2", value: 22, standard: 20 },
57538
- { label: "Current", value: 45, standard: 20 },
57539
- { label: "Avg", value: 30, standard: 20 },
57540
- { label: "Target", value: 20, standard: 20 }
57541
- ]
57542
- },
57543
- impact: "25 mins of lost production time.",
57544
- date: "2 Days Ago",
57545
- 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;
57546
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" }
57547
57991
  ];
57548
- var VideoCarousel = ({ urls }) => {
57992
+ var ClipVideoCarousel = ({ clips, clipsService }) => {
57549
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]);
57550
58040
  const nextVideo = () => {
57551
- setCurrentIndex((prev) => (prev + 1) % urls.length);
58041
+ setCurrentIndex((prev) => (prev + 1) % Math.max(videos.length, 1));
57552
58042
  };
57553
58043
  const prevVideo = () => {
57554
- setCurrentIndex((prev) => (prev - 1 + urls.length) % urls.length);
58044
+ setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
57555
58045
  };
57556
- if (!urls || urls.length === 0) return null;
58046
+ if (!clips || clips.length === 0) return null;
58047
+ const currentVideo = videos[currentIndex];
57557
58048
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group bg-gray-900 rounded-lg overflow-hidden aspect-video", children: [
57558
- /* @__PURE__ */ jsxRuntime.jsx(
57559
- "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,
57560
58053
  {
57561
- src: urls[currentIndex],
57562
- className: "w-full h-full object-cover opacity-80 group-hover:opacity-100 transition-opacity",
57563
- 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
57564
58058
  },
57565
- urls[currentIndex]
58059
+ currentVideo.src
57566
58060
  ),
57567
- /* @__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" }) }),
57568
- 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: [
57569
58067
  /* @__PURE__ */ jsxRuntime.jsx(
57570
58068
  "button",
57571
58069
  {
@@ -57593,70 +58091,273 @@ var VideoCarousel = ({ urls }) => {
57593
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: [
57594
58092
  currentIndex + 1,
57595
58093
  " / ",
57596
- urls.length
58094
+ videos.length
57597
58095
  ] })
57598
58096
  ] })
57599
58097
  ] });
57600
58098
  };
57601
- var EvidenceChart = ({ data }) => {
57602
- const chartData = data.map((point) => ({
57603
- name: point.label,
57604
- value: point.value,
57605
- standard: point.standard
57606
- }));
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);
57607
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(
57608
- BarChart,
58195
+ LineChart,
57609
58196
  {
57610
58197
  data: chartData,
57611
- bars: [
58198
+ lines: [
57612
58199
  {
57613
58200
  dataKey: "value",
57614
- name: "Actual",
57615
- fill: "#f87171",
57616
- // red-400
57617
- labelList: true
58201
+ name: "Value",
58202
+ stroke: "#f87171",
58203
+ strokeWidth: 2,
58204
+ dot: true
57618
58205
  }
57619
58206
  ],
57620
58207
  xAxisDataKey: "name",
57621
58208
  showLegend: false,
57622
58209
  showGrid: true,
57623
58210
  aspect: 2.5,
58211
+ yAxisUnit,
57624
58212
  className: "h-full"
57625
58213
  }
57626
58214
  ) });
57627
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
+ };
57628
58237
  var ImprovementCenterView = () => {
57629
58238
  const { navigate } = useNavigation();
57630
- const [selectedDate, setSelectedDate] = React24.useState("");
57631
- const [selectedLine, setSelectedLine] = React24.useState("");
57632
- const dateInputRef = React24.useRef(null);
57633
- const uniqueLines = React24.useMemo(() => {
57634
- const lines = new Set(MOCK_RECOMMENDATIONS.map((r2) => r2.line));
57635
- return Array.from(lines);
57636
- }, []);
57637
- const filteredRecommendations = React24.useMemo(() => {
57638
- return MOCK_RECOMMENDATIONS.filter((rec) => {
57639
- if (selectedDate) {
57640
- const recDate = new Date(rec.timestamp).toISOString().split("T")[0];
57641
- if (recDate !== selectedDate) return false;
57642
- }
57643
- if (selectedLine && rec.line !== selectedLine) {
57644
- 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);
57645
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;
57646
58333
  return true;
57647
- });
57648
- }, [selectedDate, selectedLine]);
57649
- const handleDateClick = () => {
57650
- dateInputRef.current?.showPicker?.();
57651
- };
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]);
57652
58345
  const clearFilters = () => {
57653
- setSelectedDate("");
57654
- setSelectedLine("");
57655
- };
57656
- 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]);
57657
58358
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
57658
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: [
57659
- /* @__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(
57660
58361
  BackButtonMinimal,
57661
58362
  {
57662
58363
  onClick: () => navigate("/"),
@@ -57664,121 +58365,190 @@ var ImprovementCenterView = () => {
57664
58365
  }
57665
58366
  ) }),
57666
58367
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
57667
- /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center flex items-center gap-2", children: [
57668
- /* @__PURE__ */ jsxRuntime.jsx(outline.LightBulbIcon, { className: "w-7 h-7 text-amber-500" }),
57669
- "Improvement Center"
57670
- ] }),
57671
- /* @__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" })
57672
58370
  ] }),
57673
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-20" })
58371
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-[120px]" })
57674
58372
  ] }) }) }),
57675
- /* @__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: [
57676
- /* @__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: [
57677
58375
  /* @__PURE__ */ jsxRuntime.jsx(
57678
- "input",
57679
- {
57680
- ref: dateInputRef,
57681
- type: "date",
57682
- value: selectedDate,
57683
- onChange: (e) => setSelectedDate(e.target.value),
57684
- className: "sr-only"
57685
- }
57686
- ),
57687
- /* @__PURE__ */ jsxRuntime.jsxs(
57688
58376
  "button",
57689
58377
  {
57690
- onClick: handleDateClick,
57691
- 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"}`,
57692
- children: [
57693
- /* @__PURE__ */ jsxRuntime.jsx(outline.CalendarIcon, { className: "w-4 h-4" }),
57694
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: selectedDate || "All Dates" })
57695
- ]
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" })
57696
58381
  }
57697
- )
57698
- ] }),
57699
- /* @__PURE__ */ jsxRuntime.jsxs(
57700
- "select",
57701
- {
57702
- value: selectedLine,
57703
- onChange: (e) => setSelectedLine(e.target.value),
57704
- 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"}`,
57705
- children: [
57706
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All Lines" }),
57707
- uniqueLines.map((line) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: line, children: line }, line))
57708
- ]
57709
- }
57710
- ),
57711
- hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsx(
57712
- "button",
57713
- {
57714
- onClick: clearFilters,
57715
- className: "p-1.5 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors",
57716
- title: "Clear filters",
57717
- children: /* @__PURE__ */ jsxRuntime.jsx(outline.XMarkIcon, { className: "w-5 h-5" })
57718
- }
57719
- )
57720
- ] }) }) }),
57721
- /* @__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: [
57722
- filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsxRuntime.jsx(
57723
- motion.div,
57724
- {
57725
- initial: { opacity: 0, y: 20 },
57726
- animate: { opacity: 1, y: 0 },
57727
- transition: { delay: index * 0.1 },
57728
- className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden",
57729
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
57730
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-4", children: [
57731
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
57732
- /* @__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() }),
57733
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 mt-2", children: rec.title }),
57734
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-500", children: [
57735
- rec.location,
57736
- " \u2022 ",
57737
- rec.line,
57738
- " \u2022 ",
57739
- rec.date
57740
- ] })
57741
- ] }) }),
57742
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: rec.description }) }),
57743
- /* @__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: [
57744
- /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
57745
- "Impact: ",
57746
- rec.impact
57747
- ] }) })
57748
- ] }),
57749
- /* @__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: [
57750
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
57751
- /* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
57752
- rec.evidence.type === "video" ? /* @__PURE__ */ jsxRuntime.jsx(outline.PlayCircleIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-4 h-4" }),
57753
- "Evidence: ",
57754
- rec.evidence.label
57755
- ] }),
57756
- 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: [
57757
- rec.evidence.videoUrls.length,
57758
- " clips"
57759
- ] })
57760
- ] }),
57761
- rec.evidence.type === "video" && rec.evidence.videoUrls && /* @__PURE__ */ jsxRuntime.jsx(VideoCarousel, { urls: rec.evidence.videoUrls }),
57762
- rec.evidence.type === "chart" && rec.evidence.chartData && /* @__PURE__ */ jsxRuntime.jsx(EvidenceChart, { data: rec.evidence.chartData })
57763
- ] })
57764
- ] }) })
57765
- },
57766
- rec.id
57767
- )) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-dashed border-gray-300", children: [
57768
- /* @__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" }) }),
57769
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-gray-900", children: "No recommendations found" }),
57770
- /* @__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) }),
57771
58384
  /* @__PURE__ */ jsxRuntime.jsx(
57772
58385
  "button",
57773
58386
  {
57774
- onClick: clearFilters,
57775
- className: "mt-4 text-sm text-blue-600 hover:text-blue-800 font-medium",
57776
- 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" })
57777
58390
  }
57778
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
+ ] })
57779
58454
  ] }),
57780
- 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." }) })
57781
- ] }) })
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
+ ] })
57782
58552
  ] });
57783
58553
  };
57784
58554
  var ImprovementCenterView_default = ImprovementCenterView;
@@ -58237,177 +59007,6 @@ var streamProxyConfig = {
58237
59007
  }
58238
59008
  };
58239
59009
 
58240
- // src/lib/api/s3-clips-parser.ts
58241
- function parseS3Uri(s3Uri, sopCategories) {
58242
- const path = new URL(s3Uri).pathname;
58243
- const parts = path.split("/").filter((p) => p);
58244
- if (s3Uri.includes("missed_qchecks")) {
58245
- console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
58246
- return null;
58247
- }
58248
- if (parts.length < 8) {
58249
- console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
58250
- return null;
58251
- }
58252
- try {
58253
- const datePart = parts[2];
58254
- const shiftPart = parts[3];
58255
- const violationType = parts[4];
58256
- let folderName = "";
58257
- let timestamp = "";
58258
- for (let i = 5; i < parts.length; i++) {
58259
- const part = parts[i];
58260
- if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
58261
- folderName = part;
58262
- const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
58263
- if (timeMatch) {
58264
- timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
58265
- break;
58266
- }
58267
- }
58268
- }
58269
- if (!timestamp) {
58270
- console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
58271
- timestamp = "00:00:00";
58272
- }
58273
- let severity = "low";
58274
- let type = "bottleneck";
58275
- let description = "Analysis Clip";
58276
- const normalizedViolationType = violationType.toLowerCase().trim();
58277
- if (sopCategories && sopCategories.length > 0) {
58278
- const matchedCategory = sopCategories.find((category) => {
58279
- const categoryId = category.id.toLowerCase();
58280
- const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
58281
- return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
58282
- normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
58283
- });
58284
- if (matchedCategory) {
58285
- type = matchedCategory.id;
58286
- description = matchedCategory.description || matchedCategory.label;
58287
- if (matchedCategory.color.includes("red")) {
58288
- severity = "high";
58289
- } else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
58290
- severity = "medium";
58291
- } else {
58292
- severity = "low";
58293
- }
58294
- console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
58295
- return { timestamp, severity, description, type, originalUri: s3Uri };
58296
- }
58297
- }
58298
- switch (normalizedViolationType) {
58299
- case "idle_time":
58300
- case "idle":
58301
- case "low_value":
58302
- case "low value":
58303
- case "low_value_moment":
58304
- case "low_value_moments":
58305
- case "low value moment":
58306
- case "low value moments":
58307
- type = "low_value";
58308
- severity = "low";
58309
- description = "Idle Time Detected";
58310
- break;
58311
- case "sop_deviation":
58312
- type = "missing_quality_check";
58313
- severity = "high";
58314
- description = "SOP Deviations";
58315
- break;
58316
- case "long_cycle_time":
58317
- severity = "high";
58318
- type = "long_cycle_time";
58319
- description = "Long Cycle Time Detected";
58320
- break;
58321
- case "best_cycle_time":
58322
- type = "best_cycle_time";
58323
- severity = "low";
58324
- description = "Best Cycle Time Performance";
58325
- break;
58326
- case "worst_cycle_time":
58327
- type = "worst_cycle_time";
58328
- severity = "high";
58329
- description = "Worst Cycle Time Performance";
58330
- break;
58331
- case "cycle_completion":
58332
- case "completed_cycles":
58333
- case "completed_cycle":
58334
- type = "cycle_completion";
58335
- severity = "low";
58336
- description = "Cycle Completion";
58337
- break;
58338
- case "running_cycle":
58339
- case "active_cycle":
58340
- case "production_cycle":
58341
- type = "running_cycle";
58342
- severity = "low";
58343
- description = "Active Production Cycle";
58344
- break;
58345
- case "setup_state":
58346
- case "machine_setup":
58347
- case "line_setup":
58348
- type = "setup_state";
58349
- severity = "medium";
58350
- description = "Machine Setup Activity";
58351
- break;
58352
- case "medium_bottleneck":
58353
- severity = "medium";
58354
- description = "Medium Bottleneck Identified";
58355
- break;
58356
- case "minor_bottleneck":
58357
- case "mild_bottleneck":
58358
- severity = "low";
58359
- description = "Minor Bottleneck Identified";
58360
- break;
58361
- default:
58362
- if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
58363
- type = "missing_quality_check";
58364
- severity = "high";
58365
- description = "SOP Deviations";
58366
- } else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
58367
- type = "worst_cycle_time";
58368
- severity = "high";
58369
- description = "Worst Cycle Time Performance";
58370
- } else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
58371
- type = "best_cycle_time";
58372
- severity = "low";
58373
- description = "Best Cycle Time Performance";
58374
- } else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
58375
- type = "long_cycle_time";
58376
- severity = "high";
58377
- description = "Long Cycle Time Detected";
58378
- } else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
58379
- type = "cycle_completion";
58380
- severity = "low";
58381
- description = "Cycle Completion";
58382
- } else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
58383
- type = "running_cycle";
58384
- severity = "low";
58385
- description = "Active Production Cycle";
58386
- } else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
58387
- type = "setup_state";
58388
- severity = "medium";
58389
- description = "Machine Setup Activity";
58390
- } else {
58391
- description = `Clip type: ${violationType.replace(/_/g, " ")}`;
58392
- console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
58393
- }
58394
- break;
58395
- }
58396
- return { timestamp, severity, description, type, originalUri: s3Uri };
58397
- } catch (error) {
58398
- console.error(`Error parsing S3 URI: ${s3Uri}`, error);
58399
- return null;
58400
- }
58401
- }
58402
- function shuffleArray(array) {
58403
- const shuffled = [...array];
58404
- for (let i = shuffled.length - 1; i > 0; i--) {
58405
- const j = Math.floor(Math.random() * (i + 1));
58406
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
58407
- }
58408
- return shuffled;
58409
- }
58410
-
58411
59010
  exports.ACTION_NAMES = ACTION_NAMES;
58412
59011
  exports.AIAgentView = AIAgentView_default;
58413
59012
  exports.AcceptInvite = AcceptInvite;