@optifye/dashboard-core 6.10.6 → 6.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -13,11 +13,11 @@ import { noop, warning, invariant, progress, secondsToMilliseconds, milliseconds
13
13
  import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
14
14
  import { Camera, ChevronDown, ChevronUp, Check, Map as Map$1, Video, ShieldCheck, Star, Award, ArrowLeft, X, Coffee, Plus, Clock, Calendar, Save, AlertCircle, Loader2, Minus, ArrowDown, ArrowUp, ChevronLeft, ChevronRight, Pause, Play, Wrench, XCircle, Package, UserX, Zap, HelpCircle, AlertTriangle, Tag, Sparkles, TrendingUp, Settings2, CheckCircle2, RefreshCw, TrendingDown, FolderOpen, Folder, Sliders, Activity, Layers, Filter, Search, Edit2, CheckCircle, User, Users, Shield, Building2, Mail, Lock, ArrowRight, Info, Share2, Trophy, Target, Download, Sun, Moon, MousePointer, UserPlus, UserCog, Trash2, Eye, MoreVertical, BarChart3, MessageSquare, Menu, Send, Copy, UserCheck, LogOut, Settings, LifeBuoy, EyeOff, UserCircle } from 'lucide-react';
15
15
  import { toast } from 'sonner';
16
- import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ReferenceLine, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
16
+ import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, ReferenceLine, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
17
17
  import { Slot } from '@radix-ui/react-slot';
18
18
  import * as SelectPrimitive from '@radix-ui/react-select';
19
19
  import { DayPicker, useNavigation as useNavigation$1 } from 'react-day-picker';
20
- import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, UsersIcon, TicketIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, HeartIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, ArrowLeftIcon, LightBulbIcon, CalendarIcon, PlayCircleIcon, FunnelIcon, XCircleIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
20
+ import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, LightBulbIcon, AdjustmentsHorizontalIcon, ClockIcon, UsersIcon, TicketIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, HeartIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, ArrowLeftIcon, XCircleIcon, PlayCircleIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
21
21
  import { CheckIcon } from '@heroicons/react/24/solid';
22
22
  import html2canvas from 'html2canvas';
23
23
  import jsPDF, { jsPDF as jsPDF$1 } from 'jspdf';
@@ -3728,18 +3728,17 @@ var initializeCoreMixpanel = (token, debugOrOptions, trackPageViewArg) => {
3728
3728
  track_pageview: trackPageView ?? true,
3729
3729
  persistence: "localStorage"
3730
3730
  };
3731
- if (sessionOpts.recordSessionsPercent !== void 0) {
3732
- initOptions.record_sessions_percent = sessionOpts.recordSessionsPercent;
3733
- } else {
3734
- initOptions.record_sessions_percent = 1;
3735
- }
3736
- if (sessionOpts.recordIdleTimeoutMs !== void 0) {
3737
- initOptions.record_idle_timeout_ms = sessionOpts.recordIdleTimeoutMs;
3731
+ const recordSessionsPercent = sessionOpts.recordSessionsPercent ?? 0;
3732
+ initOptions.record_sessions_percent = recordSessionsPercent;
3733
+ const defaultIdleTimeoutMs = 10 * 60 * 1e3;
3734
+ const recordIdleTimeoutMs = sessionOpts.recordIdleTimeoutMs ?? (recordSessionsPercent > 0 ? defaultIdleTimeoutMs : void 0);
3735
+ if (recordIdleTimeoutMs !== void 0) {
3736
+ initOptions.record_idle_timeout_ms = recordIdleTimeoutMs;
3738
3737
  }
3739
3738
  if (sessionOpts.recordHeatmapData !== void 0) {
3740
3739
  initOptions.record_heatmap_data = sessionOpts.recordHeatmapData;
3741
3740
  } else {
3742
- initOptions.record_heatmap_data = true;
3741
+ initOptions.record_heatmap_data = false;
3743
3742
  }
3744
3743
  if (sessionOpts.recordCanvas !== void 0) {
3745
3744
  initOptions.record_canvas = sessionOpts.recordCanvas;
@@ -3757,7 +3756,9 @@ var initializeCoreMixpanel = (token, debugOrOptions, trackPageViewArg) => {
3757
3756
  });
3758
3757
  mixpanel.init(token, initOptions);
3759
3758
  isMixpanelInitialized = true;
3760
- console.log("Mixpanel initialized in dashboard-core with Session Replay support.");
3759
+ if (initOptions.debug) {
3760
+ console.log("Mixpanel initialized in dashboard-core.");
3761
+ }
3761
3762
  };
3762
3763
  var trackCorePageView = (pageName, properties) => {
3763
3764
  if (!isMixpanelInitialized) return;
@@ -10301,6 +10302,41 @@ var useRealtimeLineMetrics = ({
10301
10302
  refreshMetrics: fetchData
10302
10303
  }), [metrics2, lineDetails, loading, error, fetchData]);
10303
10304
  };
10305
+ var useLines = () => {
10306
+ const supabase = useSupabase();
10307
+ const entityConfig = useEntityConfig();
10308
+ const [lines, setLines] = useState([]);
10309
+ const [loading, setLoading] = useState(true);
10310
+ const [error, setError] = useState(null);
10311
+ const fetchLines = useCallback(async () => {
10312
+ if (!supabase) {
10313
+ setLoading(false);
10314
+ return;
10315
+ }
10316
+ try {
10317
+ setLoading(true);
10318
+ let query = supabase.from("lines").select("*").eq("enable", true);
10319
+ if (entityConfig.companyId) {
10320
+ query = query.eq("company_id", entityConfig.companyId);
10321
+ }
10322
+ const { data, error: error2 } = await query;
10323
+ if (error2) throw error2;
10324
+ const sortedLines = (data || []).sort(
10325
+ (a, b) => (a.line_name || "").localeCompare(b.line_name || "")
10326
+ );
10327
+ setLines(sortedLines);
10328
+ } catch (err) {
10329
+ console.error("Error fetching lines:", err);
10330
+ setError(err);
10331
+ } finally {
10332
+ setLoading(false);
10333
+ }
10334
+ }, [supabase, entityConfig.companyId]);
10335
+ useEffect(() => {
10336
+ fetchLines();
10337
+ }, [fetchLines]);
10338
+ return { lines, loading, error, refetch: fetchLines };
10339
+ };
10304
10340
  var useTargets = (options) => {
10305
10341
  const { companyId } = useEntityConfig();
10306
10342
  const supabase = useSupabase();
@@ -12434,41 +12470,6 @@ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput, options
12434
12470
  counts
12435
12471
  };
12436
12472
  }
12437
- var useLines = () => {
12438
- const supabase = useSupabase();
12439
- const entityConfig = useEntityConfig();
12440
- const [lines, setLines] = useState([]);
12441
- const [loading, setLoading] = useState(true);
12442
- const [error, setError] = useState(null);
12443
- const fetchLines = useCallback(async () => {
12444
- if (!supabase) {
12445
- setLoading(false);
12446
- return;
12447
- }
12448
- try {
12449
- setLoading(true);
12450
- let query = supabase.from("lines").select("*").eq("enable", true);
12451
- if (entityConfig.companyId) {
12452
- query = query.eq("company_id", entityConfig.companyId);
12453
- }
12454
- const { data, error: error2 } = await query;
12455
- if (error2) throw error2;
12456
- const sortedLines = (data || []).sort(
12457
- (a, b) => (a.line_name || "").localeCompare(b.line_name || "")
12458
- );
12459
- setLines(sortedLines);
12460
- } catch (err) {
12461
- console.error("Error fetching lines:", err);
12462
- setError(err);
12463
- } finally {
12464
- setLoading(false);
12465
- }
12466
- }, [supabase, entityConfig.companyId]);
12467
- useEffect(() => {
12468
- fetchLines();
12469
- }, [fetchLines]);
12470
- return { lines, loading, error, refetch: fetchLines };
12471
- };
12472
12473
  var MAX_RETRIES = 10;
12473
12474
  var RETRY_DELAY = 500;
