@optifye/dashboard-core 6.0.1 → 6.0.2

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.d.mts CHANGED
@@ -201,7 +201,10 @@ interface ShiftConfig {
201
201
  transitionPeriodMinutes?: number;
202
202
  }
203
203
  interface WorkspaceConfig {
204
+ /** @deprecated Use lineDisplayNames for multi-line support */
204
205
  displayNames?: Record<string, string>;
206
+ /** Line-aware display names: lineId -> workspaceId -> displayName */
207
+ lineDisplayNames?: Record<string, Record<string, string>>;
205
208
  specialWorkspaces?: {
206
209
  startId?: number;
207
210
  endId?: number;
@@ -254,6 +257,17 @@ interface S3Config {
254
257
  /** Workspace-specific category overrides by UUID */
255
258
  workspaceOverrides?: Record<string, SOPCategory[]>;
256
259
  };
260
+ /** Processing configuration for S3 clips */
261
+ processing?: {
262
+ /** Default limit per category when fetching clips (default: 30) */
263
+ defaultLimitPerCategory?: number;
264
+ /** Maximum allowed limit per category (default: 1000) */
265
+ maxLimitPerCategory?: number;
266
+ /** Number of concurrent video processing operations (default: 10) */
267
+ concurrencyLimit?: number;
268
+ /** Maximum initial fetch limit for S3 listing (default: 60) */
269
+ maxInitialFetch?: number;
270
+ };
257
271
  }
258
272
  interface VideoCroppingRect {
259
273
  /** X offset as percentage (0-100) from left */
@@ -275,7 +289,10 @@ interface VideoConfig {
275
289
  /** HLS stream URLs for workspaces */
276
290
  hlsUrls?: {
277
291
  defaultHlsUrl?: string;
292
+ /** @deprecated Use lineWorkspaceHlsUrls for multi-line support */
278
293
  workspaceHlsUrls?: Record<string, string>;
294
+ /** Line-aware workspace HLS URLs: lineId -> workspaceName -> URL */
295
+ lineWorkspaceHlsUrls?: Record<string, Record<string, string>>;
279
296
  };
280
297
  /** Video cropping configuration */
281
298
  cropping?: VideoCroppingConfig;
@@ -830,6 +847,10 @@ interface LeaderboardDetailViewProps {
830
847
  * Second line ID reference (Line 2)
831
848
  */
832
849
  line2Id?: string;
850
+ /**
851
+ * Mapping of line IDs to their display names
852
+ */
853
+ lineNames?: Record<string, string>;
833
854
  /**
834
855
  * Optional className for custom styling
835
856
  */
@@ -2603,18 +2624,20 @@ locale?: string) => string;
2603
2624
  *
2604
2625
  * @param workspaceId - The workspace ID (e.g., 'WS01').
2605
2626
  * @param workspaceConfig - The WorkspaceConfig object from the dashboard configuration.
2627
+ * @param lineId - Optional line ID for line-aware display names.
2606
2628
  * @returns The display name from the config, or a fallback based on the ID.
2607
2629
  */
2608
- declare const getConfigurableWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig) => string;
2630
+ declare const getConfigurableWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig, lineId?: string) => string;
2609
2631
  /**
2610
2632
  * Gets a potentially shortened or specific part of the workspace display name.
2611
2633
  * (The logic here might need adjustment based on actual naming conventions used).
2612
2634
  *
2613
2635
  * @param workspaceId - The workspace ID (e.g., 'WS01').
2614
2636
  * @param workspaceConfig - The WorkspaceConfig object from the dashboard configuration.
2637
+ * @param lineId - Optional line ID for line-aware display names.
2615
2638
  * @returns A potentially shortened name, or the full display name, or a fallback.
2616
2639
  */
2617
- declare const getConfigurableShortWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig) => string;
2640
+ declare const getConfigurableShortWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig, lineId?: string) => string;
2618
2641
 
2619
2642
  /**
2620
2643
  * @internal
@@ -2642,6 +2665,7 @@ interface WorkspaceUrlMapping {
2642
2665
  urlName: string;
2643
2666
  workspaceId: string;
2644
2667
  workspaceName: string;
2668
+ lineId?: string;
2645
2669
  }
2646
2670
  declare const toUrlFriendlyName: (workspaceName: string | undefined) => string;
2647
2671
  declare const fromUrlFriendlyName: (urlName: string) => string;
package/dist/index.d.ts CHANGED
@@ -201,7 +201,10 @@ interface ShiftConfig {
201
201
  transitionPeriodMinutes?: number;
202
202
  }
203
203
  interface WorkspaceConfig {
204
+ /** @deprecated Use lineDisplayNames for multi-line support */
204
205
  displayNames?: Record<string, string>;
206
+ /** Line-aware display names: lineId -> workspaceId -> displayName */
207
+ lineDisplayNames?: Record<string, Record<string, string>>;
205
208
  specialWorkspaces?: {
206
209
  startId?: number;
207
210
  endId?: number;
@@ -254,6 +257,17 @@ interface S3Config {
254
257
  /** Workspace-specific category overrides by UUID */
255
258
  workspaceOverrides?: Record<string, SOPCategory[]>;
256
259
  };
260
+ /** Processing configuration for S3 clips */
261
+ processing?: {
262
+ /** Default limit per category when fetching clips (default: 30) */
263
+ defaultLimitPerCategory?: number;
264
+ /** Maximum allowed limit per category (default: 1000) */
265
+ maxLimitPerCategory?: number;
266
+ /** Number of concurrent video processing operations (default: 10) */
267
+ concurrencyLimit?: number;
268
+ /** Maximum initial fetch limit for S3 listing (default: 60) */
269
+ maxInitialFetch?: number;
270
+ };
257
271
  }
