@optifye/dashboard-core 6.10.5 → 6.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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) {
@@ -28969,6 +28987,33 @@ var VideoControls = ({
28969
28987
  }
28970
28988
  );
28971
28989
  };
28990
+
28991
+ // src/lib/utils/r2Detection.ts
28992
+ function isR2WorkerUrl(url, r2WorkerDomain) {
28993
+ if (!url || !r2WorkerDomain) return false;
28994
+ try {
28995
+ const workerDomain = new URL(r2WorkerDomain).hostname;
28996
+ return url.includes(workerDomain);
28997
+ } catch {
28998
+ return url.includes(r2WorkerDomain.replace(/https?:\/\//, ""));
28999
+ }
29000
+ }
29001
+
29002
+ // src/lib/services/hlsAuthService.ts
29003
+ async function getAuthTokenForHls(supabase) {
29004
+ try {
29005
+ const { data: { session } } = await supabase.auth.getSession();
29006
+ if (!session?.access_token) {
29007
+ console.warn("[HLS Auth] No active session, R2 streaming may fail");
29008
+ return null;
29009
+ }
29010
+ console.log("[HLS Auth] Retrieved token for HLS.js requests");
29011
+ return session.access_token;
29012
+ } catch (error) {
29013
+ console.error("[HLS Auth] Error getting auth token:", error);
29014
+ return null;
29015
+ }
29016
+ }
28972
29017
  var ERROR_MAPPING = {
28973
29018
  "networkError": {
28974
29019
  code: 2,
@@ -28995,6 +29040,38 @@ var ERROR_MAPPING = {
28995
29040
  canRetry: false
28996
29041
  }
28997
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
+ };
28998
29075
  var hlsVideoPlayerStyles = `
28999
29076
  .hls-video-player-container {
29000
29077
  width: 100%;
@@ -29091,12 +29168,17 @@ var HlsVideoPlayer = forwardRef(({
29091
29168
  onSeeked,
29092
29169
  onClick
29093
29170
  }, ref) => {
29171
+ const supabase = useSupabase();
29094
29172
  const videoContainerRef = useRef(null);
29095
29173
  const videoRef = useRef(null);
29096
29174
  const hlsRef = useRef(null);
29097
29175
  const blobUrlRef = useRef(null);
29176
+ const clipIdRef = useRef(null);
29177
+ const r2FallbackAttemptedRef = useRef(false);
29098
29178
  const [isReady, setIsReady] = useState(false);
29099
29179
  const [isLoading, setIsLoading] = useState(true);
29180
+ const [overrideSource, setOverrideSource] = useState(null);
29181
+ const effectiveSrc = overrideSource && overrideSource.baseSrc === src ? overrideSource.value : src;
29100
29182
  const [showControls, setShowControls] = useState(true);
29101
29183
  const [controlsPinned, setControlsPinned] = useState(false);
29102
29184
  const [isPlaying, setIsPlaying] = useState(false);
@@ -29156,10 +29238,16 @@ var HlsVideoPlayer = forwardRef(({
29156
29238
  onSeeked,
29157
29239
  onLoadingChange
29158
29240
  ]);
29241
+ useEffect(() => {
29242
+ clipIdRef.current = extractClipIdFromSource(src);
29243
+ r2FallbackAttemptedRef.current = false;
29244
+ setOverrideSource(null);
29245
+ }, [src]);
29159
29246
  const stableHlsConfigRef = useRef(hlsConfig);
29160
29247
  const stableOptionsRef = useRef(options);
29161
29248
  const configSignatureRef = useRef("");
29162
29249
  const [configVersion, setConfigVersion] = useState(0);
29250
+ const r2WorkerDomain = process.env.NEXT_PUBLIC_R2_WORKER_DOMAIN || "https://r2-stream-proxy.optifye-r2.workers.dev";
29163
29251
  useEffect(() => {
29164
29252
  const serialized = JSON.stringify({
29165
29253
  hlsConfig: hlsConfig || null,
@@ -29178,6 +29266,50 @@ var HlsVideoPlayer = forwardRef(({
29178
29266
  setConfigVersion((prev) => prev + 1);
29179
29267
  }
29180
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]);
29181
29313
  const cleanupBlobUrl = useCallback(() => {
29182
29314
  if (blobUrlRef.current) {
29183
29315
  URL.revokeObjectURL(blobUrlRef.current);
@@ -29218,36 +29350,56 @@ var HlsVideoPlayer = forwardRef(({
29218
29350
  dispose: () => dispose()
29219
29351
  };
29220
29352
  }, [dispose]);
29221
- const initializePlayer = useCallback(() => {
29222
- if (!videoRef.current || !src) return;
29353
+ const initializePlayer = useCallback(async () => {
29354
+ if (!videoRef.current || !effectiveSrc) return;
29223
29355
  const video = videoRef.current;
29224
29356
  const player = playerLikeObject();
29357
+ let authToken = null;
29358
+ try {
29359
+ authToken = await getAuthTokenForHls(supabase);
29360
+ if (!authToken) {
29361
+ console.warn("[HLS Auth] No active session - R2 streaming may fail");
29362
+ }
29363
+ } catch (error) {
29364
+ console.error("[HLS Auth] Error getting auth token:", error);
29365
+ }
29225
29366
  const mergedHlsConfig = {
29226
29367
  ...BASE_HLS_CONFIG,
29227
29368
  ...stableHlsConfigRef.current || {},
29228
- ...stableOptionsRef.current || {}
29369
+ ...stableOptionsRef.current || {},
29370
+ // Modern HLS.js uses Fetch API - override fetch to add Authorization header for R2 Worker requests
29371
+ fetchSetup: function(context, initParams) {
29372
+ const url = context.url;
29373
+ if (isR2WorkerUrl(url, r2WorkerDomain) && authToken) {
29374
+ initParams.headers = {
29375
+ ...initParams.headers,
29376
+ "Authorization": `Bearer ${authToken}`
29377
+ };
29378
+ }
29379
+ return new Request(url, initParams);
29380
+ }
29229
29381
  };
29230
29382
  cleanupBlobUrl();
29231
- const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
29383
+ const isHLS = effectiveSrc.endsWith(".m3u8") || effectiveSrc.startsWith("#EXTM3U");
29232
29384
  if (isHLS) {
29233
- if (src.startsWith("#EXTM3U")) {
29385
+ if (effectiveSrc.startsWith("#EXTM3U")) {
29234
29386
  const safariMode = isSafari();
29235
29387
  const browserName = getBrowserName();
29236
29388
  if (safariMode) {
29237
- const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
29389
+ const clipIdMatch = effectiveSrc.match(/# Clip ID: ([a-f0-9-]+)/i);
29238
29390
  if (clipIdMatch) {
29239
29391
  const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
29240
29392
  console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
29241
29393
  video.src = proxyUrl;
29242
29394
  } else {
29243
29395
  console.warn("[HlsVideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
29244
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
29396
+ const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
29245
29397
  const blobUrl = URL.createObjectURL(blob);
29246
29398
  blobUrlRef.current = blobUrl;
29247
29399
  video.src = blobUrl;
29248
29400
  }
29249
29401
  } else if (Hls3.isSupported()) {
29250
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
29402
+ const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
29251
29403
  const blobUrl = URL.createObjectURL(blob);
29252
29404
  blobUrlRef.current = blobUrl;
29253
29405
  console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
@@ -29260,6 +29412,9 @@ var HlsVideoPlayer = forwardRef(({
29260
29412
  });
29261
29413
  hls.on(Events.ERROR, (event, data) => {
29262
29414
  console.error("[HlsVideoPlayer] HLS.js error:", data);
29415
+ if (maybeHandleR2Fallback(data)) {
29416
+ return;
29417
+ }
29263
29418
  if (data.fatal) {
29264
29419
  let errorInfo;
29265
29420
  switch (data.type) {
@@ -29307,6 +29462,9 @@ var HlsVideoPlayer = forwardRef(({
29307
29462
  });
29308
29463
  hls.on(Events.ERROR, (event, data) => {
29309
29464
  console.error("[HlsVideoPlayer] HLS.js error:", data);
29465
+ if (maybeHandleR2Fallback(data)) {
29466
+ return;
29467
+ }
29310
29468
  if (data.fatal) {
29311
29469
  let errorInfo;
29312
29470
  switch (data.type) {
@@ -29326,14 +29484,14 @@ var HlsVideoPlayer = forwardRef(({
29326
29484
  eventCallbacksRef.current.onError?.(player, errorInfo);
29327
29485
  }
29328
29486
  });
29329
- hls.loadSource(src);
29487
+ hls.loadSource(effectiveSrc);
29330
29488
  hls.attachMedia(video);
29331
29489
  } else {
29332
- video.src = src;
29490
+ video.src = effectiveSrc;
29333
29491
  }
29334
29492
  }
29335
29493
  } else {
29336
- video.src = src;
29494
+ video.src = effectiveSrc;
29337
29495
  }
29338
29496
  const handleCanPlay = () => {
29339
29497
  if (!hlsRef.current) {
@@ -29409,6 +29567,11 @@ var HlsVideoPlayer = forwardRef(({
29409
29567
  eventCallbacksRef.current.onSeeked?.(player);
29410
29568
  };
29411
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
+ }
29412
29575
  const error = video.error;
29413
29576
  if (error) {
29414
29577
  const errorInfo = {
@@ -29457,13 +29620,20 @@ var HlsVideoPlayer = forwardRef(({
29457
29620
  video.removeEventListener("ratechange", handlePlaybackRateChange2);
29458
29621
  };
29459
29622
  }, [
29460
- src,
29623
+ effectiveSrc,
29461
29624
  cleanupBlobUrl,
29462
29625
  playerLikeObject,
29463
- configVersion
29626
+ configVersion,
29627
+ supabase,
29628
+ attemptS3Fallback,
29629
+ maybeHandleR2Fallback,
29630
+ r2WorkerDomain
29464
29631
  ]);
29465
29632
  useEffect(() => {
29466
- const cleanup = initializePlayer();
29633
+ let cleanup;
29634
+ initializePlayer().then((cleanupFn) => {
29635
+ cleanup = cleanupFn;
29636
+ });
29467
29637
  return () => {
29468
29638
  cleanup?.();
29469
29639
  if (hlsRef.current) {
@@ -29473,7 +29643,7 @@ var HlsVideoPlayer = forwardRef(({
29473
29643
  cleanupBlobUrl();
29474
29644
  setIsReady(false);
29475
29645
  };
29476
- }, [src, initializePlayer, cleanupBlobUrl]);
29646
+ }, [effectiveSrc, initializePlayer, cleanupBlobUrl]);
29477
29647
  useEffect(() => {
29478
29648
  if (videoRef.current) {
29479
29649
  if (autoplay) {
@@ -31670,6 +31840,30 @@ var FileManagerFilters = ({
31670
31840
  if (node.type === "category" || node.type === "percentile-category") {
31671
31841
  toggleExpanded(node.id);
31672
31842
  onFilterChange(node.id);
31843
+ if (node.id === "fast-cycles") {
31844
+ trackCoreEvent("Fast Clips Clicked", {
31845
+ workspaceId,
31846
+ date,
31847
+ shift,
31848
+ count: node.count || 0,
31849
+ percentile: filterState.percentile
31850
+ });
31851
+ } else if (node.id === "slow-cycles") {
31852
+ trackCoreEvent("Slow Clips Clicked", {
31853
+ workspaceId,
31854
+ date,
31855
+ shift,
31856
+ count: node.count || 0,
31857
+ percentile: filterState.percentile
31858
+ });
31859
+ } else if (node.id === "cycle_completion") {
31860
+ trackCoreEvent("Cycle Completions Clicked", {
31861
+ workspaceId,
31862
+ date,
31863
+ shift,
31864
+ count: node.count || 0
31865
+ });
31866
+ }
31673
31867
  if (node.id !== "idle_time" && idleLabelFilter) {
31674
31868
  setIdleLabelFilter(null);
31675
31869
  }
@@ -36279,7 +36473,16 @@ var STATIC_COLORS = {
36279
36473
  "Operator Idle": "#8b5cf6"
36280
36474
  // violet-500 - Low Priority/Behavioral
36281
36475
  };
36476
+ var PRODUCTIVE_COLOR = "#00AB45";
36477
+ var IDLE_COLOR = "#e5e7eb";
36282
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
+ }
36283
36486
  if (STATIC_COLORS[name]) {
36284
36487
  return STATIC_COLORS[name];
36285
36488
  }
@@ -36423,6 +36626,7 @@ var IdleTimeReasonChart = ({
36423
36626
  )
36424
36627
  ] });
36425
36628
  };
36629
+ var IdleTimeReasonChart_default = IdleTimeReasonChart;
36426
36630
  var DEFAULT_PERFORMANCE_DATA = {
36427
36631
  avg_efficiency: 0,
36428
36632
  underperforming_workspaces: 0,
@@ -37184,11 +37388,6 @@ var LinePdfGenerator = ({
37184
37388
  doc.setLineWidth(0.8);
37185
37389
  doc.line(20, 118, 190, 118);
37186
37390
  const hourlyOverviewStartY = 123;
37187
- doc.setFontSize(18);
37188
- doc.setFont("helvetica", "bold");
37189
- doc.setTextColor(40, 40, 40);
37190
- doc.text("Hourly Output Overview", 20, 133);
37191
- doc.setTextColor(0, 0, 0);
37192
37391
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
37193
37392
  const [hours, minutes] = startTimeStr.split(":");
37194
37393
  const startHour = parseInt(hours);
@@ -37379,23 +37578,43 @@ var LinePdfGenerator = ({
37379
37578
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
37380
37579
  });
37381
37580
  }
37382
- const tableHeaderY = 143;
37383
- const tableStartY = 151;
37581
+ const tableHeaderY = 146;
37582
+ const tableStartY = 153;
37384
37583
  const rowSpacing = 8;
37385
37584
  const bottomPadding = 8;
37386
37585
  const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
37387
37586
  const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
37388
37587
  doc.setFillColor(245, 245, 245);
37389
37588
  doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
37589
+ doc.setFontSize(18);
37590
+ doc.setFont("helvetica", "bold");
37591
+ doc.setTextColor(40, 40, 40);
37592
+ doc.text("Hourly Performance", 20, 133);
37593
+ doc.setTextColor(0, 0, 0);
37594
+ const gridTopY = 139;
37595
+ const headerBottomY = 148;
37596
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
37597
+ const totalRows = hourlyTimeRanges.length;
37598
+ const gridBottomY = headerBottomY + totalRows * rowSpacing;
37599
+ const tableHeight = gridBottomY - gridTopY;
37600
+ doc.setFillColor(255, 255, 255);
37601
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "F");
37602
+ doc.setDrawColor(230, 230, 230);
37603
+ doc.setLineWidth(0.2);
37604
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "S");
37605
+ doc.setDrawColor(200, 200, 200);
37606
+ doc.setLineWidth(0.3);
37607
+ doc.line(20, headerBottomY, 190, headerBottomY);
37608
+ colBoundaries.slice(1, -1).forEach((x) => {
37609
+ doc.line(x, gridTopY, x, gridBottomY);
37610
+ });
37390
37611
  doc.setFontSize(11);
37391
37612
  doc.setFont("helvetica", "bold");
37392
37613
  doc.text("Time Range", 25, tableHeaderY);
37393
- doc.text("Output", 80, tableHeaderY);
37394
- doc.text("Target", 125, tableHeaderY);
37395
- doc.text("Status", 170, tableHeaderY);
37396
- doc.setLineWidth(0.3);
37397
- doc.setDrawColor(200, 200, 200);
37398
- doc.line(20, 146, 190, 146);
37614
+ doc.text("Output", 75, tableHeaderY);
37615
+ doc.text("Target", 105, tableHeaderY);
37616
+ doc.text("Status", 135, tableHeaderY);
37617
+ doc.text("Remarks", 160, tableHeaderY);
37399
37618
  doc.setFont("helvetica", "normal");
37400
37619
  let yPos = tableStartY;
37401
37620
  const lineDateForTable = new Date(lineInfo.date);
@@ -37417,20 +37636,25 @@ var LinePdfGenerator = ({
37417
37636
  const dataCollected = !isTodayForTable || hourNumber < currentHour;
37418
37637
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
37419
37638
  const targetStr = targetOutputPerHour.toString();
37639
+ if (index < totalRows - 1) {
37640
+ const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
37641
+ doc.setDrawColor(200, 200, 200);
37642
+ doc.line(20, rowBottomY, 190, rowBottomY);
37643
+ }
37420
37644
  doc.text(timeRange, 25, yPos);
37421
- doc.text(outputStr, 80, yPos);
37422
- doc.text(targetStr, 125, yPos);
37645
+ doc.text(outputStr, 75, yPos);
37646
+ doc.text(targetStr, 105, yPos);
37423
37647
  if (!dataCollected) {
37424
37648
  doc.setTextColor(100, 100, 100);
37425
- doc.text("-", 170, yPos);
37649
+ doc.text("-", 135, yPos);
37426
37650
  } else if (actualOutput >= targetOutputPerHour) {
37427
37651
  doc.setTextColor(0, 171, 69);
37428
37652
  doc.setFont("ZapfDingbats", "normal");
37429
- doc.text("4", 170, yPos);
37653
+ doc.text("4", 135, yPos);
37430
37654
  doc.setFont("helvetica", "normal");
37431
37655
  } else {
37432
37656
  doc.setTextColor(227, 67, 41);
37433
- doc.text("\xD7", 170, yPos);
37657
+ doc.text("\xD7", 135, yPos);
37434
37658
  }
37435
37659
  doc.setTextColor(0, 0, 0);
37436
37660
  yPos += rowSpacing;
@@ -37438,7 +37662,7 @@ var LinePdfGenerator = ({
37438
37662
  doc.addPage();
37439
37663
  yPos = addHeaderPage2();
37440
37664
  const workspaceSectionStartY = yPos;
37441
- const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
37665
+ const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 3);
37442
37666
  const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
37443
37667
  const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
37444
37668
  doc.setFillColor(245, 245, 245);
@@ -37446,28 +37670,45 @@ var LinePdfGenerator = ({
37446
37670
  doc.setFontSize(18);
37447
37671
  doc.setFont("helvetica", "bold");
37448
37672
  doc.setTextColor(40, 40, 40);
37449
- doc.text("Poorest Performing Workspaces", 20, yPos);
37673
+ doc.text("Poorest Performing Workspaces", 20, yPos + 10);
37450
37674
  doc.setTextColor(0, 0, 0);
37451
- yPos += 10;
37675
+ yPos += 20;
37676
+ const wsGridTopY = yPos - 5;
37677
+ const wsHeaderBottomY = wsGridTopY + 8;
37678
+ const wsColBoundaries = [20, 80, 140, 190];
37679
+ const wsGridBottomY = wsHeaderBottomY + wsRowCount * 8;
37680
+ const wsTableTotalHeight = wsGridBottomY - wsGridTopY;
37681
+ doc.setFillColor(255, 255, 255);
37682
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "F");
37683
+ doc.setDrawColor(230, 230, 230);
37684
+ doc.setLineWidth(0.2);
37685
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "S");
37686
+ doc.setDrawColor(200, 200, 200);
37687
+ doc.setLineWidth(0.3);
37688
+ doc.line(20, wsHeaderBottomY, 190, wsHeaderBottomY);
37689
+ wsColBoundaries.slice(1, -1).forEach((x) => {
37690
+ doc.line(x, wsGridTopY, x, wsGridBottomY);
37691
+ });
37452
37692
  doc.setFontSize(11);
37453
37693
  doc.setFont("helvetica", "bold");
37454
- yPos += 5;
37694
+ yPos = wsGridTopY + 5.5;
37455
37695
  doc.text("Workspace", 25, yPos);
37456
37696
  doc.text("Current/Target", 85, yPos);
37457
37697
  doc.text("Efficiency", 145, yPos);
37458
- yPos += 3;
37459
- doc.setLineWidth(0.3);
37460
- doc.setDrawColor(200, 200, 200);
37461
- doc.line(20, yPos, 190, yPos);
37462
37698
  doc.setFont("helvetica", "normal");
37463
- yPos += 7;
37699
+ yPos = wsHeaderBottomY + 5.5;
37464
37700
  if (sortedWorkspaces.length === 0) {
37465
37701
  doc.text("No workspace data available", 25, yPos);
37466
- yPos += 10;
37702
+ yPos += 8;
37467
37703
  } else {
37468
37704
  sortedWorkspaces.forEach((ws, index) => {
37469
37705
  const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
37470
37706
  const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
37707
+ if (index < wsRowCount - 1) {
37708
+ const rowBottomY = wsHeaderBottomY + (index + 1) * 8;
37709
+ doc.setDrawColor(200, 200, 200);
37710
+ doc.line(20, rowBottomY, 190, rowBottomY);
37711
+ }
37471
37712
  doc.text(truncatedName, 25, yPos);
37472
37713
  doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
37473
37714
  doc.text(`${(ws.efficiency || 0).toFixed(1)}%`, 145, yPos);
@@ -38810,18 +39051,27 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38810
39051
  const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38811
39052
  doc.text("Hourly Performance", 20, hourlyTitleY);
38812
39053
  doc.setTextColor(0, 0, 0);
38813
- doc.setFontSize(headerFontSize);
38814
- doc.setFont("helvetica", "bold");
38815
39054
  const baseHeaderY = hasIdleTimeReason ? 208 : 198;
38816
39055
  const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
38817
- doc.text("Time Range", 25, headerY);
38818
- doc.text("Output", 95, headerY);
38819
- doc.text("Target", 135, headerY);
38820
- doc.text("Status", 170, headerY);
38821
- doc.setLineWidth(0.3);
39056
+ const gridTopY = headerY - 5;
39057
+ const headerBottomY = gridTopY + 8;
39058
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
39059
+ const totalRows = hourlyData.length;
39060
+ const gridBottomY = headerBottomY + totalRows * rowHeight;
38822
39061
  doc.setDrawColor(200, 200, 200);
38823
- const separatorY = headerY + 3;
38824
- doc.line(20, separatorY, 190, separatorY);
39062
+ doc.setLineWidth(0.3);
39063
+ doc.line(20, gridTopY, 190, gridTopY);
39064
+ doc.line(20, headerBottomY, 190, headerBottomY);
39065
+ colBoundaries.forEach((x) => {
39066
+ doc.line(x, gridTopY, x, gridBottomY);
39067
+ });
39068
+ doc.setFontSize(headerFontSize);
39069
+ doc.setFont("helvetica", "bold");
39070
+ doc.text("Time Range", 25, headerY);
39071
+ doc.text("Output", 75, headerY);
39072
+ doc.text("Target", 105, headerY);
39073
+ doc.text("Status", 135, headerY);
39074
+ doc.text("Remarks", 160, headerY);
38825
39075
  doc.setFontSize(contentFontSize);
38826
39076
  doc.setFont("helvetica", "normal");
38827
39077
  let yPos = tableStartY;
@@ -38852,20 +39102,23 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38852
39102
  const dataCollected = !isToday2 || hourNumber < currentHour;
38853
39103
  const outputStr = dataCollected ? output.toString() : "TBD";
38854
39104
  const targetStr = hourlyTarget.toString();
39105
+ const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39106
+ doc.setDrawColor(200, 200, 200);
39107
+ doc.line(20, rowBottomY, 190, rowBottomY);
38855
39108
  doc.text(timeRange, 25, yPos);
38856
- doc.text(outputStr, 95, yPos);
38857
- doc.text(targetStr, 135, yPos);
39109
+ doc.text(outputStr, 75, yPos);
39110
+ doc.text(targetStr, 105, yPos);
38858
39111
  if (!dataCollected) {
38859
39112
  doc.setTextColor(100, 100, 100);
38860
- doc.text("-", 170, yPos);
39113
+ doc.text("-", 135, yPos);
38861
39114
  } else if (output >= hourlyTarget) {
38862
39115
  doc.setTextColor(0, 171, 69);
38863
39116
  doc.setFont("ZapfDingbats", "normal");
38864
- doc.text("4", 170, yPos);
39117
+ doc.text("4", 135, yPos);
38865
39118
  doc.setFont("helvetica", "normal");
38866
39119
  } else {
38867
39120
  doc.setTextColor(227, 67, 41);
38868
- doc.text("\xD7", 170, yPos);
39121
+ doc.text("\xD7", 135, yPos);
38869
39122
  }
38870
39123
  doc.setTextColor(0, 0, 0);
38871
39124
  yPos += rowHeight;
@@ -39671,14 +39924,16 @@ var KPICard = ({
39671
39924
  "uppercase tracking-wide sm:tracking-wider"
39672
39925
  ), children: title }),
39673
39926
  /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-0.5 sm:gap-2 flex-wrap", children: [
39674
- /* @__PURE__ */ jsx("span", { className: clsx(
39675
- "font-bold text-gray-900 dark:text-gray-50",
39676
- "text-base sm:text-xl md:text-2xl"
39677
- ), children: isLoading ? "\u2014" : formattedValue }),
39678
- suffix && !isLoading && /* @__PURE__ */ jsx("span", { className: clsx(
39679
- "font-medium text-gray-600 dark:text-gray-300",
39680
- style.compact ? "text-base" : "text-lg"
39681
- ), children: suffix }),
39927
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "h-5 sm:h-6 md:h-7 w-12 sm:w-16 md:w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
39928
+ /* @__PURE__ */ jsx("span", { className: clsx(
39929
+ "font-bold text-gray-900 dark:text-gray-50",
39930
+ "text-base sm:text-xl md:text-2xl"
39931
+ ), children: formattedValue }),
39932
+ suffix && /* @__PURE__ */ jsx("span", { className: clsx(
39933
+ "font-medium text-gray-600 dark:text-gray-300",
39934
+ style.compact ? "text-base" : "text-lg"
39935
+ ), children: suffix })
39936
+ ] }),
39682
39937
  !isLoading && trendInfo.shouldShowTrend && trendInfo.trendValue !== "neutral" && title !== "Output" && /* @__PURE__ */ jsx("span", { className: trendInfo.colorClass, children: /* @__PURE__ */ jsx(
39683
39938
  trendInfo.Icon,
39684
39939
  {
@@ -39837,6 +40092,7 @@ var KPIHeader = ({
39837
40092
  };
39838
40093
  var KPISection = memo(({
39839
40094
  kpis,
40095
+ isLoading = false,
39840
40096
  className,
39841
40097
  layout: layout2 = "row",
39842
40098
  gridColumns,
@@ -39845,7 +40101,8 @@ var KPISection = memo(({
39845
40101
  compactCards = false,
39846
40102
  useSrcLayout = false
39847
40103
  }) => {
39848
- const outputDifference = kpis.outputProgress.current - kpis.outputProgress.idealOutput;
40104
+ const showSkeleton = isLoading || !kpis;
40105
+ const outputDifference = kpis ? kpis.outputProgress.current - kpis.outputProgress.idealOutput : 0;
39849
40106
  const outputIsOnTarget = outputDifference >= 0;
39850
40107
  if (useSrcLayout) {
39851
40108
  return /* @__PURE__ */ jsxs(
@@ -39862,27 +40119,30 @@ var KPISection = memo(({
39862
40119
  KPICard,
39863
40120
  {
39864
40121
  title: "Underperforming",
39865
- value: "2/3",
39866
- change: 0
40122
+ value: showSkeleton ? "" : "2/3",
40123
+ change: 0,
40124
+ isLoading: showSkeleton
39867
40125
  }
39868
40126
  ) }),
39869
40127
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
39870
40128
  KPICard,
39871
40129
  {
39872
40130
  title: "Efficiency",
39873
- value: kpis.efficiency.value,
39874
- change: kpis.efficiency.change,
39875
- suffix: "%"
40131
+ value: showSkeleton ? "" : kpis.efficiency.value,
40132
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40133
+ suffix: "%",
40134
+ isLoading: showSkeleton
39876
40135
  }
39877
40136
  ) }),
39878
40137
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
39879
40138
  KPICard,
39880
40139
  {
39881
40140
  title: "Output Progress",
39882
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39883
- change: kpis.outputProgress.change,
40141
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40142
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
39884
40143
  outputDifference,
39885
- showOutputDetails: true
40144
+ showOutputDetails: !showSkeleton,
40145
+ isLoading: showSkeleton
39886
40146
  }
39887
40147
  ) })
39888
40148
  ]
@@ -39893,34 +40153,30 @@ var KPISection = memo(({
39893
40153
  {
39894
40154
  key: "underperforming",
39895
40155
  title: "Underperforming",
39896
- value: `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
39897
- change: kpis.underperformingWorkers.change
40156
+ value: showSkeleton ? "" : `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
40157
+ change: showSkeleton ? 0 : kpis.underperformingWorkers.change,
40158
+ suffix: void 0,
40159
+ status: void 0
39898
40160
  },
39899
40161
  {
39900
40162
  key: "efficiency",
39901
40163
  title: "Efficiency",
39902
- value: kpis.efficiency.value,
39903
- change: kpis.efficiency.change,
39904
- suffix: "%"
40164
+ value: showSkeleton ? "" : kpis.efficiency.value,
40165
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40166
+ suffix: "%",
40167
+ status: void 0
39905
40168
  },
39906
40169
  {
39907
40170
  key: "outputProgress",
39908
40171
  title: "Output Progress",
39909
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39910
- change: kpis.outputProgress.change,
39911
- status: {
40172
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40173
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
40174
+ suffix: void 0,
40175
+ status: showSkeleton ? void 0 : {
39912
40176
  tooltipText: outputIsOnTarget ? "On Target" : "Off Target",
39913
40177
  positive: outputIsOnTarget
39914
40178
  }
39915
40179
  }
39916
- // Only include these additional KPIs if not using the src layout
39917
- // ...(kpis.qualityCompliance ? [{
39918
- // key: 'qualityCompliance',
39919
- // title: 'Quality Compliance',
39920
- // value: kpis.qualityCompliance.value,
39921
- // change: kpis.qualityCompliance.change,
39922
- // suffix: '%',
39923
- // }] : []),
39924
40180
  ];
39925
40181
  return /* @__PURE__ */ jsx(
39926
40182
  KPIGrid,
@@ -39937,7 +40193,8 @@ var KPISection = memo(({
39937
40193
  change: kpi.change,
39938
40194
  suffix: kpi.suffix,
39939
40195
  status: kpi.status,
39940
- style: { variant: cardVariant, compact: compactCards }
40196
+ style: { variant: cardVariant, compact: compactCards },
40197
+ isLoading: showSkeleton
39941
40198
  },
39942
40199
  kpi.key
39943
40200
  ))
@@ -39946,6 +40203,9 @@ var KPISection = memo(({
39946
40203
  }, (prevProps, nextProps) => {
39947
40204
  const prevKpis = prevProps.kpis;
39948
40205
  const nextKpis = nextProps.kpis;
40206
+ if (prevProps.isLoading !== nextProps.isLoading) return false;
40207
+ if (!prevKpis && !nextKpis) return true;
40208
+ if (!prevKpis || !nextKpis) return false;
39949
40209
  if (prevKpis === nextKpis) return true;
39950
40210
  if (Math.abs(prevKpis.efficiency.value - nextKpis.efficiency.value) >= 0.5) return false;
39951
40211
  if (prevKpis.underperformingWorkers.current !== nextKpis.underperformingWorkers.current || prevKpis.underperformingWorkers.total !== nextKpis.underperformingWorkers.total) return false;
@@ -41284,6 +41544,17 @@ var SideNavBar = memo(({
41284
41544
  });
41285
41545
  onMobileMenuClose?.();
41286
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]);
41287
41558
  const handleTargetsClick = useCallback(() => {
41288
41559
  navigate("/targets", {
41289
41560
  trackingEvent: {
@@ -41401,6 +41672,7 @@ var SideNavBar = memo(({
41401
41672
  const homeButtonClasses = useMemo(() => getButtonClasses("/"), [getButtonClasses, pathname]);
41402
41673
  const leaderboardButtonClasses = useMemo(() => getButtonClasses("/leaderboard"), [getButtonClasses, pathname]);
41403
41674
  const kpisButtonClasses = useMemo(() => getButtonClasses("/kpis"), [getButtonClasses, pathname]);
41675
+ const improvementButtonClasses = useMemo(() => getButtonClasses("/improvement-center"), [getButtonClasses, pathname]);
41404
41676
  const targetsButtonClasses = useMemo(() => getButtonClasses("/targets"), [getButtonClasses, pathname]);
41405
41677
  const shiftsButtonClasses = useMemo(() => getButtonClasses("/shifts"), [getButtonClasses, pathname]);
41406
41678
  const teamManagementButtonClasses = useMemo(() => getButtonClasses("/team-management"), [getButtonClasses, pathname]);
@@ -41468,6 +41740,22 @@ var SideNavBar = memo(({
41468
41740
  ]
41469
41741
  }
41470
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
+ ),
41471
41759
  /* @__PURE__ */ jsxs(
41472
41760
  "button",
41473
41761
  {
@@ -41668,6 +41956,19 @@ var SideNavBar = memo(({
41668
41956
  ]
41669
41957
  }
41670
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
+ ),
41671
41972
  /* @__PURE__ */ jsxs(
41672
41973
  "button",
41673
41974
  {
@@ -45698,7 +45999,8 @@ var TeamUsagePdfGenerator = ({
45698
45999
  usageData,
45699
46000
  daysInRange = 1,
45700
46001
  title = "Team Usage Report",
45701
- className
46002
+ className,
46003
+ iconOnly = false
45702
46004
  }) => {
45703
46005
  const [isGenerating, setIsGenerating] = useState(false);
45704
46006
  const generatePDF = async () => {
@@ -45878,10 +46180,11 @@ var TeamUsagePdfGenerator = ({
45878
46180
  {
45879
46181
  onClick: generatePDF,
45880
46182
  disabled: isGenerating || !usageData,
45881
- className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
46183
+ className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 ${iconOnly ? "p-2" : "px-4 py-2"} text-sm font-medium text-gray-700 hover:bg-gray-200 active:bg-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
46184
+ "aria-label": iconOnly ? isGenerating ? "Generating PDF..." : "Export PDF" : void 0,
45882
46185
  children: [
45883
- /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" }),
45884
- isGenerating ? "Generating..." : "Export PDF"
46186
+ /* @__PURE__ */ jsx(Download, { className: iconOnly ? "h-5 w-5" : "h-4 w-4" }),
46187
+ !iconOnly && (isGenerating ? "Generating..." : "Export PDF")
45885
46188
  ]
45886
46189
  }
45887
46190
  );
@@ -48650,10 +48953,19 @@ function HomeView({
48650
48953
  const [errorMessage, setErrorMessage] = useState(null);
48651
48954
  const [displayNamesInitialized, setDisplayNamesInitialized] = useState(false);
48652
48955
  const [hasInitialDataLoaded, setHasInitialDataLoaded] = useState(false);
48956
+ const KPI_CACHE_KEY = "optifye_kpi_cache";
48653
48957
  const dashboardConfig = useDashboardConfig();
48654
48958
  const entityConfig = useEntityConfig();
48655
48959
  const supabaseClient = useSupabaseClient();
48656
48960
  const { user } = useAuth();
48961
+ const { lines: dbLines } = useLines();
48962
+ const mergedLineNames = useMemo(() => {
48963
+ const merged = { ...lineNames };
48964
+ dbLines.forEach((line) => {
48965
+ merged[line.id] = line.line_name;
48966
+ });
48967
+ return merged;
48968
+ }, [lineNames, dbLines]);
48657
48969
  const isSupervisor = user?.role_level === "supervisor";
48658
48970
  const hasMultipleLines = allLineIds.length > 1;
48659
48971
  const availableLineIds = useMemo(() => {
@@ -48760,6 +49072,42 @@ function HomeView({
48760
49072
  }
48761
49073
  return buildKPIsFromLineMetricsRow(row);
48762
49074
  }, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
49075
+ const [cachedKpis, setCachedKpis] = useState(() => {
49076
+ if (typeof window === "undefined") return null;
49077
+ try {
49078
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
49079
+ if (cached) {
49080
+ const parsed = JSON.parse(cached);
49081
+ return parsed[selectedLineId] || parsed[factoryViewId] || null;
49082
+ }
49083
+ } catch (e) {
49084
+ }
49085
+ return null;
49086
+ });
49087
+ useEffect(() => {
49088
+ if (kpis && typeof window !== "undefined") {
49089
+ try {
49090
+ const existing = localStorage.getItem(KPI_CACHE_KEY);
49091
+ const cache = existing ? JSON.parse(existing) : {};
49092
+ cache[selectedLineId] = kpis;
49093
+ localStorage.setItem(KPI_CACHE_KEY, JSON.stringify(cache));
49094
+ setCachedKpis(kpis);
49095
+ } catch (e) {
49096
+ }
49097
+ }
49098
+ }, [kpis, selectedLineId, KPI_CACHE_KEY]);
49099
+ useEffect(() => {
49100
+ if (typeof window === "undefined") return;
49101
+ try {
49102
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
49103
+ if (cached) {
49104
+ const parsed = JSON.parse(cached);
49105
+ setCachedKpis(parsed[selectedLineId] || null);
49106
+ }
49107
+ } catch (e) {
49108
+ }
49109
+ }, [selectedLineId, KPI_CACHE_KEY]);
49110
+ const displayKpis = kpis || cachedKpis;
48763
49111
  const {
48764
49112
  activeBreaks: allActiveBreaks,
48765
49113
  isLoading: breaksLoading,
@@ -49061,16 +49409,16 @@ function HomeView({
49061
49409
  // Use stable string representation instead of spreading array
49062
49410
  JSON.stringify(workspaceMetrics.map((w) => `${w.workspace_uuid}-${Math.round(w.efficiency)}-${w.trend}`))
49063
49411
  ]);
49064
- const memoizedKPIs = useMemo(() => kpis, [
49412
+ const memoizedKPIs = useMemo(() => displayKpis, [
49065
49413
  // Only update reference when values change by at least 1%
49066
- kpis?.efficiency?.value ? Math.round(kpis.efficiency.value) : null,
49067
- kpis?.underperformingWorkers?.current,
49068
- kpis?.underperformingWorkers?.total,
49069
- kpis?.outputProgress?.current,
49070
- kpis?.outputProgress?.target,
49071
- kpis?.avgCycleTime?.value ? Math.round(kpis.avgCycleTime.value * 10) / 10 : null,
49414
+ displayKpis?.efficiency?.value ? Math.round(displayKpis.efficiency.value) : null,
49415
+ displayKpis?.underperformingWorkers?.current,
49416
+ displayKpis?.underperformingWorkers?.total,
49417
+ displayKpis?.outputProgress?.current,
49418
+ displayKpis?.outputProgress?.target,
49419
+ displayKpis?.avgCycleTime?.value ? Math.round(displayKpis.avgCycleTime.value * 10) / 10 : null,
49072
49420
  // Round to 1 decimal
49073
- kpis?.qualityCompliance?.value ? Math.round(kpis.qualityCompliance.value) : null
49421
+ displayKpis?.qualityCompliance?.value ? Math.round(displayKpis.qualityCompliance.value) : null
49074
49422
  ]);
49075
49423
  useEffect(() => {
49076
49424
  setIsHydrated(true);
@@ -49088,7 +49436,7 @@ function HomeView({
49088
49436
  trackCoreEvent("Home Line Filter Changed", {
49089
49437
  previous_line_id: selectedLineId,
49090
49438
  new_line_id: value,
49091
- line_name: lineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49439
+ line_name: mergedLineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49092
49440
  });
49093
49441
  try {
49094
49442
  sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
@@ -49122,9 +49470,9 @@ function HomeView({
49122
49470
  }
49123
49471
  return /* @__PURE__ */ jsxs(Select, { onValueChange: handleLineChange, defaultValue: selectedLineId, children: [
49124
49472
  /* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-[200px] bg-white border border-gray-200 shadow-sm rounded-md h-9 sm:h-9 text-xs sm:text-sm px-2 sm:px-3", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select a line" }) }),
49125
- /* @__PURE__ */ jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsx(SelectItem, { value: id3, children: lineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49473
+ /* @__PURE__ */ jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsx(SelectItem, { value: id3, children: mergedLineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49126
49474
  ] });
49127
- }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
49475
+ }, [availableLineIds, handleLineChange, selectedLineId, mergedLineNames, factoryViewId, allLineIds.length]);
49128
49476
  const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
49129
49477
  const isDataLoading = metricsLoading;
49130
49478
  if (isInitialLoading) {
@@ -49153,7 +49501,7 @@ function HomeView({
49153
49501
  lineTitle,
49154
49502
  lineId: selectedLineId === factoryViewId ? allLineIds[0] : selectedLineId,
49155
49503
  className: "w-full",
49156
- headerControls: memoizedKPIs ? /* @__PURE__ */ jsx(KPISection2, { kpis: memoizedKPIs, className: "w-full sm:w-auto" }) : null
49504
+ headerControls: /* @__PURE__ */ jsx(KPISection2, { kpis: memoizedKPIs, isLoading: !memoizedKPIs, className: "w-full sm:w-auto" })
49157
49505
  }
49158
49506
  ) }) }),
49159
49507
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative", children: [
@@ -56442,35 +56790,62 @@ var TeamManagementView = ({
56442
56790
  ) }) });
56443
56791
  }
56444
56792
  return /* @__PURE__ */ jsxs("div", { className: cn("min-h-screen bg-slate-50", className), children: [
56445
- /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", 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: [
56446
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56447
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56448
- /* @__PURE__ */ jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56449
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56450
- ] }),
56451
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
56452
- canViewUsageStats && /* @__PURE__ */ jsx(
56453
- TeamUsagePdfGenerator,
56454
- {
56455
- users,
56456
- usageData,
56457
- daysInRange: usageDateRange.daysElapsed,
56458
- title: "Team Usage Report"
56459
- }
56460
- ),
56461
- canAddUsers && /* @__PURE__ */ jsxs(
56462
- "button",
56463
- {
56464
- onClick: () => setIsAddUserDialogOpen(true),
56465
- className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56466
- children: [
56467
- /* @__PURE__ */ jsx(UserPlus, { className: "w-4 h-4" }),
56468
- /* @__PURE__ */ jsx("span", { children: "Add User" })
56469
- ]
56470
- }
56471
- )
56793
+ /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxs("div", { className: "px-3 sm:px-6 lg:px-8 py-3 sm:py-4", children: [
56794
+ /* @__PURE__ */ jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
56795
+ /* @__PURE__ */ jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }),
56796
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900 truncate px-2", children: pageTitle }) }),
56797
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
56798
+ canViewUsageStats && /* @__PURE__ */ jsx(
56799
+ TeamUsagePdfGenerator,
56800
+ {
56801
+ users,
56802
+ usageData,
56803
+ daysInRange: usageDateRange.daysElapsed,
56804
+ title: "Team Usage Report",
56805
+ iconOnly: true
56806
+ }
56807
+ ),
56808
+ canAddUsers && /* @__PURE__ */ jsx(
56809
+ "button",
56810
+ {
56811
+ onClick: () => setIsAddUserDialogOpen(true),
56812
+ className: "p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 active:bg-blue-800 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56813
+ "aria-label": "Add User",
56814
+ children: /* @__PURE__ */ jsx(UserPlus, { className: "w-5 h-5" })
56815
+ }
56816
+ )
56817
+ ] })
56818
+ ] }) }),
56819
+ /* @__PURE__ */ jsxs("div", { className: "hidden sm:flex items-center justify-between", children: [
56820
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56821
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56822
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56823
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56824
+ ] }),
56825
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
56826
+ canViewUsageStats && /* @__PURE__ */ jsx(
56827
+ TeamUsagePdfGenerator,
56828
+ {
56829
+ users,
56830
+ usageData,
56831
+ daysInRange: usageDateRange.daysElapsed,
56832
+ title: "Team Usage Report"
56833
+ }
56834
+ ),
56835
+ canAddUsers && /* @__PURE__ */ jsxs(
56836
+ "button",
56837
+ {
56838
+ onClick: () => setIsAddUserDialogOpen(true),
56839
+ className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56840
+ children: [
56841
+ /* @__PURE__ */ jsx(UserPlus, { className: "w-4 h-4" }),
56842
+ /* @__PURE__ */ jsx("span", { children: "Add User" })
56843
+ ]
56844
+ }
56845
+ )
56846
+ ] })
56472
56847
  ] })
56473
- ] }) }) }),
56848
+ ] }) }),
56474
56849
  /* @__PURE__ */ jsx("main", { className: "px-4 sm:px-6 lg:px-8 py-6", children: /* @__PURE__ */ jsx(
56475
56850
  UserManagementTable,
56476
56851
  {
@@ -57408,135 +57783,258 @@ Please ensure:
57408
57783
  }
57409
57784
  var AuthenticatedTicketsView = withAuth(React24__default.memo(TicketsView));
57410
57785
  var TicketsView_default = TicketsView;
57411
- var MOCK_RECOMMENDATIONS = [
57412
- {
57413
- id: "rec-1",
57414
- type: "cycle_time",
57415
- title: "Cycle Time Deviation Detected",
57416
- location: "Unit 1: Station 7",
57417
- line: "Line 1",
57418
- description: "Cycle time observed was 90s but the standard set was 180s. This indicates a potential process deviation or standard mismatch.",
57419
- evidence: {
57420
- type: "video",
57421
- label: "Operator Cycle Recording",
57422
- videoUrls: [
57423
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
57424
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
57425
- // Mock duplicate for demo
57426
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
57427
- // Mock duplicate for demo
57428
- ]
57429
- },
57430
- impact: "Potential 50% efficiency gain or standard adjustment needed.",
57431
- date: "Today",
57432
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
57433
- },
57434
- {
57435
- id: "rec-2",
57436
- type: "efficiency",
57437
- title: "Consistent Low Efficiency Period",
57438
- location: "Line 2",
57439
- line: "Line 2",
57440
- description: "Efficiency from 7pm - 8pm is consistently lower than standard. This pattern has been observed for the last 3 days.",
57441
- evidence: {
57442
- type: "chart",
57443
- label: "7pm - 8pm Efficiency Trend",
57444
- chartData: [
57445
- { label: "7:00", value: 45, standard: 85 },
57446
- { label: "7:15", value: 42, standard: 85 },
57447
- { label: "7:30", value: 40, standard: 85 },
57448
- { label: "7:45", value: 48, standard: 85 },
57449
- { label: "8:00", value: 50, standard: 85 }
57450
- ]
57451
- },
57452
- impact: "Loss of approx. 40 units per hour during shift changeover.",
57453
- date: "Today",
57454
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
57455
- },
57456
- {
57457
- id: "rec-3",
57458
- type: "downtime",
57459
- title: "Recurring Micro-stoppages",
57460
- location: "Unit 3: Conveyor",
57461
- line: "Line 1",
57462
- description: "Frequent micro-stoppages detected every 15 minutes. This suggests a potential jam or sensor issue.",
57463
- evidence: {
57464
- type: "chart",
57465
- label: "Downtime Events",
57466
- chartData: [
57467
- { label: "9:00", value: 2, standard: 0 },
57468
- { label: "9:15", value: 3, standard: 0 },
57469
- { label: "9:30", value: 1, standard: 0 },
57470
- { label: "9:45", value: 4, standard: 0 },
57471
- { label: "10:00", value: 2, standard: 0 }
57472
- ]
57473
- },
57474
- impact: "Cumulative downtime of 45 mins per shift.",
57475
- date: "Yesterday",
57476
- timestamp: new Date(Date.now() - 864e5).toISOString()
57477
- },
57478
- {
57479
- id: "rec-4",
57480
- type: "efficiency",
57481
- title: "High Idle Time Detected",
57482
- location: "Packaging Station",
57483
- line: "Line 2",
57484
- description: "Operator idle time exceeds 20% of shift duration. Consider rebalancing workload.",
57485
- evidence: {
57486
- type: "video",
57487
- label: "Idle Time Observation",
57488
- videoUrls: [
57489
- "/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
57490
- ]
57491
- },
57492
- impact: "Potential to reassign 1 FTE to other tasks.",
57493
- date: "Yesterday",
57494
- timestamp: new Date(Date.now() - 864e5).toISOString()
57495
- },
57496
- {
57497
- id: "rec-5",
57498
- type: "cycle_time",
57499
- title: "Slow Setup Time",
57500
- location: "Machine 5",
57501
- line: "Line 1",
57502
- description: "Setup time for product changeover took 45 mins, standard is 20 mins.",
57503
- evidence: {
57504
- type: "chart",
57505
- label: "Changeover Duration",
57506
- chartData: [
57507
- { label: "Prev 1", value: 25, standard: 20 },
57508
- { label: "Prev 2", value: 22, standard: 20 },
57509
- { label: "Current", value: 45, standard: 20 },
57510
- { label: "Avg", value: 30, standard: 20 },
57511
- { label: "Target", value: 20, standard: 20 }
57512
- ]
57513
- },
57514
- impact: "25 mins of lost production time.",
57515
- date: "2 Days Ago",
57516
- 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;
57517
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" }
57518
57962
  ];
57519
- var VideoCarousel = ({ urls }) => {
57963
+ var ClipVideoCarousel = ({ clips, clipsService }) => {
57520
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]);
57521
58011
  const nextVideo = () => {
57522
- setCurrentIndex((prev) => (prev + 1) % urls.length);
58012
+ setCurrentIndex((prev) => (prev + 1) % Math.max(videos.length, 1));
57523
58013
  };
57524
58014
  const prevVideo = () => {
57525
- setCurrentIndex((prev) => (prev - 1 + urls.length) % urls.length);
58015
+ setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
57526
58016
  };
57527
- if (!urls || urls.length === 0) return null;
58017
+ if (!clips || clips.length === 0) return null;
58018
+ const currentVideo = videos[currentIndex];
57528
58019
  return /* @__PURE__ */ jsxs("div", { className: "relative group bg-gray-900 rounded-lg overflow-hidden aspect-video", children: [
57529
- /* @__PURE__ */ jsx(
57530
- "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,
57531
58024
  {
57532
- src: urls[currentIndex],
57533
- className: "w-full h-full object-cover opacity-80 group-hover:opacity-100 transition-opacity",
57534
- 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
57535
58029
  },
57536
- urls[currentIndex]
58030
+ currentVideo.src
57537
58031
  ),
57538
- /* @__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" }) }),
57539
- 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: [
57540
58038
  /* @__PURE__ */ jsx(
57541
58039
  "button",
57542
58040
  {
@@ -57564,70 +58062,273 @@ var VideoCarousel = ({ urls }) => {
57564
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: [
57565
58063
  currentIndex + 1,
57566
58064
  " / ",
57567
- urls.length
58065
+ videos.length
57568
58066
  ] })
57569
58067
  ] })
57570
58068
  ] });
57571
58069
  };
57572
- var EvidenceChart = ({ data }) => {
57573
- const chartData = data.map((point) => ({
57574
- name: point.label,
57575
- value: point.value,
57576
- standard: point.standard
57577
- }));
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);
57578
58165
  return /* @__PURE__ */ jsx("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2", children: /* @__PURE__ */ jsx(
57579
- BarChart,
58166
+ LineChart,
57580
58167
  {
57581
58168
  data: chartData,
57582
- bars: [
58169
+ lines: [
57583
58170
  {
57584
58171
  dataKey: "value",
57585
- name: "Actual",
57586
- fill: "#f87171",
57587
- // red-400
57588
- labelList: true
58172
+ name: "Value",
58173
+ stroke: "#f87171",
58174
+ strokeWidth: 2,
58175
+ dot: true
57589
58176
  }
57590
58177
  ],
57591
58178
  xAxisDataKey: "name",
57592
58179
  showLegend: false,
57593
58180
  showGrid: true,
57594
58181
  aspect: 2.5,
58182
+ yAxisUnit,
57595
58183
  className: "h-full"
57596
58184
  }
57597
58185
  ) });
57598
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
+ };
57599
58208
  var ImprovementCenterView = () => {
57600
58209
  const { navigate } = useNavigation();
57601
- const [selectedDate, setSelectedDate] = useState("");
57602
- const [selectedLine, setSelectedLine] = useState("");
57603
- const dateInputRef = useRef(null);
57604
- const uniqueLines = useMemo(() => {
57605
- const lines = new Set(MOCK_RECOMMENDATIONS.map((r2) => r2.line));
57606
- return Array.from(lines);
57607
- }, []);
57608
- const filteredRecommendations = useMemo(() => {
57609
- return MOCK_RECOMMENDATIONS.filter((rec) => {
57610
- if (selectedDate) {
57611
- const recDate = new Date(rec.timestamp).toISOString().split("T")[0];
57612
- if (recDate !== selectedDate) return false;
57613
- }
57614
- if (selectedLine && rec.line !== selectedLine) {
57615
- 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);
57616
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;
57617
58304
  return true;
57618
- });
57619
- }, [selectedDate, selectedLine]);
57620
- const handleDateClick = () => {
57621
- dateInputRef.current?.showPicker?.();
57622
- };
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]);
57623
58316
  const clearFilters = () => {
57624
- setSelectedDate("");
57625
- setSelectedLine("");
57626
- };
57627
- 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]);
57628
58329
  return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
57629
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: [
57630
- /* @__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(
57631
58332
  BackButtonMinimal,
57632
58333
  {
57633
58334
  onClick: () => navigate("/"),
@@ -57635,121 +58336,190 @@ var ImprovementCenterView = () => {
57635
58336
  }
57636
58337
  ) }),
57637
58338
  /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
57638
- /* @__PURE__ */ jsxs("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center flex items-center gap-2", children: [
57639
- /* @__PURE__ */ jsx(LightBulbIcon, { className: "w-7 h-7 text-amber-500" }),
57640
- "Improvement Center"
57641
- ] }),
57642
- /* @__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" })
57643
58341
  ] }),
57644
- /* @__PURE__ */ jsx("div", { className: "w-20" })
58342
+ /* @__PURE__ */ jsx("div", { className: "min-w-[120px]" })
57645
58343
  ] }) }) }),
57646
- /* @__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: [
57647
- /* @__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: [
57648
58346
  /* @__PURE__ */ jsx(
57649
- "input",
57650
- {
57651
- ref: dateInputRef,
57652
- type: "date",
57653
- value: selectedDate,
57654
- onChange: (e) => setSelectedDate(e.target.value),
57655
- className: "sr-only"
57656
- }
57657
- ),
57658
- /* @__PURE__ */ jsxs(
57659
58347
  "button",
57660
58348
  {
57661
- onClick: handleDateClick,
57662
- 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"}`,
57663
- children: [
57664
- /* @__PURE__ */ jsx(CalendarIcon, { className: "w-4 h-4" }),
57665
- /* @__PURE__ */ jsx("span", { children: selectedDate || "All Dates" })
57666
- ]
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" })
57667
58352
  }
57668
- )
57669
- ] }),
57670
- /* @__PURE__ */ jsxs(
57671
- "select",
57672
- {
57673
- value: selectedLine,
57674
- onChange: (e) => setSelectedLine(e.target.value),
57675
- 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"}`,
57676
- children: [
57677
- /* @__PURE__ */ jsx("option", { value: "", children: "All Lines" }),
57678
- uniqueLines.map((line) => /* @__PURE__ */ jsx("option", { value: line, children: line }, line))
57679
- ]
57680
- }
57681
- ),
57682
- hasActiveFilters && /* @__PURE__ */ jsx(
57683
- "button",
57684
- {
57685
- onClick: clearFilters,
57686
- className: "p-1.5 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors",
57687
- title: "Clear filters",
57688
- children: /* @__PURE__ */ jsx(XMarkIcon, { className: "w-5 h-5" })
57689
- }
57690
- )
57691
- ] }) }) }),
57692
- /* @__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: [
57693
- filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsx(
57694
- motion.div,
57695
- {
57696
- initial: { opacity: 0, y: 20 },
57697
- animate: { opacity: 1, y: 0 },
57698
- transition: { delay: index * 0.1 },
57699
- className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden",
57700
- children: /* @__PURE__ */ jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
57701
- /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4", children: [
57702
- /* @__PURE__ */ jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
57703
- /* @__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() }),
57704
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900 mt-2", children: rec.title }),
57705
- /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-gray-500", children: [
57706
- rec.location,
57707
- " \u2022 ",
57708
- rec.line,
57709
- " \u2022 ",
57710
- rec.date
57711
- ] })
57712
- ] }) }),
57713
- /* @__PURE__ */ jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsx("p", { children: rec.description }) }),
57714
- /* @__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: [
57715
- /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
57716
- "Impact: ",
57717
- rec.impact
57718
- ] }) })
57719
- ] }),
57720
- /* @__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: [
57721
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
57722
- /* @__PURE__ */ jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
57723
- rec.evidence.type === "video" ? /* @__PURE__ */ jsx(PlayCircleIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-4 h-4" }),
57724
- "Evidence: ",
57725
- rec.evidence.label
57726
- ] }),
57727
- 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: [
57728
- rec.evidence.videoUrls.length,
57729
- " clips"
57730
- ] })
57731
- ] }),
57732
- rec.evidence.type === "video" && rec.evidence.videoUrls && /* @__PURE__ */ jsx(VideoCarousel, { urls: rec.evidence.videoUrls }),
57733
- rec.evidence.type === "chart" && rec.evidence.chartData && /* @__PURE__ */ jsx(EvidenceChart, { data: rec.evidence.chartData })
57734
- ] })
57735
- ] }) })
57736
- },
57737
- rec.id
57738
- )) : /* @__PURE__ */ jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-dashed border-gray-300", children: [
57739
- /* @__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" }) }),
57740
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: "No recommendations found" }),
57741
- /* @__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) }),
57742
58355
  /* @__PURE__ */ jsx(
57743
58356
  "button",
57744
58357
  {
57745
- onClick: clearFilters,
57746
- className: "mt-4 text-sm text-blue-600 hover:text-blue-800 font-medium",
57747
- 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" })
57748
58361
  }
57749
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
+ ] })
57750
58425
  ] }),