12474
12475
  function useNavigation(customNavigate) {
@@ -25323,6 +25324,7 @@ var DEFAULT_BAR_RADIUS = [4, 4, 0, 0];
25323
25324
  var BarChartComponent = ({
25324
25325
  data,
25325
25326
  bars,
25327
+ referenceLines,
25326
25328
  xAxisDataKey = "name",
25327
25329
  xAxisLabel,
25328
25330
  yAxisLabel,
@@ -25406,6 +25408,22 @@ var BarChartComponent = ({
25406
25408
  stroke: axisStrokeColor
25407
25409
  }
25408
25410
  ),
25411
+ referenceLines?.map((line, idx) => /* @__PURE__ */ jsx(
25412
+ ReferenceLine,
25413
+ {
25414
+ y: line.y,
25415
+ stroke: line.stroke || axisStrokeColor,
25416
+ strokeDasharray: line.strokeDasharray || "4 4",
25417
+ strokeWidth: line.strokeWidth,
25418
+ label: line.label ? {
25419
+ value: line.label,
25420
+ position: "insideTopRight",
25421
+ fill: line.stroke || axisTickFillColor,
25422
+ fontSize: 12
25423
+ } : void 0
25424
+ },
25425
+ `ref-line-${idx}`
25426
+ )),
25409
25427
  showTooltip && /* @__PURE__ */ jsx(Tooltip, { formatter: tooltipFormatter || defaultTooltipFormatter, cursor: { fill: "transparent" } }),
25410
25428
  showLegend && /* @__PURE__ */ jsx(Legend, { payload: legendPayload }),
25411
25429
  bars.map((barConfig, index) => /* @__PURE__ */ jsx(
@@ -25441,7 +25459,7 @@ var BarChartComponent = ({
25441
25459
  return /* @__PURE__ */ jsx("div", { className: clsx("w-full", className), children: chartContent });
25442
25460
  };
25443
25461
  var BarChart = React24__default.memo(BarChartComponent, (prevProps, nextProps) => {
25444
- 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) {
25462
+ 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) {
25445
25463
  return false;
25446
25464
  }
25447
25465
  if (prevProps.data.length !== nextProps.data.length) {
@@ -29022,6 +29040,38 @@ var ERROR_MAPPING = {
29022
29040
  canRetry: false
29023
29041
  }
29024
29042
  };
29043
+ var CLIP_ID_COMMENT_REGEX = /#\s*Clip ID:\s*([a-f0-9-]+)/i;
29044
+ var PLAYLIST_PROXY_REGEX = /\/api\/clips\/playlist\/([a-f0-9-]+)/i;
29045
+ var R2_FALLBACK_DETAILS = /* @__PURE__ */ new Set([
29046
+ "fragLoadError",
29047
+ "fragLoadHTTPError",
29048
+ "manifestLoadError",
29049
+ "levelLoadError",
29050
+ "keyLoadError"
29051
+ ]);
29052
+ var extractClipIdFromSource = (source) => {
29053
+ if (!source) return null;
29054
+ const commentMatch = source.match(CLIP_ID_COMMENT_REGEX);
29055
+ if (commentMatch) return commentMatch[1];
29056
+ const urlMatch = source.match(PLAYLIST_PROXY_REGEX);
29057
+ if (urlMatch) return urlMatch[1];
29058
+ return null;
29059
+ };
29060
+ var ensureClipIdComment = (playlist, clipId) => {
29061
+ if (!playlist || playlist.includes(`# Clip ID: ${clipId}`) || CLIP_ID_COMMENT_REGEX.test(playlist)) {
29062
+ return playlist;
29063
+ }
29064
+ const lines = playlist.split("\n");
29065
+ if (lines.length === 0) return playlist;
29066
+ return [lines[0], `# Clip ID: ${clipId}`, ...lines.slice(1)].join("\n");
29067
+ };
29068
+ var getHlsErrorUrl = (data) => {
29069
+ return data?.frag?.url || data?.response?.url || data?.context?.url || data?.networkDetails?.response?.url || data?.url;
29070
+ };
29071
+ var getHlsErrorStatus = (data) => {
29072
+ const status = data?.response?.code ?? data?.response?.status ?? data?.networkDetails?.status ?? data?.networkDetails?.response?.status;
29073
+ return typeof status === "number" ? status : void 0;
29074
+ };
29025
29075
  var hlsVideoPlayerStyles = `
29026
29076
  .hls-video-player-container {
29027
29077
  width: 100%;
@@ -29123,8 +29173,12 @@ var HlsVideoPlayer = forwardRef(({
29123
29173
  const videoRef = useRef(null);
29124
29174
  const hlsRef = useRef(null);
29125
29175
  const blobUrlRef = useRef(null);
29176
+ const clipIdRef = useRef(null);
29177
+ const r2FallbackAttemptedRef = useRef(false);
29126
29178
  const [isReady, setIsReady] = useState(false);
29127
29179
  const [isLoading, setIsLoading] = useState(true);
29180
+ const [overrideSource, setOverrideSource] = useState(null);
29181
+ const effectiveSrc = overrideSource && overrideSource.baseSrc === src ? overrideSource.value : src;
29128
29182
  const [showControls, setShowControls] = useState(true);
29129
29183
  const [controlsPinned, setControlsPinned] = useState(false);
29130
29184
  const [isPlaying, setIsPlaying] = useState(false);
@@ -29184,10 +29238,16 @@ var HlsVideoPlayer = forwardRef(({
29184
29238
  onSeeked,
29185
29239
  onLoadingChange
29186
29240
  ]);
29241
+ useEffect(() => {
29242
+ clipIdRef.current = extractClipIdFromSource(src);
29243
+ r2FallbackAttemptedRef.current = false;
29244
+ setOverrideSource(null);
29245
+ }, [src]);
29187
29246
  const stableHlsConfigRef = useRef(hlsConfig);
29188
29247
  const stableOptionsRef = useRef(options);
29189
29248
  const configSignatureRef = useRef("");
29190
29249
  const [configVersion, setConfigVersion] = useState(0);
29250
+ const r2WorkerDomain = process.env.NEXT_PUBLIC_R2_WORKER_DOMAIN || "https://r2-stream-proxy.optifye-r2.workers.dev";
29191
29251
  useEffect(() => {
29192
29252
  const serialized = JSON.stringify({
29193
29253
  hlsConfig: hlsConfig || null,
@@ -29206,6 +29266,50 @@ var HlsVideoPlayer = forwardRef(({
29206
29266
  setConfigVersion((prev) => prev + 1);
29207
29267
  }
29208
29268
  }, [hlsConfig, options]);
29269
+ const attemptS3Fallback = useCallback(async (mode, reason, errorUrl) => {
29270
+ if (r2FallbackAttemptedRef.current) return false;
29271
+ const clipId = clipIdRef.current || extractClipIdFromSource(effectiveSrc);
29272
+ if (!clipId) {
29273
+ console.warn(`[HlsVideoPlayer] R2 fallback skipped - no clip ID (${reason})`);
29274
+ return false;
29275
+ }
29276
+ r2FallbackAttemptedRef.current = true;
29277
+ if (mode === "url") {
29278
+ const fallbackUrl = `/api/clips/playlist/${clipId}?source=s3`;
29279
+ console.warn(`[HlsVideoPlayer] Switching to S3 playlist URL (${reason})`, { errorUrl, clipId });
29280
+ setOverrideSource({ baseSrc: src, value: fallbackUrl });
29281
+ return true;
29282
+ }
29283
+ try {
29284
+ console.warn(`[HlsVideoPlayer] Fetching S3 playlist (${reason})`, { errorUrl, clipId });
29285
+ const response = await fetch(`/api/clips/playlist/${clipId}?source=s3`);
29286
+ if (!response.ok) {
29287
+ console.warn("[HlsVideoPlayer] S3 playlist fetch failed", { status: response.status, clipId });
29288
+ return false;
29289
+ }
29290
+ let playlistText = await response.text();
29291
+ playlistText = ensureClipIdComment(playlistText, clipId);
29292
+ setOverrideSource({ baseSrc: src, value: playlistText });
29293
+ return true;
29294
+ } catch (error) {
29295
+ console.error("[HlsVideoPlayer] S3 fallback failed", error);
29296
+ return false;
29297
+ }
29298
+ }, [effectiveSrc, src]);
29299
+ const maybeHandleR2Fallback = useCallback((data) => {
29300
+ const errorUrl = getHlsErrorUrl(data);
29301
+ if (!errorUrl || !isR2WorkerUrl(errorUrl, r2WorkerDomain)) {
29302
+ return false;
29303
+ }
29304
+ const status = getHlsErrorStatus(data);
29305
+ const details = data?.details;
29306
+ const shouldFallback = R2_FALLBACK_DETAILS.has(details) || typeof status === "number" && status >= 400 && status < 500;
29307
+ if (!shouldFallback) {
29308
+ return false;
29309
+ }
29310
+ attemptS3Fallback("playlist", `HLS.js ${details || data?.type || "error"}`, errorUrl);
29311
+ return true;
29312
+ }, [attemptS3Fallback, r2WorkerDomain]);
29209
29313
  const cleanupBlobUrl = useCallback(() => {
29210
29314
  if (blobUrlRef.current) {
29211
29315
  URL.revokeObjectURL(blobUrlRef.current);
@@ -29247,70 +29351,55 @@ var HlsVideoPlayer = forwardRef(({
29247
29351
  };
29248
29352
  }, [dispose]);
29249
29353
  const initializePlayer = useCallback(async () => {
29250
- if (!videoRef.current || !src) return;
29354
+ if (!videoRef.current || !effectiveSrc) return;
29251
29355
  const video = videoRef.current;
29252
29356
  const player = playerLikeObject();
29253
29357
  let authToken = null;
29254
29358
  try {
29255
29359
  authToken = await getAuthTokenForHls(supabase);
29256
- if (authToken) {
29257
- console.log("[HLS Auth] Retrieved token for R2 streaming");
29258
- } else {
29360
+ if (!authToken) {
29259
29361
  console.warn("[HLS Auth] No active session - R2 streaming may fail");
29260
29362
  }
29261
29363
  } catch (error) {
29262
- console.error("[HLS Auth] Error retrieving token:", error);
29364
+ console.error("[HLS Auth] Error getting auth token:", error);
29263
29365
  }
29264
- const r2WorkerDomain = "https://r2-stream-proxy.optifye-r2.workers.dev";
29265
29366
  const mergedHlsConfig = {
29266
29367
  ...BASE_HLS_CONFIG,
29267
29368
  ...stableHlsConfigRef.current || {},
29268
29369
  ...stableOptionsRef.current || {},
29269
- // Modern HLS.js uses Fetch API - override fetch to add Authorization header
29370
+ // Modern HLS.js uses Fetch API - override fetch to add Authorization header for R2 Worker requests
29270
29371
  fetchSetup: function(context, initParams) {
29271
29372
  const url = context.url;
29272
- console.log("[HLS fetchSetup] Request URL:", url);
29273
- console.log("[HLS fetchSetup] R2 Worker domain:", r2WorkerDomain);
29274
- console.log("[HLS fetchSetup] Token available:", !!authToken);
29275
- const isR2 = isR2WorkerUrl(url, r2WorkerDomain);
29276
- console.log("[HLS fetchSetup] Is R2 Worker URL:", isR2);
29277
- if (isR2) {
29278
- if (authToken) {
29279
- initParams.headers = {
29280
- ...initParams.headers,
29281
- "Authorization": `Bearer ${authToken}`
29282
- };
29283
- console.log("[HLS Auth] \u2705 Injected JWT for R2 request:", url);
29284
- } else {
29285
- console.warn("[HLS Auth] \u26A0\uFE0F No token available for R2 request:", url);
29286
- }
29287
- } else {
29288
- console.log("[HLS Auth] CloudFront URL - no auth needed:", url);
29373
+ if (isR2WorkerUrl(url, r2WorkerDomain) && authToken) {
29374
+ initParams.headers = {
29375
+ ...initParams.headers,
29376
+ "Authorization": `Bearer ${authToken}`
29377
+ };
29289
29378
  }
29290
29379
  return new Request(url, initParams);
29291
29380
  }
29292
29381
  };
29293
29382
  cleanupBlobUrl();
29294
- const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
29383
+ const isHLS = effectiveSrc.endsWith(".m3u8") || effectiveSrc.startsWith("#EXTM3U");
29295
29384
  if (isHLS) {
29296
- if (src.startsWith("#EXTM3U")) {
29385
+ if (effectiveSrc.startsWith("#EXTM3U")) {
29297
29386
  const safariMode = isSafari();
29298
29387
  const browserName = getBrowserName();
29299
29388
  if (safariMode) {
29300
- const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
29389
+ const clipIdMatch = effectiveSrc.match(/# Clip ID: ([a-f0-9-]+)/i);
29301
29390
  if (clipIdMatch) {
29302
29391
  const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
29303
29392
  console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
29304
29393
  video.src = proxyUrl;
29305
29394
  } else {
29306
29395
  console.warn("[HlsVideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
29307
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
29396
+ const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
29308
29397
  const blobUrl = URL.createObjectURL(blob);
29309
29398
  blobUrlRef.current = blobUrl;
29310
29399
  video.src = blobUrl;
29311
29400
  }
29312
29401
  } else if (Hls3.isSupported()) {
29313
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
29402
+ const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
29314
29403
  const blobUrl = URL.createObjectURL(blob);
29315
29404
  blobUrlRef.current = blobUrl;
29316
29405
  console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
@@ -29323,24 +29412,16 @@ var HlsVideoPlayer = forwardRef(({
29323
29412
  });
29324
29413
  hls.on(Events.ERROR, (event, data) => {
29325
29414
  console.error("[HlsVideoPlayer] HLS.js error:", data);
29415
+ if (maybeHandleR2Fallback(data)) {
29416
+ return;
29417
+ }
29326
29418
  if (data.fatal) {
29327
29419
  let errorInfo;
29328
29420
  switch (data.type) {
29329
29421
  case ErrorTypes.NETWORK_ERROR:
29330
- if (data.response?.code === 401) {
29331
- errorInfo = {
29332
- code: 401,
29333
- type: "recoverable",
29334
- message: "Authentication expired. Please refresh the page.",
29335
- canRetry: true,
29336
- details: "JWT_EXPIRED"
29337
- };
29338
- console.error("[HLS Auth] 401 Unauthorized - token may be expired");
29339
- } else {
29340
- errorInfo = ERROR_MAPPING.networkError;
29341
- console.log("[HlsVideoPlayer] Attempting to recover from network error");
29342
- hls.startLoad();
29343
- }
29422
+ errorInfo = ERROR_MAPPING.networkError;
29423
+ console.log("[HlsVideoPlayer] Attempting to recover from network error");
29424
+ hls.startLoad();
29344
29425
  break;
29345
29426
  case ErrorTypes.MEDIA_ERROR:
29346
29427
  errorInfo = ERROR_MAPPING.mediaError;
@@ -29381,23 +29462,15 @@ var HlsVideoPlayer = forwardRef(({
29381
29462
  });
29382
29463
  hls.on(Events.ERROR, (event, data) => {
29383
29464
  console.error("[HlsVideoPlayer] HLS.js error:", data);
29465
+ if (maybeHandleR2Fallback(data)) {
29466
+ return;
29467
+ }
29384
29468
  if (data.fatal) {
29385
29469
  let errorInfo;
29386
29470
  switch (data.type) {
29387
29471
  case ErrorTypes.NETWORK_ERROR:
29388
- if (data.response?.code === 401) {
29389
- errorInfo = {
29390
- code: 401,
29391
- type: "recoverable",
29392
- message: "Authentication expired. Please refresh the page.",
29393
- canRetry: true,
29394
- details: "JWT_EXPIRED"
29395
- };
29396
- console.error("[HLS Auth] 401 Unauthorized - token may be expired");
29397
- } else {
29398
- errorInfo = ERROR_MAPPING.networkError;
29399
- hls.startLoad();
29400
- }
29472
+ errorInfo = ERROR_MAPPING.networkError;
29473
+ hls.startLoad();
29401
29474
  break;
29402
29475
  case ErrorTypes.MEDIA_ERROR:
29403
29476
  errorInfo = ERROR_MAPPING.mediaError;
@@ -29411,14 +29484,14 @@ var HlsVideoPlayer = forwardRef(({
29411
29484
  eventCallbacksRef.current.onError?.(player, errorInfo);
29412
29485
  }
29413
29486
  });
29414
- hls.loadSource(src);
29487
+ hls.loadSource(effectiveSrc);
29415
29488
  hls.attachMedia(video);
29416
29489
  } else {
29417
- video.src = src;
29490
+ video.src = effectiveSrc;
29418
29491
  }
29419
29492
  }
29420
29493
  } else {
29421
- video.src = src;
29494
+ video.src = effectiveSrc;
29422
29495
  }
29423
29496
  const handleCanPlay = () => {
29424
29497
  if (!hlsRef.current) {
@@ -29494,6 +29567,11 @@ var HlsVideoPlayer = forwardRef(({
29494
29567
  eventCallbacksRef.current.onSeeked?.(player);
29495
29568
  };
29496
29569
  const handleError = () => {
29570
+ const currentSrc = video.currentSrc || video.src;
29571
+ if (isSafari() && currentSrc.includes("/api/clips/playlist/") && !currentSrc.includes("source=s3") && !r2FallbackAttemptedRef.current) {
29572
+ attemptS3Fallback("url", "native playback error", currentSrc);
29573
+ return;
29574
+ }
29497
29575
  const error = video.error;
29498
29576
  if (error) {
29499
29577
  const errorInfo = {
@@ -29542,10 +29620,14 @@ var HlsVideoPlayer = forwardRef(({
29542
29620
  video.removeEventListener("ratechange", handlePlaybackRateChange2);
29543
29621
  };
29544
29622
  }, [
29545
- src,
29623
+ effectiveSrc,
29546
29624
  cleanupBlobUrl,
29547
29625
  playerLikeObject,
29548
- configVersion
29626
+ configVersion,
29627
+ supabase,
29628
+ attemptS3Fallback,
29629
+ maybeHandleR2Fallback,
29630
+ r2WorkerDomain
29549
29631
  ]);
29550
29632
  useEffect(() => {
29551
29633
  let cleanup;
@@ -29561,7 +29643,7 @@ var HlsVideoPlayer = forwardRef(({
29561
29643
  cleanupBlobUrl();
29562
29644
  setIsReady(false);
29563
29645
  };
29564
- }, [src, initializePlayer, cleanupBlobUrl]);
29646
+ }, [effectiveSrc, initializePlayer, cleanupBlobUrl]);
29565
29647
  useEffect(() => {
29566
29648
  if (videoRef.current) {
29567
29649
  if (autoplay) {
@@ -36391,7 +36473,16 @@ var STATIC_COLORS = {
36391
36473
  "Operator Idle": "#8b5cf6"
36392
36474
  // violet-500 - Low Priority/Behavioral
36393
36475
  };
36476
+ var PRODUCTIVE_COLOR = "#00AB45";
36477
+ var IDLE_COLOR = "#e5e7eb";
36394
36478
  var getColorForEntry = (name, index) => {
36479
+ const normalized = name.trim().toLowerCase();
36480
+ if (normalized === "productive" || normalized === "productive time") {
36481
+ return PRODUCTIVE_COLOR;
36482
+ }
36483
+ if (normalized === "idle" || normalized === "idle time") {
36484
+ return IDLE_COLOR;
36485
+ }
36395
36486
  if (STATIC_COLORS[name]) {
36396
36487
  return STATIC_COLORS[name];
36397
36488
  }
@@ -36535,6 +36626,7 @@ var IdleTimeReasonChart = ({
36535
36626
  )
36536
36627
  ] });
36537
36628
  };
36629
+ var IdleTimeReasonChart_default = IdleTimeReasonChart;
36538
36630
  var DEFAULT_PERFORMANCE_DATA = {
36539
36631
  avg_efficiency: 0,
36540
36632
  underperforming_workspaces: 0,
@@ -37344,7 +37436,7 @@ var LinePdfGenerator = ({
37344
37436
  };
37345
37437
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
37346
37438
  const shiftDuration = hourlyTimeRanges.length || 11;
37347
- const targetOutputPerHour = Math.round(lineInfo.metrics.line_threshold / shiftDuration);
37439
+ const targetOutputPerHour = Math.round(lineInfo.metrics.threshold_pph ?? 0);
37348
37440
  let hourlyActualOutput = [];
37349
37441
  if (lineInfo.metrics.output_hourly && Object.keys(lineInfo.metrics.output_hourly).length > 0) {
37350
37442
  const [startHourStr, startMinuteStr] = (lineInfo.metrics.shift_start || "6:00").split(":");
@@ -41452,6 +41544,17 @@ var SideNavBar = memo(({
41452
41544
  });
41453
41545
  onMobileMenuClose?.();
41454
41546
  }, [navigate, onMobileMenuClose]);
41547
+ const handleImprovementClick = useCallback(() => {
41548
+ navigate("/improvement-center", {
41549
+ trackingEvent: {
41550
+ name: "Improvement Center Clicked",
41551
+ properties: {
41552
+ source: "side_nav"
41553
+ }
41554
+ }
41555
+ });
41556
+ onMobileMenuClose?.();
41557
+ }, [navigate, onMobileMenuClose]);
41455
41558
  const handleTargetsClick = useCallback(() => {
41456
41559
  navigate("/targets", {
41457
41560
  trackingEvent: {
@@ -41569,6 +41672,7 @@ var SideNavBar = memo(({
41569
41672
  const homeButtonClasses = useMemo(() => getButtonClasses("/"), [getButtonClasses, pathname]);
41570
41673
  const leaderboardButtonClasses = useMemo(() => getButtonClasses("/leaderboard"), [getButtonClasses, pathname]);
41571
41674
  const kpisButtonClasses = useMemo(() => getButtonClasses("/kpis"), [getButtonClasses, pathname]);
41675
+ const improvementButtonClasses = useMemo(() => getButtonClasses("/improvement-center"), [getButtonClasses, pathname]);
41572
41676
  const targetsButtonClasses = useMemo(() => getButtonClasses("/targets"), [getButtonClasses, pathname]);
41573
41677
  const shiftsButtonClasses = useMemo(() => getButtonClasses("/shifts"), [getButtonClasses, pathname]);
41574
41678
  const teamManagementButtonClasses = useMemo(() => getButtonClasses("/team-management"), [getButtonClasses, pathname]);
@@ -41636,6 +41740,22 @@ var SideNavBar = memo(({
41636
41740
  ]
41637
41741
  }
41638
41742
  ),
41743
+ /* @__PURE__ */ jsxs(
41744
+ "button",
41745
+ {
41746
+ onClick: handleImprovementClick,
41747
+ className: improvementButtonClasses,
41748
+ "aria-label": "Improvement Center",
41749
+ tabIndex: 0,
41750
+ role: "tab",
41751
+ "aria-selected": pathname === "/improvement-center" || pathname.startsWith("/improvement-center/"),
41752
+ children: [
41753
+ /* @__PURE__ */ jsx(LightBulbIcon, { className: "w-5 h-5 mb-1" }),
41754
+ /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight text-center", children: "Improvement" }),
41755
+ /* @__PURE__ */ 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" })
41756
+ ]
41757
+ }
41758
+ ),
41639
41759
  /* @__PURE__ */ jsxs(
41640
41760
  "button",
41641
41761
  {
@@ -41836,6 +41956,19 @@ var SideNavBar = memo(({
41836
41956
  ]
41837
41957
  }
41838
41958
  ),
41959
+ /* @__PURE__ */ jsxs(
41960
+ "button",
41961
+ {
41962
+ onClick: handleMobileNavClick(handleImprovementClick),
41963
+ className: getMobileButtonClass("/improvement-center"),
41964
+ "aria-label": "Improvement Center",
41965
+ children: [
41966
+ /* @__PURE__ */ jsx(LightBulbIcon, { className: getIconClass("/improvement-center") }),
41967
+ /* @__PURE__ */ jsx("span", { className: "text-base font-medium", children: "Improvement" }),
41968
+ /* @__PURE__ */ 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" })
41969
+ ]
41970
+ }
41971
+ ),
41839
41972
  /* @__PURE__ */ jsxs(
41840
41973
  "button",
41841
41974
  {
@@ -57650,135 +57783,258 @@ Please ensure:
57650
57783
  }
57651
57784
  var AuthenticatedTicketsView = withAuth(React24__default.memo(TicketsView));
57652
57785
  var TicketsView_default = TicketsView;
57653
- var MOCK_RECOMMENDATIONS = [
57654
- {
57655
- id: "rec-1",
57656
- type: "cycle_time",
57657
- title: "Cycle Time Deviation Detected",
57658
- location: "Unit 1: Station 7",
57659
- line: "Line 1",
57660
- description: "Cycle time observed was 90s but the standard set was 180s. This indicates a potential process deviation or standard mismatch.",
57661
- evidence: {
57662
- type: "video",
57663
- label: "Operator Cycle Recording",
57664
- videoUrls: [
57665
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
57666
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
57667
- // Mock duplicate for demo
57668
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
57669
- // Mock duplicate for demo
57670
- ]
57671
- },
57672
- impact: "Potential 50% efficiency gain or standard adjustment needed.",
57673
- date: "Today",
57674
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
57675
- },
57676
- {
57677
- id: "rec-2",
57678
- type: "efficiency",
57679
- title: "Consistent Low Efficiency Period",
57680
- location: "Line 2",
57681
- line: "Line 2",
57682
- description: "Efficiency from 7pm - 8pm is consistently lower than standard. This pattern has been observed for the last 3 days.",
57683
- evidence: {
57684
- type: "chart",
57685
- label: "7pm - 8pm Efficiency Trend",
57686
- chartData: [
57687
- { label: "7:00", value: 45, standard: 85 },
57688
- { label: "7:15", value: 42, standard: 85 },
57689
- { label: "7:30", value: 40, standard: 85 },
57690
- { label: "7:45", value: 48, standard: 85 },
57691
- { label: "8:00", value: 50, standard: 85 }
57692
- ]
57693
- },
57694
- impact: "Loss of approx. 40 units per hour during shift changeover.",
57695
- date: "Today",
57696
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
57697
- },
57698
- {
57699
- id: "rec-3",
57700
- type: "downtime",
57701
- title: "Recurring Micro-stoppages",
57702
- location: "Unit 3: Conveyor",
57703
- line: "Line 1",
57704
- description: "Frequent micro-stoppages detected every 15 minutes. This suggests a potential jam or sensor issue.",
57705
- evidence: {
57706
- type: "chart",
57707
- label: "Downtime Events",
57708
- chartData: [
57709
- { label: "9:00", value: 2, standard: 0 },
57710
- { label: "9:15", value: 3, standard: 0 },
57711
- { label: "9:30", value: 1, standard: 0 },
57712
- { label: "9:45", value: 4, standard: 0 },
57713
- { label: "10:00", value: 2, standard: 0 }
57714
- ]
57715
- },
57716
- impact: "Cumulative downtime of 45 mins per shift.",
57717
- date: "Yesterday",
57718
- timestamp: new Date(Date.now() - 864e5).toISOString()
57719
- },
57720
- {
57721
- id: "rec-4",
57722
- type: "efficiency",
57723
- title: "High Idle Time Detected",
57724
- location: "Packaging Station",
57725
- line: "Line 2",
57726
- description: "Operator idle time exceeds 20% of shift duration. Consider rebalancing workload.",
57727
- evidence: {
57728
- type: "video",
57729
- label: "Idle Time Observation",
57730
- videoUrls: [
57731
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
57732
- ]
57733
- },
57734
- impact: "Potential to reassign 1 FTE to other tasks.",
57735
- date: "Yesterday",
57736
- timestamp: new Date(Date.now() - 864e5).toISOString()
57737
- },
57738
- {
57739
- id: "rec-5",
57740
- type: "cycle_time",
57741
- title: "Slow Setup Time",
57742
- location: "Machine 5",
57743
- line: "Line 1",
57744
- description: "Setup time for product changeover took 45 mins, standard is 20 mins.",
57745
- evidence: {
57746
- type: "chart",
57747
- label: "Changeover Duration",
57748
- chartData: [
57749
- { label: "Prev 1", value: 25, standard: 20 },
57750
- { label: "Prev 2", value: 22, standard: 20 },
57751
- { label: "Current", value: 45, standard: 20 },
57752
- { label: "Avg", value: 30, standard: 20 },
57753
- { label: "Target", value: 20, standard: 20 }
57754
- ]
57755
- },
57756
- impact: "25 mins of lost production time.",
57757
- date: "2 Days Ago",
57758
- timestamp: new Date(Date.now() - 1728e5).toISOString()
57786
+
57787
+ // src/lib/api/s3-clips-parser.ts
57788
+ function parseS3Uri(s3Uri, sopCategories) {
57789
+ const path = new URL(s3Uri).pathname;
57790
+ const parts = path.split("/").filter((p) => p);
57791
+ if (s3Uri.includes("missed_qchecks")) {
57792
+ console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
57793
+ return null;
57759
57794
  }
57795
+ if (parts.length < 8) {
57796
+ console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
57797
+ return null;
57798
+ }
57799
+ try {
57800
+ const datePart = parts[2];
57801
+ const shiftPart = parts[3];
57802
+ const violationType = parts[4];
57803
+ let folderName = "";
57804
+ let timestamp = "";
57805
+ for (let i = 5; i < parts.length; i++) {
57806
+ const part = parts[i];
57807
+ if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
57808
+ folderName = part;
57809
+ const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
57810
+ if (timeMatch) {
57811
+ timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
57812
+ break;
57813
+ }
57814
+ }
57815
+ }
57816
+ if (!timestamp) {
57817
+ console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
57818
+ timestamp = "00:00:00";
57819
+ }
57820
+ let severity = "low";
57821
+ let type = "bottleneck";
57822
+ let description = "Analysis Clip";
57823
+ const normalizedViolationType = violationType.toLowerCase().trim();
57824
+ if (sopCategories && sopCategories.length > 0) {
57825
+ const matchedCategory = sopCategories.find((category) => {
57826
+ const categoryId = category.id.toLowerCase();
57827
+ const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
57828
+ return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
57829
+ normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
57830
+ });
57831
+ if (matchedCategory) {
57832
+ type = matchedCategory.id;
57833
+ description = matchedCategory.description || matchedCategory.label;
57834
+ if (matchedCategory.color.includes("red")) {
57835
+ severity = "high";
57836
+ } else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
57837
+ severity = "medium";
57838
+ } else {
57839
+ severity = "low";
57840
+ }
57841
+ console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
57842
+ return { timestamp, severity, description, type, originalUri: s3Uri };
57843
+ }
57844
+ }
57845
+ switch (normalizedViolationType) {
57846
+ case "idle_time":
57847
+ case "idle":
57848
+ case "low_value":
57849
+ case "low value":
57850
+ case "low_value_moment":
57851
+ case "low_value_moments":
57852
+ case "low value moment":
57853
+ case "low value moments":
57854
+ type = "low_value";
57855
+ severity = "low";
57856
+ description = "Idle Time Detected";
57857
+ break;
57858
+ case "sop_deviation":
57859
+ type = "missing_quality_check";
57860
+ severity = "high";
57861
+ description = "SOP Deviations";
57862
+ break;
57863
+ case "long_cycle_time":
57864
+ severity = "high";
57865
+ type = "long_cycle_time";
57866
+ description = "Long Cycle Time Detected";
57867
+ break;
57868
+ case "best_cycle_time":
57869
+ type = "best_cycle_time";
57870
+ severity = "low";
57871
+ description = "Best Cycle Time Performance";
57872
+ break;
57873
+ case "worst_cycle_time":
57874
+ type = "worst_cycle_time";
57875
+ severity = "high";
57876
+ description = "Worst Cycle Time Performance";
57877
+ break;
57878
+ case "cycle_completion":
57879
+ case "completed_cycles":
57880
+ case "completed_cycle":
57881
+ type = "cycle_completion";
57882
+ severity = "low";
57883
+ description = "Cycle Completion";
57884
+ break;
57885
+ case "running_cycle":
57886
+ case "active_cycle":
57887
+ case "production_cycle":
57888
+ type = "running_cycle";
57889
+ severity = "low";
57890
+ description = "Active Production Cycle";
57891
+ break;
57892
+ case "setup_state":
57893
+ case "machine_setup":
57894
+ case "line_setup":
57895
+ type = "setup_state";
57896
+ severity = "medium";
57897
+ description = "Machine Setup Activity";
57898
+ break;
57899
+ case "medium_bottleneck":
57900
+ severity = "medium";
57901
+ description = "Medium Bottleneck Identified";
57902
+ break;
57903
+ case "minor_bottleneck":
57904
+ case "mild_bottleneck":
57905
+ severity = "low";
57906
+ description = "Minor Bottleneck Identified";
57907
+ break;
57908
+ default:
57909
+ if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
57910
+ type = "missing_quality_check";
57911
+ severity = "high";
57912
+ description = "SOP Deviations";
57913
+ } else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
57914
+ type = "worst_cycle_time";
57915
+ severity = "high";
57916
+ description = "Worst Cycle Time Performance";
57917
+ } else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
57918
+ type = "best_cycle_time";
57919
+ severity = "low";
57920
+ description = "Best Cycle Time Performance";
57921
+ } else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
57922
+ type = "long_cycle_time";
57923
+ severity = "high";
57924
+ description = "Long Cycle Time Detected";
57925
+ } else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
57926
+ type = "cycle_completion";
57927
+ severity = "low";
57928
+ description = "Cycle Completion";
57929
+ } else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
57930
+ type = "running_cycle";
57931
+ severity = "low";
57932
+ description = "Active Production Cycle";
57933
+ } else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
57934
+ type = "setup_state";
57935
+ severity = "medium";
57936
+ description = "Machine Setup Activity";
57937
+ } else {
57938
+ description = `Clip type: ${violationType.replace(/_/g, " ")}`;
57939
+ console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
57940
+ }
57941
+ break;
57942
+ }
57943
+ return { timestamp, severity, description, type, originalUri: s3Uri };
57944
+ } catch (error) {
57945
+ console.error(`Error parsing S3 URI: ${s3Uri}`, error);
57946
+ return null;
57947
+ }
57948
+ }
57949
+ function shuffleArray(array) {
57950
+ const shuffled = [...array];
57951
+ for (let i = shuffled.length - 1; i > 0; i--) {
57952
+ const j = Math.floor(Math.random() * (i + 1));
57953
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
57954
+ }
57955
+ return shuffled;
57956
+ }
57957
+ var CATEGORY_OPTIONS = [
57958
+ { id: "all", label: "All" },
57959
+ { id: "cycle_time", label: "Cycle Time" },
57960
+ { id: "efficiency", label: "Efficiency" },
57961
+ { id: "downtime", label: "Downtime" }
57760
57962
  ];
57761
- var VideoCarousel = ({ urls }) => {
57963
+ var ClipVideoCarousel = ({ clips, clipsService }) => {
57762
57964
  const [currentIndex, setCurrentIndex] = useState(0);
57965
+ const [videos, setVideos] = useState([]);
57966
+ const [loading, setLoading] = useState(false);
57967
+ const [error, setError] = useState(null);
57968
+ useEffect(() => {
57969
+ let cancelled = false;
57970
+ const load = async () => {
57971
+ if (!clipsService) {
57972
+ setError("Clips are not configured for this dashboard");
57973
+ return;
57974
+ }
57975
+ if (!clips || clips.length === 0) return;
57976
+ setLoading(true);
57977
+ setError(null);
57978
+ setVideos([]);
57979
+ try {
57980
+ const resolved = await Promise.all(
57981
+ clips.map(async (c) => {
57982
+ const video = await clipsService.getClipById(c.clip_id);
57983
+ if (!video) return null;
57984
+ const cycleTime = typeof c.cycle_time_seconds === "number" ? c.cycle_time_seconds : typeof c.cycle_sec === "number" ? c.cycle_sec : void 0;
57985
+ return {
57986
+ ...video,
57987
+ cycle_time_seconds: cycleTime ?? video.cycle_time_seconds
57988
+ };
57989
+ })
57990
+ );
57991
+ if (cancelled) return;
57992
+ setVideos(resolved.filter(Boolean) || []);
57993
+ } catch (err) {
57994
+ if (cancelled) return;
57995
+ setError(err?.message || "Failed to load clips");
57996
+ setVideos([]);
57997
+ } finally {
57998
+ if (!cancelled) setLoading(false);
57999
+ }
58000
+ };
58001
+ load();
58002
+ return () => {
58003
+ cancelled = true;
58004
+ };
58005
+ }, [clipsService, clips]);
58006
+ useEffect(() => {
58007
+ if (videos.length > 0 && currentIndex >= videos.length) {
58008
+ setCurrentIndex(0);
58009
+ }
58010
+ }, [videos.length, currentIndex]);
57763
58011
  const nextVideo = () => {
57764
- setCurrentIndex((prev) => (prev + 1) % urls.length);
58012
+ setCurrentIndex((prev) => (prev + 1) % Math.max(videos.length, 1));
57765
58013
  };
57766
58014
  const prevVideo = () => {
57767
- setCurrentIndex((prev) => (prev - 1 + urls.length) % urls.length);
58015
+ setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
57768
58016
  };
57769
- if (!urls || urls.length === 0) return null;
58017
+ if (!clips || clips.length === 0) return null;
58018
+ const currentVideo = videos[currentIndex];
57770
58019
  return /* @__PURE__ */ jsxs("div", { className: "relative group bg-gray-900 rounded-lg overflow-hidden aspect-video", children: [
57771
- /* @__PURE__ */ jsx(
57772
- "video",
58020
+ loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: "Loading clips\u2026" }),
58021
+ !loading && error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: error }),
58022
+ !loading && !error && currentVideo?.src && /* @__PURE__ */ jsx(
58023
+ VideoPlayer,
57773
58024
  {
57774
- src: urls[currentIndex],
57775
- className: "w-full h-full object-cover opacity-80 group-hover:opacity-100 transition-opacity",
57776
- controls: true
58025
+ src: currentVideo.src,
58026
+ className: "w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity",
58027
+ controls: true,
58028
+ playsInline: true
57777
58029
  },
57778
- urls[currentIndex]
58030
+ currentVideo.src
57779
58031
  ),
57780
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none group-hover:opacity-0 transition-opacity", children: /* @__PURE__ */ jsx(PlayCircleIcon, { className: "w-16 h-16 text-white opacity-80" }) }),
57781
- urls.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
58032
+ !loading && !error && typeof currentVideo?.cycle_time_seconds === "number" && /* @__PURE__ */ 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: [
58033
+ currentVideo.cycle_time_seconds.toFixed(1),
58034
+ "s"
58035
+ ] }),
58036
+ !loading && !error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none group-hover:opacity-0 transition-opacity", children: /* @__PURE__ */ jsx(PlayCircleIcon, { className: "w-16 h-16 text-white opacity-80" }) }),
58037
+ videos.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
57782
58038
  /* @__PURE__ */ jsx(
57783
58039
  "button",
57784
58040
  {
@@ -57806,70 +58062,273 @@ var VideoCarousel = ({ urls }) => {
57806
58062
  /* @__PURE__ */ 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: [
57807
58063
  currentIndex + 1,
57808
58064
  " / ",
57809
- urls.length
58065
+ videos.length
57810
58066
  ] })
57811
58067
  ] })
57812
58068
  ] });
57813
58069
  };
57814
- var EvidenceChart = ({ data }) => {
57815
- const chartData = data.map((point) => ({
57816
- name: point.label,
57817
- value: point.value,
57818
- standard: point.standard
57819
- }));
58070
+ var BarChartEvidence = ({ x, y, unit, data, series, referenceLines }) => {
58071
+ const scale2 = unit === "fraction" ? 100 : 1;
58072
+ const yAxisUnit = unit === "fraction" ? "%" : "";
58073
+ const targetLineIndex = referenceLines?.findIndex(
58074
+ (line) => (line.label || "").toLowerCase().includes("target")
58075
+ ) ?? -1;
58076
+ const resolvedTargetLineIndex = targetLineIndex >= 0 ? targetLineIndex : referenceLines?.length === 1 ? 0 : -1;
58077
+ const unitLabel = unit === "fraction" ? "%" : unit === "pieces" ? "PPH" : unit === "ratio" ? "" : unit || "";
58078
+ const formatTargetValue = (value) => {
58079
+ if (unit === "fraction") return value.toFixed(0);
58080
+ if (Number.isInteger(value)) return value.toString();
58081
+ return value.toFixed(1);
58082
+ };
58083
+ const formatTargetLabel = (label, value) => {
58084
+ const base = label || "Target";
58085
+ const suffix = unitLabel ? unitLabel === "%" ? "%" : ` ${unitLabel}` : "";
58086
+ return `${base}: ${formatTargetValue(value)}${suffix}`;
58087
+ };
58088
+ const isMultiSeries = Array.isArray(series) && series.length > 0 && Array.isArray(data);
58089
+ const chartData = isMultiSeries ? (data || []).map((row) => {
58090
+ const scaled = { ...row };
58091
+ series?.forEach((s) => {
58092
+ const raw = row[s.dataKey];
58093
+ if (typeof raw === "number") {
58094
+ scaled[s.dataKey] = raw * scale2;
58095
+ }
58096
+ });
58097
+ return scaled;
58098
+ }) : (x || []).map((xv, idx) => {
58099
+ const yv = y?.[idx];
58100
+ if (yv === null || yv === void 0) return null;
58101
+ const label = typeof xv === "number" ? `${xv.toString().padStart(2, "0")}:00` : String(xv);
58102
+ return { name: label, value: yv * scale2 };
58103
+ }).filter(Boolean);
58104
+ const referenceLineConfigs = (referenceLines || []).map((line, idx) => {
58105
+ const scaledValue = line.y * scale2;
58106
+ const isTarget = idx === resolvedTargetLineIndex;
58107
+ return {
58108
+ ...line,
58109
+ y: scaledValue,
58110
+ label: isTarget ? formatTargetLabel(line.label, scaledValue) : line.label,
58111
+ stroke: isTarget ? "#E34329" : line.stroke,
58112
+ strokeDasharray: isTarget ? "5 5" : line.strokeDasharray,
58113
+ strokeWidth: isTarget ? 2 : line.strokeWidth
58114
+ };
58115
+ });
58116
+ const targetLine = resolvedTargetLineIndex >= 0 ? (referenceLines || [])[resolvedTargetLineIndex] : void 0;
58117
+ const targetValue = targetLine ? targetLine.y * scale2 : null;
58118
+ const targetLabel = targetLine && targetValue !== null ? formatTargetLabel(targetLine.label, targetValue) : null;
58119
+ return /* @__PURE__ */ jsxs("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2 relative", children: [
58120
+ targetLabel && /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 z-10 pointer-events-none", children: /* @__PURE__ */ 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: [
58121
+ /* @__PURE__ */ jsx("span", { className: "block w-6 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
58122
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: targetLabel })
58123
+ ] }) }),
58124
+ /* @__PURE__ */ jsx(
58125
+ BarChart,
58126
+ {
58127
+ data: chartData,
58128
+ bars: isMultiSeries ? (series || []).map((s) => ({
58129
+ dataKey: s.dataKey,
58130
+ name: s.name,
58131
+ fill: s.fill,
58132
+ stackId: s.stackId,
58133
+ labelList: false
58134
+ })) : [
58135
+ {
58136
+ dataKey: "value",
58137
+ name: "Value",
58138
+ fill: "#f87171",
58139
+ // red-400
58140
+ labelList: false
58141
+ }
58142
+ ],
58143
+ xAxisDataKey: "name",
58144
+ showLegend: isMultiSeries && (series || []).length > 1,
58145
+ showGrid: true,
58146
+ aspect: 2.5,
58147
+ yAxisUnit,
58148
+ referenceLines: referenceLineConfigs,
58149
+ className: "h-full"
58150
+ }
58151
+ )
58152
+ ] });
58153
+ };
58154
+ var PieChartEvidence = ({ data }) => {
58155
+ return /* @__PURE__ */ jsx("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2", children: /* @__PURE__ */ jsx(IdleTimeReasonChart_default, { data }) });
58156
+ };
58157
+ var LineChartEvidence = ({ x, y, unit }) => {
58158
+ const scale2 = unit === "fraction" ? 100 : 1;
58159
+ const yAxisUnit = unit === "fraction" ? "%" : "";
58160
+ const chartData = x.map((xv, idx) => {
58161
+ const yv = y[idx];
58162
+ if (yv === null || yv === void 0) return null;
58163
+ return { name: String(xv), value: yv * scale2 };
58164
+ }).filter(Boolean);
57820
58165
  return /* @__PURE__ */ jsx("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2", children: /* @__PURE__ */ jsx(
57821
- BarChart,
58166
+ LineChart,
57822
58167
  {
57823
58168
  data: chartData,
57824
- bars: [
58169
+ lines: [
57825
58170
  {
57826
58171
  dataKey: "value",
57827
- name: "Actual",
57828
- fill: "#f87171",
57829
- // red-400
57830
- labelList: true
58172
+ name: "Value",
58173
+ stroke: "#f87171",
58174
+ strokeWidth: 2,
58175
+ dot: true
57831
58176
  }
57832
58177
  ],
57833
58178
  xAxisDataKey: "name",
57834
58179
  showLegend: false,
57835
58180
  showGrid: true,
57836
58181
  aspect: 2.5,
58182
+ yAxisUnit,
57837
58183
  className: "h-full"
57838
58184
  }
57839
58185
  ) });
57840
58186
  };
58187
+ var TableEvidence = ({ columns, rows }) => {
58188
+ const formatValue = (value) => {
58189
+ if (typeof value === "number") {
58190
+ if (Number.isInteger(value)) return value.toString();
58191
+ return value.toFixed(3);
58192
+ }
58193
+ if (value === null || value === void 0) return "-";
58194
+ return String(value);
58195
+ };
58196
+ return /* @__PURE__ */ jsx("div", { className: "w-full bg-white border border-gray-100 rounded-lg overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full text-xs", children: [
58197
+ /* @__PURE__ */ jsx("thead", { className: "bg-gray-50 border-b border-gray-100", children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx(
58198
+ "th",
58199
+ {
58200
+ className: "px-3 py-2 text-left font-semibold text-gray-600 uppercase tracking-wide",
58201
+ children: col.replace(/_/g, " ")
58202
+ },
58203
+ col
58204
+ )) }) }),
58205
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, idx) => /* @__PURE__ */ jsx("tr", { className: "border-b border-gray-100 last:border-b-0", children: columns.map((col) => /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-gray-700 whitespace-nowrap", children: formatValue(row[col]) }, col)) }, idx)) })
58206
+ ] }) }) });
58207
+ };
57841
58208
  var ImprovementCenterView = () => {
57842
58209
  const { navigate } = useNavigation();
57843
- const [selectedDate, setSelectedDate] = useState("");
57844
- const [selectedLine, setSelectedLine] = useState("");
57845
- const dateInputRef = useRef(null);
57846
- const uniqueLines = useMemo(() => {
57847
- const lines = new Set(MOCK_RECOMMENDATIONS.map((r2) => r2.line));
57848
- return Array.from(lines);
57849
- }, []);
57850
- const filteredRecommendations = useMemo(() => {
57851
- return MOCK_RECOMMENDATIONS.filter((rec) => {
57852
- if (selectedDate) {
57853
- const recDate = new Date(rec.timestamp).toISOString().split("T")[0];
57854
- if (recDate !== selectedDate) return false;
57855
- }
57856
- if (selectedLine && rec.line !== selectedLine) {
57857
- return false;
58210
+ const supabase = useSupabase();
58211
+ const { user } = useAuth();
58212
+ const dashboardConfig = useDashboardConfig();
58213
+ const entityConfig = useEntityConfig();
58214
+ const [currentDate, setCurrentDate] = useState(/* @__PURE__ */ new Date());
58215
+ const [selectedCategory, setSelectedCategory] = useState("all");
58216
+ const [selectedLineId, setSelectedLineId] = useState("all");
58217
+ const [selectedStatus, setSelectedStatus] = useState("all");
58218
+ const [recommendations, setRecommendations] = useState([]);
58219
+ const [isLoading, setIsLoading] = useState(false);
58220
+ const [loadError, setLoadError] = useState(null);
58221
+ const computeWeeksOpen = (firstSeenAt) => {
58222
+ if (!firstSeenAt) return void 0;
58223
+ const firstSeen = new Date(firstSeenAt);
58224
+ if (Number.isNaN(firstSeen.getTime())) return void 0;
58225
+ const now2 = /* @__PURE__ */ new Date();
58226
+ const diffDays = Math.max(0, Math.floor((now2.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
58227
+ return Math.max(1, Math.ceil(diffDays / 7));
58228
+ };
58229
+ const configuredLines = useMemo(() => {
58230
+ return entityConfig.lines || entityConfig.lineNames || {};
58231
+ }, [entityConfig.lines, entityConfig.lineNames]);
58232
+ const configuredLineIds = useMemo(() => Object.keys(configuredLines), [configuredLines]);
58233
+ const { accessibleLineIds } = useUserLineAccess(configuredLineIds);
58234
+ const scopeLineIds = useMemo(() => {
58235
+ return accessibleLineIds.length > 0 ? accessibleLineIds : configuredLineIds;
58236
+ }, [accessibleLineIds, configuredLineIds]);
58237
+ const scopeLineIdsKey = useMemo(() => scopeLineIds.join(","), [scopeLineIds]);
58238
+ const companyId = user?.company_id || entityConfig.companyId;
58239
+ const clipsService = useMemo(() => {
58240
+ try {
58241
+ return new S3ClipsSupabaseService(dashboardConfig);
58242
+ } catch (err) {
58243
+ return null;
58244
+ }
58245
+ }, [dashboardConfig]);
58246
+ const nextMonth = () => {
58247
+ setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1));
58248
+ };
58249
+ const prevMonth = () => {
58250
+ setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1));
58251
+ };
58252
+ const formatMonth = (date) => {
58253
+ return date.toLocaleDateString("en-US", { month: "long", year: "numeric" });
58254
+ };
58255
+ useEffect(() => {
58256
+ let cancelled = false;
58257
+ const fetchRecommendations = async () => {
58258
+ if (!supabase || !companyId) return;
58259
+ setIsLoading(true);
58260
+ setLoadError(null);
58261
+ try {
58262
+ if (scopeLineIds.length === 0) {
58263
+ setRecommendations([]);
58264
+ return;
58265
+ }
58266
+ const params = new URLSearchParams({
58267
+ company_id: companyId,
58268
+ status: "all",
58269
+ limit: "500"
58270
+ });
58271
+ params.set("month", `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, "0")}`);
58272
+ if (scopeLineIds.length > 0) {
58273
+ params.set("line_ids", scopeLineIds.join(","));
58274
+ }
58275
+ const res = await fetchBackendJson(supabase, `/api/improvement-center/recommendations?${params.toString()}`);
58276
+ if (cancelled) return;
58277
+ let recs = res.recommendations || [];
58278
+ recs = recs.map((r2) => ({
58279
+ ...r2,
58280
+ ticket_status: r2.issue_status === "resolved" ? "solved" : "active",
58281
+ weeks_open: typeof r2.weeks_open === "number" ? r2.weeks_open : computeWeeksOpen(r2.first_seen_at) ?? 1
58282
+ }));
58283
+ const allowed = new Set(scopeLineIds);
58284
+ setRecommendations(recs.filter((r2) => !r2.line_id || allowed.has(r2.line_id)));
58285
+ } catch (err) {
58286
+ if (cancelled) return;
58287
+ setLoadError(err?.message || "Failed to load recommendations");
58288
+ setRecommendations([]);
58289
+ } finally {
58290
+ if (!cancelled) setIsLoading(false);
57858
58291
  }
58292
+ };
58293
+ fetchRecommendations();
58294
+ return () => {
58295
+ cancelled = true;
58296
+ };
58297
+ }, [supabase, companyId, scopeLineIdsKey, currentDate]);
58298
+ const filteredRecommendations = useMemo(() => {
58299
+ return recommendations.filter((rec) => {
58300
+ if (selectedCategory !== "all" && rec.type !== selectedCategory) return false;
58301
+ if (selectedLineId !== "all" && rec.line_id !== selectedLineId) return false;
58302
+ if (selectedStatus === "resolved" && rec.ticket_status !== "solved") return false;
58303
+ if (selectedStatus === "unresolved" && rec.ticket_status === "solved") return false;
57859
58304
  return true;
57860
- });
57861
- }, [selectedDate, selectedLine]);
57862
- const handleDateClick = () => {
57863
- dateInputRef.current?.showPicker?.();
57864
- };
58305
+ }).sort((a, b) => (b.weeks_open || 0) - (a.weeks_open || 0));
58306
+ }, [recommendations, selectedCategory, selectedLineId, selectedStatus]);
58307
+ const stats = useMemo(() => {
58308
+ const total = recommendations.length;
58309
+ const resolved = recommendations.filter((r2) => r2.ticket_status === "solved").length;
58310
+ return {
58311
+ all: total,
58312
+ resolved,
58313
+ unresolved: total - resolved
58314
+ };
58315
+ }, [recommendations]);
57865
58316
  const clearFilters = () => {
57866
- setSelectedDate("");
57867
- setSelectedLine("");
57868
- };
57869
- const hasActiveFilters = selectedDate || selectedLine;
58317
+ setSelectedCategory("all");
58318
+ setSelectedLineId("all");
58319
+ setSelectedStatus("all");
58320
+ };
58321
+ const lineOptions = useMemo(() => {
58322
+ const options = [{ id: "all", label: "All Lines" }];
58323
+ scopeLineIds.forEach((lineId) => {
58324
+ const lineName = configuredLines[lineId] || lineId;
58325
+ options.push({ id: lineId, label: lineName });
58326
+ });
58327
+ return options;
58328
+ }, [scopeLineIds, configuredLines]);
57870
58329
  return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
57871
58330
  /* @__PURE__ */ jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
57872
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(
58331
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4 min-w-[120px]", children: /* @__PURE__ */ jsx(
57873
58332
  BackButtonMinimal,
57874
58333
  {
57875
58334
  onClick: () => navigate("/"),
@@ -57877,121 +58336,190 @@ var ImprovementCenterView = () => {
57877
58336
  }
57878
58337
  ) }),
57879
58338
  /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
57880
- /* @__PURE__ */ jsxs("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center flex items-center gap-2", children: [
57881
- /* @__PURE__ */ jsx(LightBulbIcon, { className: "w-7 h-7 text-amber-500" }),
57882
- "Improvement Center"
57883
- ] }),
57884
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: "Daily AI-driven recommendations to optimize your line performance" })
58339
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "Improvement Center" }),
58340
+ /* @__PURE__ */ 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" })
57885
58341
  ] }),
57886
- /* @__PURE__ */ jsx("div", { className: "w-20" })
58342
+ /* @__PURE__ */ jsx("div", { className: "min-w-[120px]" })
57887
58343
  ] }) }) }),
57888
- /* @__PURE__ */ jsx("div", { className: "bg-gray-50 px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto flex items-center justify-end gap-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
57889
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
58344
+ /* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 sm:p-6 max-w-7xl mx-auto w-full", children: [
58345
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 bg-white rounded-full px-4 py-2 border border-gray-200 shadow-sm", children: [
57890
58346
  /* @__PURE__ */ jsx(
57891
- "input",
57892
- {
57893
- ref: dateInputRef,
57894
- type: "date",
57895
- value: selectedDate,
57896
- onChange: (e) => setSelectedDate(e.target.value),
57897
- className: "sr-only"
57898
- }
57899
- ),
57900
- /* @__PURE__ */ jsxs(
57901
58347
  "button",
57902
58348
  {
57903
- onClick: handleDateClick,
57904
- 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"}`,
57905
- children: [
57906
- /* @__PURE__ */ jsx(CalendarIcon, { className: "w-4 h-4" }),
57907
- /* @__PURE__ */ jsx("span", { children: selectedDate || "All Dates" })
57908
- ]
58349
+ onClick: prevMonth,
58350
+ className: "p-1 rounded-full hover:bg-gray-100 text-gray-500 transition-colors",
58351
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "w-5 h-5" })
57909
58352
  }
57910
- )
57911
- ] }),
57912
- /* @__PURE__ */ jsxs(
57913
- "select",
57914
- {
57915
- value: selectedLine,
57916
- onChange: (e) => setSelectedLine(e.target.value),
57917
- 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"}`,
57918
- children: [
57919
- /* @__PURE__ */ jsx("option", { value: "", children: "All Lines" }),
57920
- uniqueLines.map((line) => /* @__PURE__ */ jsx("option", { value: line, children: line }, line))
57921
- ]
57922
- }
57923
- ),
57924
- hasActiveFilters && /* @__PURE__ */ jsx(
57925
- "button",
57926
- {
57927
- onClick: clearFilters,
57928
- className: "p-1.5 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors",
57929
- title: "Clear filters",
57930
- children: /* @__PURE__ */ jsx(XMarkIcon, { className: "w-5 h-5" })
57931
- }
57932
- )
57933
- ] }) }) }),
57934
- /* @__PURE__ */ jsx("main", { className: "flex-1 p-4 sm:p-6 max-w-7xl mx-auto w-full", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-6", children: [
57935
- filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsx(
57936
- motion.div,
57937
- {
57938
- initial: { opacity: 0, y: 20 },
57939
- animate: { opacity: 1, y: 0 },
57940
- transition: { delay: index * 0.1 },
57941
- className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden",
57942
- children: /* @__PURE__ */ jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
57943
- /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4", children: [
57944
- /* @__PURE__ */ jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
57945
- /* @__PURE__ */ 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() }),
57946
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900 mt-2", children: rec.title }),
57947
- /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-gray-500", children: [
57948
- rec.location,
57949
- " \u2022 ",
57950
- rec.line,
57951
- " \u2022 ",
57952
- rec.date
57953
- ] })
57954
- ] }) }),
57955
- /* @__PURE__ */ jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsx("p", { children: rec.description }) }),
57956
- /* @__PURE__ */ jsx("div", { className: "bg-amber-50 border border-amber-100 rounded-lg p-3", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-amber-800 font-medium flex items-start gap-2", children: [
57957
- /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
57958
- "Impact: ",
57959
- rec.impact
57960
- ] }) })
57961
- ] }),
57962
- /* @__PURE__ */ 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: [
57963
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
57964
- /* @__PURE__ */ jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
57965
- rec.evidence.type === "video" ? /* @__PURE__ */ jsx(PlayCircleIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-4 h-4" }),
57966
- "Evidence: ",
57967
- rec.evidence.label
57968
- ] }),
57969
- rec.evidence.type === "video" && rec.evidence.videoUrls && rec.evidence.videoUrls.length > 1 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded", children: [
57970
- rec.evidence.videoUrls.length,
57971
- " clips"
57972
- ] })
57973
- ] }),
57974
- rec.evidence.type === "video" && rec.evidence.videoUrls && /* @__PURE__ */ jsx(VideoCarousel, { urls: rec.evidence.videoUrls }),
57975
- rec.evidence.type === "chart" && rec.evidence.chartData && /* @__PURE__ */ jsx(EvidenceChart, { data: rec.evidence.chartData })
57976
- ] })
57977
- ] }) })
57978
- },
57979
- rec.id
57980
- )) : /* @__PURE__ */ jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-dashed border-gray-300", children: [
57981
- /* @__PURE__ */ jsx("div", { className: "mx-auto flex items-center justify-center w-12 h-12 rounded-full bg-gray-100 mb-3", children: /* @__PURE__ */ jsx(FunnelIcon, { className: "w-6 h-6 text-gray-400" }) }),
57982
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: "No recommendations found" }),
57983
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Try adjusting your filters to see more results." }),
58353
+ ),
58354
+ /* @__PURE__ */ jsx("span", { className: "text-base font-semibold text-gray-900 min-w-[160px] text-center", children: formatMonth(currentDate) }),
57984
58355
  /* @__PURE__ */ jsx(
57985
58356
  "button",
57986
58357
  {
57987
- onClick: clearFilters,
57988
- className: "mt-4 text-sm text-blue-600 hover:text-blue-800 font-medium",
57989
- children: "Clear all filters"
58358
+ onClick: nextMonth,
58359
+ className: "p-1 rounded-full hover:bg-gray-100 text-gray-500 transition-colors",
58360
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-5 h-5" })
57990
58361
  }
57991
58362
  )