258
272
  interface VideoCroppingRect {
259
273
  /** X offset as percentage (0-100) from left */
@@ -275,7 +289,10 @@ interface VideoConfig {
275
289
  /** HLS stream URLs for workspaces */
276
290
  hlsUrls?: {
277
291
  defaultHlsUrl?: string;
292
+ /** @deprecated Use lineWorkspaceHlsUrls for multi-line support */
278
293
  workspaceHlsUrls?: Record<string, string>;
294
+ /** Line-aware workspace HLS URLs: lineId -> workspaceName -> URL */
295
+ lineWorkspaceHlsUrls?: Record<string, Record<string, string>>;
279
296
  };
280
297
  /** Video cropping configuration */
281
298
  cropping?: VideoCroppingConfig;
@@ -830,6 +847,10 @@ interface LeaderboardDetailViewProps {
830
847
  * Second line ID reference (Line 2)
831
848
  */
832
849
  line2Id?: string;
850
+ /**
851
+ * Mapping of line IDs to their display names
852
+ */
853
+ lineNames?: Record<string, string>;
833
854
  /**
834
855
  * Optional className for custom styling
835
856
  */
@@ -2603,18 +2624,20 @@ locale?: string) => string;
2603
2624
  *
2604
2625
  * @param workspaceId - The workspace ID (e.g., 'WS01').
2605
2626
  * @param workspaceConfig - The WorkspaceConfig object from the dashboard configuration.
2627
+ * @param lineId - Optional line ID for line-aware display names.
2606
2628
  * @returns The display name from the config, or a fallback based on the ID.
2607
2629
  */
2608
- declare const getConfigurableWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig) => string;
2630
+ declare const getConfigurableWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig, lineId?: string) => string;
2609
2631
  /**
2610
2632
  * Gets a potentially shortened or specific part of the workspace display name.
2611
2633
  * (The logic here might need adjustment based on actual naming conventions used).
2612
2634
  *
2613
2635
  * @param workspaceId - The workspace ID (e.g., 'WS01').
2614
2636
  * @param workspaceConfig - The WorkspaceConfig object from the dashboard configuration.
2637
+ * @param lineId - Optional line ID for line-aware display names.
2615
2638
  * @returns A potentially shortened name, or the full display name, or a fallback.
2616
2639
  */
2617
- declare const getConfigurableShortWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig) => string;
2640
+ declare const getConfigurableShortWorkspaceDisplayName: (workspaceId: string, workspaceConfig?: WorkspaceConfig, lineId?: string) => string;
2618
2641
 