57751
- 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." }) })
57752
- ] }) })
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
+ ] })
57753
58523
  ] });
57754
58524
  };
57755
58525
  var ImprovementCenterView_default = ImprovementCenterView;
@@ -58208,175 +58978,4 @@ var streamProxyConfig = {
58208
58978
  }
58209
58979
  };
58210
58980
 
58211
- // src/lib/api/s3-clips-parser.ts
58212
- function parseS3Uri(s3Uri, sopCategories) {
58213
- const path = new URL(s3Uri).pathname;
58214
- const parts = path.split("/").filter((p) => p);
58215
- if (s3Uri.includes("missed_qchecks")) {
58216
- console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
58217
- return null;
58218
- }
58219
- if (parts.length < 8) {
58220
- console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
58221
- return null;
58222
- }
58223
- try {
58224
- const datePart = parts[2];
58225
- const shiftPart = parts[3];
58226
- const violationType = parts[4];
58227
- let folderName = "";
58228
- let timestamp = "";
58229
- for (let i = 5; i < parts.length; i++) {
58230
- const part = parts[i];
58231
- if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
58232
- folderName = part;
58233
- const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
58234
- if (timeMatch) {
58235
- timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
58236
- break;
58237
- }
58238
- }
58239
- }
58240
- if (!timestamp) {
58241
- console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
58242
- timestamp = "00:00:00";
58243
- }
58244
- let severity = "low";
58245
- let type = "bottleneck";
58246
- let description = "Analysis Clip";
58247
- const normalizedViolationType = violationType.toLowerCase().trim();
58248
- if (sopCategories && sopCategories.length > 0) {
58249
- const matchedCategory = sopCategories.find((category) => {
58250
- const categoryId = category.id.toLowerCase();
58251
- const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
58252
- return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
58253
- normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
58254
- });
58255
- if (matchedCategory) {
58256
- type = matchedCategory.id;
58257
- description = matchedCategory.description || matchedCategory.label;
58258
- if (matchedCategory.color.includes("red")) {
58259
- severity = "high";
58260
- } else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
58261
- severity = "medium";
58262
- } else {
58263
- severity = "low";
58264
- }
58265
- console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
58266
- return { timestamp, severity, description, type, originalUri: s3Uri };
58267
- }
58268
- }
58269
- switch (normalizedViolationType) {
58270
- case "idle_time":
58271
- case "idle":
58272
- case "low_value":
58273
- case "low value":
58274
- case "low_value_moment":
58275
- case "low_value_moments":
58276
- case "low value moment":
58277
- case "low value moments":
58278
- type = "low_value";
58279
- severity = "low";
58280
- description = "Idle Time Detected";
58281
- break;
58282
- case "sop_deviation":
58283
- type = "missing_quality_check";
58284
- severity = "high";
58285
- description = "SOP Deviations";
58286
- break;
58287
- case "long_cycle_time":
58288
- severity = "high";
58289
- type = "long_cycle_time";
58290
- description = "Long Cycle Time Detected";
58291
- break;
58292
- case "best_cycle_time":
58293
- type = "best_cycle_time";
58294
- severity = "low";
58295
- description = "Best Cycle Time Performance";
58296
- break;
58297
- case "worst_cycle_time":
58298
- type = "worst_cycle_time";
58299
- severity = "high";
58300
- description = "Worst Cycle Time Performance";
58301
- break;
58302
- case "cycle_completion":
58303
- case "completed_cycles":
58304
- case "completed_cycle":
58305
- type = "cycle_completion";
58306
- severity = "low";
58307
- description = "Cycle Completion";
58308
- break;
58309
- case "running_cycle":
58310
- case "active_cycle":
58311
- case "production_cycle":
58312
- type = "running_cycle";
58313
- severity = "low";
58314
- description = "Active Production Cycle";
58315
- break;
58316
- case "setup_state":
58317
- case "machine_setup":
58318
- case "line_setup":
58319
- type = "setup_state";
58320
- severity = "medium";
58321
- description = "Machine Setup Activity";
58322
- break;
58323
- case "medium_bottleneck":
58324
- severity = "medium";
58325
- description = "Medium Bottleneck Identified";
58326
- break;
58327
- case "minor_bottleneck":
58328
- case "mild_bottleneck":
58329
- severity = "low";
58330
- description = "Minor Bottleneck Identified";
58331
- break;
58332
- default:
58333
- if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
58334
- type = "missing_quality_check";
58335
- severity = "high";
58336
- description = "SOP Deviations";
58337
- } else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
58338
- type = "worst_cycle_time";
58339
- severity = "high";
58340
- description = "Worst Cycle Time Performance";
58341
- } else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
58342
- type = "best_cycle_time";
58343
- severity = "low";
58344
- description = "Best Cycle Time Performance";
58345
- } else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
58346
- type = "long_cycle_time";
58347
- severity = "high";
58348
- description = "Long Cycle Time Detected";
58349
- } else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
58350
- type = "cycle_completion";
58351
- severity = "low";
58352
- description = "Cycle Completion";
58353
- } else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
58354
- type = "running_cycle";
58355
- severity = "low";
58356
- description = "Active Production Cycle";
58357
- } else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
58358
- type = "setup_state";
58359
- severity = "medium";
58360
- description = "Machine Setup Activity";
58361
- } else {
58362
- description = `Clip type: ${violationType.replace(/_/g, " ")}`;
58363
- console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
58364
- }
58365
- break;
58366
- }
58367
- return { timestamp, severity, description, type, originalUri: s3Uri };
58368
- } catch (error) {
58369
- console.error(`Error parsing S3 URI: ${s3Uri}`, error);
58370
- return null;
58371
- }
58372
- }
58373
- function shuffleArray(array) {
58374
- const shuffled = [...array];
58375
- for (let i = shuffled.length - 1; i > 0; i--) {
58376
- const j = Math.floor(Math.random() * (i + 1));
58377
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
58378
- }
58379
- return shuffled;
58380
- }
58381
-
58382
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 };