58363
+ ] }) }),
58364
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row items-center justify-between gap-4 mb-6", children: [
58365
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
58366
+ /* @__PURE__ */ jsxs(
58367
+ "button",
58368
+ {
58369
+ onClick: () => setSelectedStatus("all"),
58370
+ 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"}`,
58371
+ children: [
58372
+ "All ",
58373
+ /* @__PURE__ */ jsx("span", { className: "ml-1.5 bg-gray-100 px-2 py-0.5 rounded-full text-xs text-gray-600", children: stats.all })
58374
+ ]
58375
+ }
58376
+ ),
58377
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300 mx-1" }),
58378
+ /* @__PURE__ */ jsxs(
58379
+ "button",
58380
+ {
58381
+ onClick: () => setSelectedStatus("resolved"),
58382
+ 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"}`,
58383
+ children: [
58384
+ /* @__PURE__ */ jsx(CheckCircleIcon, { className: "w-4 h-4 text-green-500" }),
58385
+ "Resolved ",
58386
+ stats.resolved
58387
+ ]
58388
+ }
58389
+ ),
58390
+ /* @__PURE__ */ jsxs(
58391
+ "button",
58392
+ {
58393
+ onClick: () => setSelectedStatus("unresolved"),
58394
+ 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"}`,
58395
+ children: [
58396
+ /* @__PURE__ */ jsx(XCircleIcon, { className: "w-4 h-4 text-red-500" }),
58397
+ "Unresolved ",
58398
+ stats.unresolved
58399
+ ]
58400
+ }
58401
+ )
58402
+ ] }),
58403
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 w-full md:w-auto", children: [
58404
+ /* @__PURE__ */ jsx(
58405
+ "select",
58406
+ {
58407
+ value: selectedCategory,
58408
+ onChange: (e) => setSelectedCategory(e.target.value),
58409
+ 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",
58410
+ 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` },
58411
+ children: CATEGORY_OPTIONS.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.id, children: opt.label }, opt.id))
58412
+ }
58413
+ ),
58414
+ /* @__PURE__ */ jsx(
58415
+ "select",
58416
+ {
58417
+ value: selectedLineId,
58418
+ onChange: (e) => setSelectedLineId(e.target.value),
58419
+ 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",
58420
+ 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` },
58421
+ children: lineOptions.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.id, children: opt.label }, opt.id))
58422
+ }
58423
+ )
58424
+ ] })
57992
58425
  ] }),