2619
2642
  /**
2620
2643
  * @internal
@@ -2642,6 +2665,7 @@ interface WorkspaceUrlMapping {
2642
2665
  urlName: string;
2643
2666
  workspaceId: string;
2644
2667
  workspaceName: string;
2668
+ lineId?: string;
2645
2669
  }
2646
2670
  declare const toUrlFriendlyName: (workspaceName: string | undefined) => string;
2647
2671
  declare const fromUrlFriendlyName: (urlName: string) => string;
package/dist/index.js CHANGED
@@ -161,6 +161,9 @@ var DEFAULT_AUTH_CONFIG = {
161
161
  // Defaults related to auth providers, redirects etc.
162
162
  };
163
163
  var DEFAULT_VIDEO_CONFIG = {
164
+ hlsUrls: {
165
+ lineWorkspaceHlsUrls: {}
166
+ },
164
167
  canvasConfig: {
165
168
  fps: 30,
166
169
  useRAF: true
@@ -5125,19 +5128,23 @@ var useActiveBreaks = (lineIds) => {
5125
5128
  const checkActiveBreaks = React14.useCallback(async () => {
5126
5129
  try {
5127
5130
  setError(null);
5128
- if (!lineIds || lineIds.length === 0) {
5131
+ const validLineIds = lineIds.filter(
5132
+ (id3) => id3 && id3 !== "factory" && id3 !== "all" && // Basic UUID format check
5133
+ id3.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
5134
+ );
5135
+ if (!validLineIds || validLineIds.length === 0) {
5129
5136
  setActiveBreaks([]);
5130
5137
  setIsLoading(false);
5131
5138
  return;
5132
5139
  }
5133
5140
  const currentMinutes = getCurrentTimeInMinutes();
5134
- const { data: dayShifts, error: dayError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 0).in("line_id", lineIds);
5135
- const { data: nightShifts, error: nightError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 1).in("line_id", lineIds);
5141
+ const { data: dayShifts, error: dayError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 0).in("line_id", validLineIds);
5142
+ const { data: nightShifts, error: nightError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 1).in("line_id", validLineIds);
5136
5143
  if (dayError || nightError) {
5137
5144
  throw new Error("Failed to fetch shift configurations");
5138
5145
  }
5139
5146
  const foundActiveBreaks = [];
5140
- for (const lineId of lineIds) {
5147
+ for (const lineId of validLineIds) {
5141
5148
  const dayShift = dayShifts?.find((s) => s.line_id === lineId);
5142
5149
  const nightShift = nightShifts?.find((s) => s.line_id === lineId);
5143
5150
  if (!dayShift || !nightShift) continue;
@@ -5621,12 +5628,18 @@ var FALLBACK_DISPLAY_NAMES = {
5621
5628
  "WS03": "Filling station"
5622
5629
  // ... Add others if known defaults are useful
5623
5630
  };
5624
- var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5631
+ var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5632
+ if (lineId && workspaceConfig?.lineDisplayNames?.[lineId]) {
5633
+ const lineDisplayNames = workspaceConfig.lineDisplayNames[lineId];
5634
+ if (lineDisplayNames[workspaceId]) {
5635
+ return lineDisplayNames[workspaceId];
5636
+ }
5637
+ }
5625
5638
  const displayNames = workspaceConfig?.displayNames || FALLBACK_DISPLAY_NAMES;
5626
5639
  return displayNames[workspaceId] || workspaceId.replace(/^WS/i, "") || workspaceId;
5627
5640
  };
5628
- var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5629
- const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig);
5641
+ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5642
+ const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig, lineId);
5630
5643
  const match = fullName.match(/([A-Z]\d+(?:-\w+)?)/i);
5631
5644
  if (match && match[0]) {
5632
5645
  return match[0];
@@ -17631,18 +17644,6 @@ var VideoCard = React14__namespace.default.memo(({
17631
17644
  return prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.workspace_name === nextProps.workspace.workspace_name && Math.abs(prevProps.workspace.efficiency - nextProps.workspace.efficiency) < 1 && prevProps.hlsUrl === nextProps.hlsUrl && prevProps.shouldPlay === nextProps.shouldPlay && prevProps.cropping?.x === nextProps.cropping?.x && prevProps.cropping?.y === nextProps.cropping?.y && prevProps.cropping?.width === nextProps.cropping?.width && prevProps.cropping?.height === nextProps.cropping?.height;
17632
17645
  });
17633
17646
  VideoCard.displayName = "VideoCard";
17634
- var DEFAULT_WORKSPACE_HLS_URLS = {
17635
- "WS1": "https://dnh-hls.optifye.ai/cam1/index.m3u8",
17636
- "WS2": "https://dnh-hls.optifye.ai/cam2/index.m3u8",
17637
- "WS3": "https://dnh-hls.optifye.ai/cam3/index.m3u8",
17638
- "WS4": "https://dnh-hls.optifye.ai/cam3/index.m3u8",
17639
- "WS01": "https://59.144.218.58:8443/camera6.m3u8",
17640
- "WS02": "https://59.144.218.58:8443/camera2.m3u8",
17641
- "WS03": "https://59.144.218.58:8443/camera3.m3u8",
17642
- "WS04": "https://59.144.218.58:8443/camera4.m3u8",
17643
- "WS05": "https://59.144.218.58:8443/camera1.m3u8",
17644
- "WS06": "https://59.144.218.58:8443/camera5.m3u8"
17645
- };
17646
17647
  var DEFAULT_HLS_URL = "https://192.168.5.9:8443/cam1.m3u8";
17647
17648
  var VideoGridView = React14__namespace.default.memo(({
17648
17649
  workspaces,
@@ -17656,13 +17657,20 @@ var VideoGridView = React14__namespace.default.memo(({
17656
17657
  const [gridCols, setGridCols] = React14.useState(4);
17657
17658
  const [visibleWorkspaces, setVisibleWorkspaces] = React14.useState(/* @__PURE__ */ new Set());
17658
17659
  const videoConfig = useVideoConfig();
17659
- const { cropping, canvasConfig } = videoConfig;
17660
+ const { cropping, canvasConfig, hlsUrls } = videoConfig;
17660
17661
  const mergedVideoSources = {
17661
- defaultHlsUrl: videoSources.defaultHlsUrl || DEFAULT_HLS_URL,
17662
- workspaceHlsUrls: { ...DEFAULT_WORKSPACE_HLS_URLS, ...videoSources.workspaceHlsUrls }
17662
+ defaultHlsUrl: videoSources.defaultHlsUrl || hlsUrls?.defaultHlsUrl || DEFAULT_HLS_URL,
17663
+ workspaceHlsUrls: { ...videoSources.workspaceHlsUrls, ...hlsUrls?.workspaceHlsUrls },
17664
+ lineWorkspaceHlsUrls: hlsUrls?.lineWorkspaceHlsUrls || {}
17663
17665
  };
17664
- const getWorkspaceHlsUrl = React14.useCallback((workspaceName) => {
17666
+ const getWorkspaceHlsUrl = React14.useCallback((workspaceName, lineId) => {
17665
17667
  const wsName = workspaceName.toUpperCase();
17668
+ if (lineId && mergedVideoSources.lineWorkspaceHlsUrls[lineId]) {
17669
+ const lineUrls = mergedVideoSources.lineWorkspaceHlsUrls[lineId];
17670
+ if (lineUrls[wsName]) {
17671
+ return lineUrls[wsName];
17672
+ }
17673
+ }
17666
17674
  return mergedVideoSources.workspaceHlsUrls[wsName] || mergedVideoSources.defaultHlsUrl;
17667
17675
  }, [mergedVideoSources]);
17668
17676
  const getWorkspaceCropping = React14.useCallback((workspaceName) => {
@@ -17822,7 +17830,7 @@ var VideoGridView = React14__namespace.default.memo(({
17822
17830
  VideoCard,
17823
17831
  {
17824
17832
  workspace,
17825
- hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name),
17833
+ hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id),
17826
17834
  shouldPlay: isVisible,
17827
17835
  onClick: () => handleWorkspaceClick(workspace),
17828
17836
  onFatalError: throttledReloadDashboard,
@@ -21546,6 +21554,11 @@ var S3ClipsService = class {
21546
21554
  if (!config.s3Config) {
21547
21555
  throw new Error("S3 configuration is required");
21548
21556
  }
21557
+ const processing = config.s3Config.processing || {};
21558
+ this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
21559
+ this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
21560
+ this.concurrencyLimit = processing.concurrencyLimit || 10;
21561
+ this.maxInitialFetch = processing.maxInitialFetch || 60;
21549
21562
  const region = this.validateAndSanitizeRegion(config.s3Config.region);
21550
21563
  console.log(`S3ClipsService: Using AWS region: ${region}`);
21551
21564
  this.s3Client = new clientS3.S3Client({
@@ -21830,9 +21843,9 @@ var S3ClipsService = class {
21830
21843
  }
21831
21844
  return summary;
21832
21845
  }
21833
- const limitPerCategory = limit ? Math.min(Math.max(limit, 1), 1e3) : 30;
21846
+ const limitPerCategory = limit ? Math.min(Math.max(limit, 1), this.maxLimitPerCategory) : this.defaultLimitPerCategory;
21834
21847
  const shouldFetchAll = category === "missing_quality_check" || category === "low_value";
21835
- const initialFetchLimit = shouldFetchAll ? void 0 : category ? limitPerCategory * 3 : void 0;
21848
+ const initialFetchLimit = shouldFetchAll ? void 0 : category ? Math.min(limitPerCategory * 3, this.maxInitialFetch) : void 0;
21836
21849
  const s3Uris = await this.listS3Clips({ workspaceId, date, shiftId, maxKeys: initialFetchLimit });
21837
21850
  if (s3Uris.length === 0) {
21838
21851
  console.log(`S3ClipsService: No HLS playlists found for workspace ${workspaceId} on date ${date}, shift ${shiftId}`);
@@ -21900,12 +21913,11 @@ var S3ClipsService = class {
21900
21913
  }
21901
21914
  console.log(`S3ClipsService: Total filtered URIs across all categories: ${filteredUris.length}`);
21902
21915
  }
21903
- const concurrencyLimit = 10;
21904
21916
  let processedCount = 0;
21905
21917
  const videoResults = [];
21906
21918
  console.log(`S3ClipsService: Processing ${filteredUris.length} URIs for ${category || "all categories"} with limit ${limitPerCategory} per category`);
21907
- for (let i = 0; i < filteredUris.length; i += concurrencyLimit) {
21908
- const batch = filteredUris.slice(i, i + concurrencyLimit);
21919
+ for (let i = 0; i < filteredUris.length; i += this.concurrencyLimit) {
21920
+ const batch = filteredUris.slice(i, i + this.concurrencyLimit);
21909
21921
  const batchPromises = batch.map(async (uri, batchIndex) => {
21910
21922
  const index = i + batchIndex;
21911
21923
  const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false, includeMetadata || (!!timestampStart || !!timestampEnd));
@@ -22468,7 +22480,7 @@ var BottlenecksContent = ({
22468
22480
  return "Cycle Completion";
22469
22481
  case "bottleneck":
22470
22482
  default:
22471
- return "Bottleneck";
22483
+ return "";
22472
22484
  }
22473
22485
  };
22474
22486
  const getColorClasses = (color2) => {
@@ -27386,11 +27398,17 @@ function HomeView({
27386
27398
  lineId: selectedLineId,
27387
27399
  onLineMetricsUpdate
27388
27400
  });
27401
+ const lineIdsForBreaks = React14.useMemo(() => {
27402
+ if (selectedLineId === factoryViewId) {
27403
+ return allLineIds;
27404
+ }
27405
+ return [selectedLineId];
27406
+ }, [selectedLineId, factoryViewId, allLineIds]);
27389
27407
  const {
27390
27408
  activeBreaks,
27391
27409
  isLoading: breaksLoading,
27392
27410
  error: breaksError
27393
- } = useActiveBreaks([selectedLineId]);
27411
+ } = useActiveBreaks(lineIdsForBreaks);
27394
27412
  const memoizedWorkspaceMetrics = React14.useMemo(() => workspaceMetrics, [
27395
27413
  // Only update reference if meaningful properties change
27396
27414
  workspaceMetrics.length,
@@ -28657,8 +28675,7 @@ var MobileWorkspaceCard = React14.memo(({
28657
28675
  cardClass,
28658
28676
  onWorkspaceClick,
28659
28677
  getMedalIcon,
28660
- line1Id,
28661
- line2Id
28678
+ getLineName
28662
28679
  }) => /* @__PURE__ */ jsxRuntime.jsxs(
28663
28680
  "div",
28664
28681
  {
@@ -28676,7 +28693,7 @@ var MobileWorkspaceCard = React14.memo(({
28676
28693
  ] }),
28677
28694
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
28678
28695
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-semibold text-gray-900", children: getWorkspaceDisplayName(workspace.workspace_name) }),
28679
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: workspace.line_id === line1Id ? "Line 1" : workspace.line_id === line2Id ? "Line 2" : workspace.line_id })
28696
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: getLineName(workspace.line_id) })
28680
28697
  ] })
28681
28698
  ] }),
28682
28699
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right", children: [
@@ -28714,8 +28731,7 @@ var DesktopWorkspaceRow = React14.memo(({
28714
28731
  rowClass,
28715
28732
  onWorkspaceClick,
28716
28733
  getMedalIcon,
28717
- line1Id,
28718
- line2Id
28734
+ getLineName
28719
28735
  }) => /* @__PURE__ */ jsxRuntime.jsxs(
28720
28736
  "tr",
28721
28737
  {
@@ -28727,7 +28743,7 @@ var DesktopWorkspaceRow = React14.memo(({
28727
28743
  getMedalIcon(index + 1)
28728
28744
  ] }) }),
28729
28745
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: getWorkspaceDisplayName(workspace.workspace_name) }) }),
28730
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: workspace.line_id === line1Id ? "Line 1" : workspace.line_id === line2Id ? "Line 2" : workspace.line_id }) }),
28746
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: getLineName(workspace.line_id) }) }),
28731
28747
  /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base font-medium whitespace-nowrap", children: [
28732
28748
  (workspace.efficiency || 0).toFixed(1),
28733
28749
  "%"
@@ -28755,10 +28771,19 @@ var LeaderboardDetailView = React14.memo(({
28755
28771
  onWorkspaceClick,
28756
28772
  line1Id = "",
28757
28773
  line2Id = "",
28774
+ lineNames = {},
28758
28775
  className = ""
28759
28776
  }) => {
28760
28777
  const navigation = useNavigation();
28761
28778
  const [sortAscending, setSortAscending] = React14.useState(false);
28779
+ const getLineName = React14.useCallback((lineId2) => {
28780
+ if (lineNames[lineId2]) {
28781
+ return lineNames[lineId2];
28782
+ }
28783
+ if (lineId2 === line1Id) return "Line 1";
28784
+ if (lineId2 === line2Id) return "Line 2";
28785
+ return lineId2;
28786
+ }, [lineNames, line1Id, line2Id]);
28762
28787
  const handleSortToggle = React14.useCallback(() => {
28763
28788
  setSortAscending(!sortAscending);
28764
28789
  }, [sortAscending]);
@@ -28932,8 +28957,7 @@ var LeaderboardDetailView = React14.memo(({
28932
28957
  cardClass,
28933
28958
  onWorkspaceClick: handleWorkspaceClick,
28934
28959
  getMedalIcon,
28935
- line1Id,
28936
- line2Id
28960
+ getLineName
28937
28961
  },
28938
28962
  ws.workspace_uuid
28939
28963
  );
@@ -28958,8 +28982,7 @@ var LeaderboardDetailView = React14.memo(({
28958
28982
  rowClass,
28959
28983
  onWorkspaceClick: handleWorkspaceClick,
28960
28984
  getMedalIcon,
28961
- line1Id,
28962
- line2Id
28985
+ getLineName
28963
28986
  },
28964
28987
  ws.workspace_uuid
28965
28988
  );
@@ -28968,7 +28991,7 @@ var LeaderboardDetailView = React14.memo(({
28968
28991
  ] })
28969
28992
  ] });
28970
28993
  }, (prevProps, nextProps) => {
28971
- return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && prevProps.className === nextProps.className && prevProps.onBackClick === nextProps.onBackClick && prevProps.onWorkspaceClick === nextProps.onWorkspaceClick;
28994
+ return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && JSON.stringify(prevProps.lineNames) === JSON.stringify(nextProps.lineNames) && prevProps.className === nextProps.className && prevProps.onBackClick === nextProps.onBackClick && prevProps.onWorkspaceClick === nextProps.onWorkspaceClick;
28972
28995
  });
28973
28996
  LeaderboardDetailView.displayName = "LeaderboardDetailView";
28974
28997
  var LeaderboardDetailView_default = LeaderboardDetailView;
package/dist/index.mjs CHANGED
@@ -132,6 +132,9 @@ var DEFAULT_AUTH_CONFIG = {
132
132
  // Defaults related to auth providers, redirects etc.
133
133
  };
134
134
  var DEFAULT_VIDEO_CONFIG = {
135
+ hlsUrls: {
136
+ lineWorkspaceHlsUrls: {}
137
+ },
135
138
  canvasConfig: {
136
139
  fps: 30,
137
140
  useRAF: true
@@ -5096,19 +5099,23 @@ var useActiveBreaks = (lineIds) => {
5096
5099
  const checkActiveBreaks = useCallback(async () => {
5097
5100
  try {
5098
5101
  setError(null);
5099
- if (!lineIds || lineIds.length === 0) {
5102
+ const validLineIds = lineIds.filter(
5103
+ (id3) => id3 && id3 !== "factory" && id3 !== "all" && // Basic UUID format check
5104
+ id3.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
5105
+ );
5106
+ if (!validLineIds || validLineIds.length === 0) {
5100
5107
  setActiveBreaks([]);
5101
5108
  setIsLoading(false);
5102
5109
  return;
5103
5110
  }
5104
5111
  const currentMinutes = getCurrentTimeInMinutes();
5105
- const { data: dayShifts, error: dayError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 0).in("line_id", lineIds);
5106
- const { data: nightShifts, error: nightError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 1).in("line_id", lineIds);
5112
+ const { data: dayShifts, error: dayError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 0).in("line_id", validLineIds);
5113
+ const { data: nightShifts, error: nightError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 1).in("line_id", validLineIds);
5107
5114
  if (dayError || nightError) {
5108
5115
  throw new Error("Failed to fetch shift configurations");
5109
5116
  }
5110
5117
  const foundActiveBreaks = [];
5111
- for (const lineId of lineIds) {
5118
+ for (const lineId of validLineIds) {
5112
5119
  const dayShift = dayShifts?.find((s) => s.line_id === lineId);
5113
5120
  const nightShift = nightShifts?.find((s) => s.line_id === lineId);
5114
5121
  if (!dayShift || !nightShift) continue;
@@ -5592,12 +5599,18 @@ var FALLBACK_DISPLAY_NAMES = {
5592
5599
  "WS03": "Filling station"
5593
5600
  // ... Add others if known defaults are useful
5594
5601
  };
5595
- var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5602
+ var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5603
+ if (lineId && workspaceConfig?.lineDisplayNames?.[lineId]) {
5604
+ const lineDisplayNames = workspaceConfig.lineDisplayNames[lineId];
5605
+ if (lineDisplayNames[workspaceId]) {
5606
+ return lineDisplayNames[workspaceId];
5607
+ }
5608
+ }
5596
5609
  const displayNames = workspaceConfig?.displayNames || FALLBACK_DISPLAY_NAMES;
5597
5610
  return displayNames[workspaceId] || workspaceId.replace(/^WS/i, "") || workspaceId;
5598
5611
  };
5599
- var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5600
- const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig);
5612
+ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5613
+ const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig, lineId);
5601
5614
  const match = fullName.match(/([A-Z]\d+(?:-\w+)?)/i);
5602
5615
  if (match && match[0]) {
5603
5616
  return match[0];
@@ -17602,18 +17615,6 @@ var VideoCard = React14__default.memo(({
17602
17615
  return prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.workspace_name === nextProps.workspace.workspace_name && Math.abs(prevProps.workspace.efficiency - nextProps.workspace.efficiency) < 1 && prevProps.hlsUrl === nextProps.hlsUrl && prevProps.shouldPlay === nextProps.shouldPlay && prevProps.cropping?.x === nextProps.cropping?.x && prevProps.cropping?.y === nextProps.cropping?.y && prevProps.cropping?.width === nextProps.cropping?.width && prevProps.cropping?.height === nextProps.cropping?.height;
17603
17616
  });
17604
17617
  VideoCard.displayName = "VideoCard";
17605
- var DEFAULT_WORKSPACE_HLS_URLS = {
17606
- "WS1": "https://dnh-hls.optifye.ai/cam1/index.m3u8",
17607
- "WS2": "https://dnh-hls.optifye.ai/cam2/index.m3u8",
17608
- "WS3": "https://dnh-hls.optifye.ai/cam3/index.m3u8",
17609
- "WS4": "https://dnh-hls.optifye.ai/cam3/index.m3u8",
17610
- "WS01": "https://59.144.218.58:8443/camera6.m3u8",
17611
- "WS02": "https://59.144.218.58:8443/camera2.m3u8",
17612
- "WS03": "https://59.144.218.58:8443/camera3.m3u8",
17613
- "WS04": "https://59.144.218.58:8443/camera4.m3u8",
17614
- "WS05": "https://59.144.218.58:8443/camera1.m3u8",
17615
- "WS06": "https://59.144.218.58:8443/camera5.m3u8"
17616
- };
17617
17618
  var DEFAULT_HLS_URL = "https://192.168.5.9:8443/cam1.m3u8";
17618
17619
  var VideoGridView = React14__default.memo(({
17619
17620
  workspaces,
@@ -17627,13 +17628,20 @@ var VideoGridView = React14__default.memo(({
17627
17628
  const [gridCols, setGridCols] = useState(4);
17628
17629
  const [visibleWorkspaces, setVisibleWorkspaces] = useState(/* @__PURE__ */ new Set());
17629
17630
  const videoConfig = useVideoConfig();
17630
- const { cropping, canvasConfig } = videoConfig;
17631
+ const { cropping, canvasConfig, hlsUrls } = videoConfig;
17631
17632
  const mergedVideoSources = {
17632
- defaultHlsUrl: videoSources.defaultHlsUrl || DEFAULT_HLS_URL,
17633
- workspaceHlsUrls: { ...DEFAULT_WORKSPACE_HLS_URLS, ...videoSources.workspaceHlsUrls }
17633
+ defaultHlsUrl: videoSources.defaultHlsUrl || hlsUrls?.defaultHlsUrl || DEFAULT_HLS_URL,
17634
+ workspaceHlsUrls: { ...videoSources.workspaceHlsUrls, ...hlsUrls?.workspaceHlsUrls },
17635
+ lineWorkspaceHlsUrls: hlsUrls?.lineWorkspaceHlsUrls || {}
17634
17636
  };
17635
- const getWorkspaceHlsUrl = useCallback((workspaceName) => {
17637
+ const getWorkspaceHlsUrl = useCallback((workspaceName, lineId) => {
17636
17638
  const wsName = workspaceName.toUpperCase();
17639
+ if (lineId && mergedVideoSources.lineWorkspaceHlsUrls[lineId]) {
17640
+ const lineUrls = mergedVideoSources.lineWorkspaceHlsUrls[lineId];
17641
+ if (lineUrls[wsName]) {
17642
+ return lineUrls[wsName];
17643
+ }
17644
+ }
17637
17645
  return mergedVideoSources.workspaceHlsUrls[wsName] || mergedVideoSources.defaultHlsUrl;
17638
17646
  }, [mergedVideoSources]);
17639
17647
  const getWorkspaceCropping = useCallback((workspaceName) => {
@@ -17793,7 +17801,7 @@ var VideoGridView = React14__default.memo(({
17793
17801
  VideoCard,
17794
17802
  {
17795
17803
  workspace,
17796
- hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name),
17804
+ hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id),
17797
17805
  shouldPlay: isVisible,
17798
17806
  onClick: () => handleWorkspaceClick(workspace),
17799
17807
  onFatalError: throttledReloadDashboard,
@@ -21517,6 +21525,11 @@ var S3ClipsService = class {
21517
21525
  if (!config.s3Config) {
21518
21526
  throw new Error("S3 configuration is required");
21519
21527
  }
21528
+ const processing = config.s3Config.processing || {};
21529
+ this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
21530
+ this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
21531
+ this.concurrencyLimit = processing.concurrencyLimit || 10;
21532
+ this.maxInitialFetch = processing.maxInitialFetch || 60;
21520
21533
  const region = this.validateAndSanitizeRegion(config.s3Config.region);
21521
21534
  console.log(`S3ClipsService: Using AWS region: ${region}`);
21522
21535
  this.s3Client = new S3Client({
@@ -21801,9 +21814,9 @@ var S3ClipsService = class {
21801
21814
  }
21802
21815
  return summary;
21803
21816
  }
21804
- const limitPerCategory = limit ? Math.min(Math.max(limit, 1), 1e3) : 30;
21817
+ const limitPerCategory = limit ? Math.min(Math.max(limit, 1), this.maxLimitPerCategory) : this.defaultLimitPerCategory;
21805
21818
  const shouldFetchAll = category === "missing_quality_check" || category === "low_value";
21806
- const initialFetchLimit = shouldFetchAll ? void 0 : category ? limitPerCategory * 3 : void 0;
21819
+ const initialFetchLimit = shouldFetchAll ? void 0 : category ? Math.min(limitPerCategory * 3, this.maxInitialFetch) : void 0;
21807
21820
  const s3Uris = await this.listS3Clips({ workspaceId, date, shiftId, maxKeys: initialFetchLimit });
21808
21821
  if (s3Uris.length === 0) {
21809
21822
  console.log(`S3ClipsService: No HLS playlists found for workspace ${workspaceId} on date ${date}, shift ${shiftId}`);
@@ -21871,12 +21884,11 @@ var S3ClipsService = class {
21871
21884
  }
21872
21885
  console.log(`S3ClipsService: Total filtered URIs across all categories: ${filteredUris.length}`);
21873
21886
  }
21874
- const concurrencyLimit = 10;
21875
21887
  let processedCount = 0;
21876
21888
  const videoResults = [];
21877
21889
  console.log(`S3ClipsService: Processing ${filteredUris.length} URIs for ${category || "all categories"} with limit ${limitPerCategory} per category`);
21878
- for (let i = 0; i < filteredUris.length; i += concurrencyLimit) {
21879
- const batch = filteredUris.slice(i, i + concurrencyLimit);
21890
+ for (let i = 0; i < filteredUris.length; i += this.concurrencyLimit) {
21891
+ const batch = filteredUris.slice(i, i + this.concurrencyLimit);
21880
21892
  const batchPromises = batch.map(async (uri, batchIndex) => {
21881
21893
  const index = i + batchIndex;
21882
21894
  const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false, includeMetadata || (!!timestampStart || !!timestampEnd));
@@ -22439,7 +22451,7 @@ var BottlenecksContent = ({
22439
22451
  return "Cycle Completion";
22440
22452
  case "bottleneck":
22441
22453
  default:
22442
- return "Bottleneck";
22454
+ return "";
22443
22455
  }
22444
22456
  };
22445
22457
  const getColorClasses = (color2) => {
@@ -27357,11 +27369,17 @@ function HomeView({
27357
27369
  lineId: selectedLineId,
27358
27370
  onLineMetricsUpdate
27359
27371
  });
27372
+ const lineIdsForBreaks = useMemo(() => {
27373
+ if (selectedLineId === factoryViewId) {
27374
+ return allLineIds;
27375
+ }
27376
+ return [selectedLineId];
27377
+ }, [selectedLineId, factoryViewId, allLineIds]);
27360
27378
  const {
27361
27379
  activeBreaks,
27362
27380
  isLoading: breaksLoading,
27363
27381
  error: breaksError
27364
- } = useActiveBreaks([selectedLineId]);
27382
+ } = useActiveBreaks(lineIdsForBreaks);
27365
27383
  const memoizedWorkspaceMetrics = useMemo(() => workspaceMetrics, [
27366
27384
  // Only update reference if meaningful properties change
27367
27385
  workspaceMetrics.length,
@@ -28628,8 +28646,7 @@ var MobileWorkspaceCard = memo(({
28628
28646
  cardClass,
28629
28647
  onWorkspaceClick,
28630
28648
  getMedalIcon,
28631
- line1Id,
28632
- line2Id
28649
+ getLineName
28633
28650
  }) => /* @__PURE__ */ jsxs(
28634
28651
  "div",
28635
28652
  {
@@ -28647,7 +28664,7 @@ var MobileWorkspaceCard = memo(({
28647
28664
  ] }),
28648
28665
  /* @__PURE__ */ jsxs("div", { children: [
28649
28666
  /* @__PURE__ */ jsx("div", { className: "font-semibold text-gray-900", children: getWorkspaceDisplayName(workspace.workspace_name) }),
28650
- /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: workspace.line_id === line1Id ? "Line 1" : workspace.line_id === line2Id ? "Line 2" : workspace.line_id })
28667
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: getLineName(workspace.line_id) })
28651
28668
  ] })
28652
28669
  ] }),
28653
28670
  /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
@@ -28685,8 +28702,7 @@ var DesktopWorkspaceRow = memo(({
28685
28702
  rowClass,
28686
28703
  onWorkspaceClick,
28687
28704
  getMedalIcon,
28688
- line1Id,
28689
- line2Id
28705
+ getLineName
28690
28706
  }) => /* @__PURE__ */ jsxs(
28691
28707
  "tr",
28692
28708
  {
@@ -28698,7 +28714,7 @@ var DesktopWorkspaceRow = memo(({
28698
28714
  getMedalIcon(index + 1)
28699
28715
  ] }) }),
28700
28716
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap", children: /* @__PURE__ */ jsx("div", { className: "font-medium", children: getWorkspaceDisplayName(workspace.workspace_name) }) }),
28701
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap", children: /* @__PURE__ */ jsx("div", { className: "font-medium", children: workspace.line_id === line1Id ? "Line 1" : workspace.line_id === line2Id ? "Line 2" : workspace.line_id }) }),
28717
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base whitespace-nowrap", children: /* @__PURE__ */ jsx("div", { className: "font-medium", children: getLineName(workspace.line_id) }) }),
28702
28718
  /* @__PURE__ */ jsxs("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base font-medium whitespace-nowrap", children: [
28703
28719
  (workspace.efficiency || 0).toFixed(1),
28704
28720
  "%"
@@ -28726,10 +28742,19 @@ var LeaderboardDetailView = memo(({
28726
28742
  onWorkspaceClick,
28727
28743
  line1Id = "",
28728
28744
  line2Id = "",
28745
+ lineNames = {},
28729
28746
  className = ""
28730
28747
  }) => {
28731
28748
  const navigation = useNavigation();
28732
28749
  const [sortAscending, setSortAscending] = useState(false);
28750
+ const getLineName = useCallback((lineId2) => {
28751
+ if (lineNames[lineId2]) {
28752
+ return lineNames[lineId2];
28753
+ }
28754
+ if (lineId2 === line1Id) return "Line 1";
28755
+ if (lineId2 === line2Id) return "Line 2";
28756
+ return lineId2;
28757
+ }, [lineNames, line1Id, line2Id]);
28733
28758
  const handleSortToggle = useCallback(() => {
28734
28759
  setSortAscending(!sortAscending);
28735
28760
  }, [sortAscending]);
@@ -28903,8 +28928,7 @@ var LeaderboardDetailView = memo(({
28903
28928
  cardClass,
28904
28929
  onWorkspaceClick: handleWorkspaceClick,
28905
28930
  getMedalIcon,
28906
- line1Id,
28907
- line2Id
28931
+ getLineName
28908
28932
  },
28909
28933
  ws.workspace_uuid
28910
28934
  );
@@ -28929,8 +28953,7 @@ var LeaderboardDetailView = memo(({
28929
28953
  rowClass,
28930
28954
  onWorkspaceClick: handleWorkspaceClick,
28931
28955
  getMedalIcon,
28932
- line1Id,
28933
- line2Id
28956
+ getLineName
28934
28957
  },
28935
28958
  ws.workspace_uuid
28936
28959
  );
@@ -28939,7 +28962,7 @@ var LeaderboardDetailView = memo(({
28939
28962
  ] })
28940
28963
  ] });
28941
28964
  }, (prevProps, nextProps) => {
28942
- return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && prevProps.className === nextProps.className && prevProps.onBackClick === nextProps.onBackClick && prevProps.onWorkspaceClick === nextProps.onWorkspaceClick;
28965
+ return prevProps.lineId === nextProps.lineId && prevProps.date === nextProps.date && prevProps.shift === nextProps.shift && prevProps.line1Id === nextProps.line1Id && prevProps.line2Id === nextProps.line2Id && JSON.stringify(prevProps.lineNames) === JSON.stringify(nextProps.lineNames) && prevProps.className === nextProps.className && prevProps.onBackClick === nextProps.onBackClick && prevProps.onWorkspaceClick === nextProps.onWorkspaceClick;
28943
28966
  });
28944
28967
  LeaderboardDetailView.displayName = "LeaderboardDetailView";
28945
28968
  var LeaderboardDetailView_default = LeaderboardDetailView;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",