57993
- filteredRecommendations.length > 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-10", children: /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-sm", children: "More recommendations will appear here as we analyze more data." }) })
57994
- ] }) })
58426
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6", children: [
58427
+ filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsx(
58428
+ motion.div,
58429
+ {
58430
+ initial: { opacity: 0, y: 20 },
58431
+ animate: { opacity: 1, y: 0 },
58432
+ transition: { delay: index * 0.1 },
58433
+ className: `bg-white rounded-xl shadow-sm border overflow-hidden ${rec.ticket_status === "solved" ? "border-green-200" : "border-gray-200"}`,
58434
+ children: /* @__PURE__ */ jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
58435
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4", children: [
58436
+ /* @__PURE__ */ jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 w-full", children: [
58437
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
58438
+ /* @__PURE__ */ 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() }),
58439
+ (rec.shift_label || rec.shift_id !== void 0) && /* @__PURE__ */ 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}` }),
58440
+ rec.ticket_status === "solved" ? /* @__PURE__ */ 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: [
58441
+ /* @__PURE__ */ jsx(CheckCircleIcon, { className: "w-3.5 h-3.5" }),
58442
+ "Resolved"
58443
+ ] }) : /* @__PURE__ */ 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: [
58444
+ /* @__PURE__ */ jsx(ClockIcon, { className: "w-3.5 h-3.5" }),
58445
+ "Open for ",
58446
+ rec.weeks_open || 1,
58447
+ " week",
58448
+ (rec.weeks_open || 1) !== 1 ? "s" : ""
58449
+ ] })
58450
+ ] }),
58451
+ /* @__PURE__ */ jsxs("div", { children: [
58452
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900", children: rec.title }),
58453
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-gray-500 mt-1", children: [
58454
+ rec.location,
58455
+ " \u2022 ",
58456
+ rec.line
58457
+ ] })
58458
+ ] })
58459
+ ] }) }),
58460
+ /* @__PURE__ */ jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsx("p", { children: rec.description }) }),
58461
+ /* @__PURE__ */ jsx("div", { className: "bg-amber-50 border border-amber-100 rounded-lg p-3", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-amber-800 font-medium flex items-start gap-2", children: [
58462
+ /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
58463
+ "Impact: ",
58464
+ rec.impact
58465
+ ] }) })
58466
+ ] }),
58467
+ /* @__PURE__ */ 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__ */ jsx("div", { className: "space-y-4", children: rec.evidence.map((ev, idx) => {
58468
+ const isVideo = ev.type === "video_gallery";
58469
+ const isChart = ev.type === "bar_chart" || ev.type === "timeseries" || ev.type === "pie_chart";
58470
+ const title = ev.title || "Evidence";
58471
+ const clips = Array.isArray(ev.clips) ? ev.clips : [];
58472
+ const clipCount = clips.length;
58473
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
58474
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
58475
+ /* @__PURE__ */ jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
58476
+ isVideo ? /* @__PURE__ */ jsx(PlayCircleIcon, { className: "w-4 h-4" }) : isChart ? /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-4 h-4" }),
58477
+ title
58478
+ ] }),
58479
+ isVideo && clipCount > 1 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded", children: [
58480
+ clipCount,
58481
+ " clips"
58482
+ ] })
58483
+ ] }),
58484
+ ev.type === "video_gallery" && /* @__PURE__ */ jsx(ClipVideoCarousel, { clips, clipsService }),
58485
+ ev.type === "bar_chart" && (Array.isArray(ev.x) && Array.isArray(ev.y) || Array.isArray(ev.data) && Array.isArray(ev.series)) && /* @__PURE__ */ jsx(
58486
+ BarChartEvidence,
58487
+ {
58488
+ x: ev.x,
58489
+ y: ev.y,
58490
+ data: ev.data,
58491
+ series: ev.series,
58492
+ unit: ev.unit,
58493
+ referenceLines: Array.isArray(ev.reference_lines) ? ev.reference_lines : void 0
58494
+ }
58495
+ ),
58496
+ ev.type === "pie_chart" && Array.isArray(ev.data) && /* @__PURE__ */ jsx(PieChartEvidence, { data: ev.data }),
58497
+ ev.type === "timeseries" && Array.isArray(ev.x) && Array.isArray(ev.y) && /* @__PURE__ */ jsx(LineChartEvidence, { x: ev.x, y: ev.y, unit: ev.unit }),
58498
+ ev.type === "table" && Array.isArray(ev.columns) && Array.isArray(ev.rows) && /* @__PURE__ */ jsx(TableEvidence, { columns: ev.columns, rows: ev.rows })
58499
+ ] }, `${rec.id}-ev-${idx}`);
58500
+ }) }) : /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: isLoading ? "Loading evidence\u2026" : loadError ? loadError : "No evidence available" }) })
58501
+ ] }) })
58502
+ },
58503
+ rec.id
58504
+ )) : (
58505
+ // Success State (Zero Tickets)
58506
+ /* @__PURE__ */ jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-gray-200 shadow-sm", children: [
58507
+ /* @__PURE__ */ jsx("div", { className: "mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-50 mb-4", children: /* @__PURE__ */ jsx(CheckCircleIcon, { className: "w-8 h-8 text-green-500" }) }),
58508
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "No tickets found" }),
58509
+ /* @__PURE__ */ 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!" }),
58510
+ (selectedCategory !== "all" || selectedLineId !== "all" || selectedStatus !== "all") && /* @__PURE__ */ jsx(
58511
+ "button",
58512
+ {
58513
+ onClick: clearFilters,
58514
+ 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",
58515
+ children: "Clear filters"
58516
+ }
58517
+ )
58518
+ ] })
58519
+ ),
58520
+ filteredRecommendations.length > 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-10", children: /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-sm", children: "More recommendations will appear here as we analyze more data." }) })
58521
+ ] })
58522
+ ] })
57995
58523
  ] });
57996
58524
  };
57997
58525
  var ImprovementCenterView_default = ImprovementCenterView;
@@ -58450,175 +58978,4 @@ var streamProxyConfig = {
58450
58978
  }
58451
58979
  };
58452
58980
 
58453
- // src/lib/api/s3-clips-parser.ts
58454
- function parseS3Uri(s3Uri, sopCategories) {
58455
- const path = new URL(s3Uri).pathname;
58456
- const parts = path.split("/").filter((p) => p);
58457
- if (s3Uri.includes("missed_qchecks")) {
58458
- console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
58459
- return null;
58460
- }
58461
- if (parts.length < 8) {
58462
- console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
58463
- return null;
58464
- }
58465
- try {
58466
- const datePart = parts[2];
58467
- const shiftPart = parts[3];
58468
- const violationType = parts[4];
58469
- let folderName = "";
58470
- let timestamp = "";
58471
- for (let i = 5; i < parts.length; i++) {
58472
- const part = parts[i];
58473
- if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
58474
- folderName = part;
58475
- const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
58476
- if (timeMatch) {
58477
- timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
58478
- break;
58479
- }
58480
- }
58481
- }
58482
- if (!timestamp) {
58483
- console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
58484
- timestamp = "00:00:00";
58485
- }
58486
- let severity = "low";
58487
- let type = "bottleneck";
58488
- let description = "Analysis Clip";
58489
- const normalizedViolationType = violationType.toLowerCase().trim();
58490
- if (sopCategories && sopCategories.length > 0) {
58491
- const matchedCategory = sopCategories.find((category) => {
58492
- const categoryId = category.id.toLowerCase();
58493
- const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
58494
- return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
58495
- normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
58496
- });
58497
- if (matchedCategory) {
58498
- type = matchedCategory.id;
58499
- description = matchedCategory.description || matchedCategory.label;
58500
- if (matchedCategory.color.includes("red")) {
58501
- severity = "high";
58502
- } else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
58503
- severity = "medium";
58504
- } else {
58505
- severity = "low";
58506
- }
58507
- console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
58508
- return { timestamp, severity, description, type, originalUri: s3Uri };
58509
- }
58510
- }
58511
- switch (normalizedViolationType) {
58512
- case "idle_time":
58513
- case "idle":
58514
- case "low_value":
58515
- case "low value":
58516
- case "low_value_moment":
58517
- case "low_value_moments":
58518
- case "low value moment":
58519
- case "low value moments":
58520
- type = "low_value";
58521
- severity = "low";
58522
- description = "Idle Time Detected";
58523
- break;
58524
- case "sop_deviation":
58525
- type = "missing_quality_check";
58526
- severity = "high";
58527
- description = "SOP Deviations";
58528
- break;
58529
- case "long_cycle_time":
58530
- severity = "high";
58531
- type = "long_cycle_time";
58532
- description = "Long Cycle Time Detected";
58533
- break;
58534
- case "best_cycle_time":
58535
- type = "best_cycle_time";
58536
- severity = "low";
58537
- description = "Best Cycle Time Performance";
58538
- break;
58539
- case "worst_cycle_time":
58540
- type = "worst_cycle_time";
58541
- severity = "high";
58542
- description = "Worst Cycle Time Performance";
58543
- break;
58544
- case "cycle_completion":
58545
- case "completed_cycles":
58546
- case "completed_cycle":
58547
- type = "cycle_completion";
58548
- severity = "low";
58549
- description = "Cycle Completion";
58550
- break;
58551
- case "running_cycle":
58552
- case "active_cycle":
58553
- case "production_cycle":
58554
- type = "running_cycle";
58555
- severity = "low";
58556
- description = "Active Production Cycle";
58557
- break;
58558
- case "setup_state":
58559
- case "machine_setup":
58560
- case "line_setup":
58561
- type = "setup_state";
58562
- severity = "medium";
58563
- description = "Machine Setup Activity";
58564
- break;
58565
- case "medium_bottleneck":
58566
- severity = "medium";
58567
- description = "Medium Bottleneck Identified";
58568
- break;
58569
- case "minor_bottleneck":
58570
- case "mild_bottleneck":
58571
- severity = "low";
58572
- description = "Minor Bottleneck Identified";
58573
- break;
58574
- default:
58575
- if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
58576
- type = "missing_quality_check";
58577
- severity = "high";
58578
- description = "SOP Deviations";
58579
- } else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
58580
- type = "worst_cycle_time";
58581
- severity = "high";
58582
- description = "Worst Cycle Time Performance";
58583
- } else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
58584
- type = "best_cycle_time";
58585
- severity = "low";
58586
- description = "Best Cycle Time Performance";
58587
- } else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
58588
- type = "long_cycle_time";
58589
- severity = "high";
58590
- description = "Long Cycle Time Detected";
58591
- } else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
58592
- type = "cycle_completion";
58593
- severity = "low";
58594
- description = "Cycle Completion";
58595
- } else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
58596
- type = "running_cycle";
58597
- severity = "low";
58598
- description = "Active Production Cycle";
58599
- } else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
58600
- type = "setup_state";
58601
- severity = "medium";
58602
- description = "Machine Setup Activity";
58603
- } else {
58604
- description = `Clip type: ${violationType.replace(/_/g, " ")}`;
58605
- console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
58606
- }
58607
- break;
58608
- }
58609
- return { timestamp, severity, description, type, originalUri: s3Uri };
58610
- } catch (error) {
58611
- console.error(`Error parsing S3 URI: ${s3Uri}`, error);
58612
- return null;
58613
- }
58614
- }
58615
- function shuffleArray(array) {
58616
- const shuffled = [...array];
58617
- for (let i = shuffled.length - 1; i > 0; i--) {
58618
- const j = Math.floor(Math.random() * (i + 1));
58619
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
58620
- }
58621
- return shuffled;
58622
- }
58623
-
58624
58981
  export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, TeamUsagePdfGenerator, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, aggregateKPIsFromLineMetricsRows, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, buildKPIsFromLineMetricsRow, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatDuration, formatISTDate, formatIdleTime, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAvailableShiftIds, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getNextUpdateInterval, getOperationalDate, getReasonColor, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeReasons, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMultiLineShiftConfigs, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };