@optifye/dashboard-core 6.0.1 → 6.0.3

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
@@ -890,17 +893,18 @@ var dashboardService = {
890
893
  const queryShiftId = shiftProp !== void 0 ? shiftProp : currentShiftResult.shiftId;
891
894
  try {
892
895
  if (lineIdToQuery === factoryViewId) {
893
- if (!defaultLineId || !secondaryLineId || !companyId) {
894
- throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
896
+ if (!defaultLineId || !companyId) {
897
+ throw new Error("Factory View requires at least defaultLineId and companyId to be configured.");
895
898
  }
896
899
  const { data: line1Data, error: line1Error } = await supabase.from(linesTable).select("id, line_name, factory_id, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", defaultLineId).single();
897
900
  if (line1Error) throw line1Error;
898
901
  if (!line1Data) {
899
902
  throw new Error(`Default line ${defaultLineId} for Factory View not found.`);
900
903
  }
901
- const { data: metricsData, error: metricsError2 } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", queryShiftId).eq("date", queryDate);
904
+ const lineIdsToQuery = [defaultLineId, secondaryLineId].filter((id3) => id3 !== void 0 && id3 !== null);
905
+ const { data: metricsData, error: metricsError2 } = await supabase.from(lineMetricsTable).select("*").in("line_id", lineIdsToQuery).eq("shift_id", queryShiftId).eq("date", queryDate);
902
906
  if (metricsError2) throw metricsError2;
903
- const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", queryShiftId).eq("date", queryDate);
907
+ const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", lineIdsToQuery).eq("shift_id", queryShiftId).eq("date", queryDate);
904
908
  if (performanceError2) throw performanceError2;
905
909
  const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
906
910
  const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
@@ -5125,19 +5129,23 @@ var useActiveBreaks = (lineIds) => {
5125
5129
  const checkActiveBreaks = React14.useCallback(async () => {
5126
5130
  try {
5127
5131
  setError(null);
5128
- if (!lineIds || lineIds.length === 0) {
5132
+ const validLineIds = lineIds.filter(
5133
+ (id3) => id3 && id3 !== "factory" && id3 !== "all" && // Basic UUID format check
5134
+ id3.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
5135
+ );
5136
+ if (!validLineIds || validLineIds.length === 0) {
5129
5137
  setActiveBreaks([]);
5130
5138
  setIsLoading(false);
5131
5139
  return;
5132
5140
  }
5133
5141
  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);
5142
+ 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);
5143
+ 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
5144
  if (dayError || nightError) {
5137
5145
  throw new Error("Failed to fetch shift configurations");
5138
5146
  }
5139
5147
  const foundActiveBreaks = [];
5140
- for (const lineId of lineIds) {
5148
+ for (const lineId of validLineIds) {
5141
5149
  const dayShift = dayShifts?.find((s) => s.line_id === lineId);
5142
5150
  const nightShift = nightShifts?.find((s) => s.line_id === lineId);
5143
5151
  if (!dayShift || !nightShift) continue;
@@ -5621,12 +5629,18 @@ var FALLBACK_DISPLAY_NAMES = {
5621
5629
  "WS03": "Filling station"
5622
5630
  // ... Add others if known defaults are useful
5623
5631
  };
5624
- var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5632
+ var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5633
+ if (lineId && workspaceConfig?.lineDisplayNames?.[lineId]) {
5634
+ const lineDisplayNames = workspaceConfig.lineDisplayNames[lineId];
5635
+ if (lineDisplayNames[workspaceId]) {
5636
+ return lineDisplayNames[workspaceId];
5637
+ }
5638
+ }
5625
5639
  const displayNames = workspaceConfig?.displayNames || FALLBACK_DISPLAY_NAMES;
5626
5640
  return displayNames[workspaceId] || workspaceId.replace(/^WS/i, "") || workspaceId;
5627
5641
  };
5628
- var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5629
- const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig);
5642
+ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5643
+ const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig, lineId);
5630
5644
  const match = fullName.match(/([A-Z]\d+(?:-\w+)?)/i);
5631
5645
  if (match && match[0]) {
5632
5646
  return match[0];
@@ -17631,18 +17645,6 @@ var VideoCard = React14__namespace.default.memo(({
17631
17645
  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
17646
  });
17633
17647
  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
17648
  var DEFAULT_HLS_URL = "https://192.168.5.9:8443/cam1.m3u8";
17647
17649
  var VideoGridView = React14__namespace.default.memo(({
17648
17650
  workspaces,
@@ -17656,13 +17658,20 @@ var VideoGridView = React14__namespace.default.memo(({
17656
17658
  const [gridCols, setGridCols] = React14.useState(4);
17657
17659
  const [visibleWorkspaces, setVisibleWorkspaces] = React14.useState(/* @__PURE__ */ new Set());
17658
17660
  const videoConfig = useVideoConfig();
17659
- const { cropping, canvasConfig } = videoConfig;
17661
+ const { cropping, canvasConfig, hlsUrls } = videoConfig;
17660
17662
  const mergedVideoSources = {
17661
- defaultHlsUrl: videoSources.defaultHlsUrl || DEFAULT_HLS_URL,
17662
- workspaceHlsUrls: { ...DEFAULT_WORKSPACE_HLS_URLS, ...videoSources.workspaceHlsUrls }
17663
+ defaultHlsUrl: videoSources.defaultHlsUrl || hlsUrls?.defaultHlsUrl || DEFAULT_HLS_URL,
17664
+ workspaceHlsUrls: { ...videoSources.workspaceHlsUrls, ...hlsUrls?.workspaceHlsUrls },
17665
+ lineWorkspaceHlsUrls: hlsUrls?.lineWorkspaceHlsUrls || {}
17663
17666
  };
17664
- const getWorkspaceHlsUrl = React14.useCallback((workspaceName) => {
17667
+ const getWorkspaceHlsUrl = React14.useCallback((workspaceName, lineId) => {
17665
17668
  const wsName = workspaceName.toUpperCase();
17669
+ if (lineId && mergedVideoSources.lineWorkspaceHlsUrls[lineId]) {
17670
+ const lineUrls = mergedVideoSources.lineWorkspaceHlsUrls[lineId];
17671
+ if (lineUrls[wsName]) {
17672
+ return lineUrls[wsName];
17673
+ }
17674
+ }
17666
17675
  return mergedVideoSources.workspaceHlsUrls[wsName] || mergedVideoSources.defaultHlsUrl;
17667
17676
  }, [mergedVideoSources]);
17668
17677
  const getWorkspaceCropping = React14.useCallback((workspaceName) => {
@@ -17822,7 +17831,7 @@ var VideoGridView = React14__namespace.default.memo(({
17822
17831
  VideoCard,
17823
17832
  {
17824
17833
  workspace,
17825
- hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name),
17834
+ hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id),
17826
17835
  shouldPlay: isVisible,
17827
17836
  onClick: () => handleWorkspaceClick(workspace),
17828
17837
  onFatalError: throttledReloadDashboard,
@@ -21475,7 +21484,7 @@ function parseS3Uri(s3Uri, sopCategories) {
21475
21484
  break;
21476
21485
  case "long_cycle_time":
21477
21486
  severity = "high";
21478
- type = "bottleneck";
21487
+ type = "long_cycle_time";
21479
21488
  description = "Long Cycle Time Detected";
21480
21489
  break;
21481
21490
  case "best_cycle_time":
@@ -21519,7 +21528,7 @@ function parseS3Uri(s3Uri, sopCategories) {
21519
21528
  severity = "low";
21520
21529
  description = "Best Cycle Time Performance";
21521
21530
  } else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
21522
- type = "bottleneck";
21531
+ type = "long_cycle_time";
21523
21532
  severity = "high";
21524
21533
  description = "Long Cycle Time Detected";
21525
21534
  } else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
@@ -21546,6 +21555,11 @@ var S3ClipsService = class {
21546
21555
  if (!config.s3Config) {
21547
21556
  throw new Error("S3 configuration is required");
21548
21557
  }
21558
+ const processing = config.s3Config.processing || {};
21559
+ this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
21560
+ this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
21561
+ this.concurrencyLimit = processing.concurrencyLimit || 10;
21562
+ this.maxInitialFetch = processing.maxInitialFetch || 60;
21549
21563
  const region = this.validateAndSanitizeRegion(config.s3Config.region);
21550
21564
  console.log(`S3ClipsService: Using AWS region: ${region}`);
21551
21565
  this.s3Client = new clientS3.S3Client({
@@ -21830,9 +21844,9 @@ var S3ClipsService = class {
21830
21844
  }
21831
21845
  return summary;
21832
21846
  }
21833
- const limitPerCategory = limit ? Math.min(Math.max(limit, 1), 1e3) : 30;
21847
+ const limitPerCategory = limit ? Math.min(Math.max(limit, 1), this.maxLimitPerCategory) : this.defaultLimitPerCategory;
21834
21848
  const shouldFetchAll = category === "missing_quality_check" || category === "low_value";
21835
- const initialFetchLimit = shouldFetchAll ? void 0 : category ? limitPerCategory * 3 : void 0;
21849
+ const initialFetchLimit = shouldFetchAll ? void 0 : category ? Math.min(limitPerCategory * 3, this.maxInitialFetch) : void 0;
21836
21850
  const s3Uris = await this.listS3Clips({ workspaceId, date, shiftId, maxKeys: initialFetchLimit });
21837
21851
  if (s3Uris.length === 0) {
21838
21852
  console.log(`S3ClipsService: No HLS playlists found for workspace ${workspaceId} on date ${date}, shift ${shiftId}`);
@@ -21900,12 +21914,11 @@ var S3ClipsService = class {
21900
21914
  }
21901
21915
  console.log(`S3ClipsService: Total filtered URIs across all categories: ${filteredUris.length}`);
21902
21916
  }
21903
- const concurrencyLimit = 10;
21904
21917
  let processedCount = 0;
21905
21918
  const videoResults = [];
21906
21919
  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);
21920
+ for (let i = 0; i < filteredUris.length; i += this.concurrencyLimit) {
21921
+ const batch = filteredUris.slice(i, i + this.concurrencyLimit);
21909
21922
  const batchPromises = batch.map(async (uri, batchIndex) => {
21910
21923
  const index = i + batchIndex;
21911
21924
  const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false, includeMetadata || (!!timestampStart || !!timestampEnd));
@@ -22064,6 +22077,7 @@ var BottlenecksContent = ({
22064
22077
  const firstWorstCycle = videos.find((v) => v.type === "worst_cycle_time");
22065
22078
  const firstSOPDeviation = videos.find((v) => v.type === "missing_quality_check");
22066
22079
  const firstCycleCompletion = videos.find((v) => v.type === "cycle_completions");
22080
+ const firstLongCycleTime = videos.find((v) => v.type === "long_cycle_time");
22067
22081
  preloadVideosUrl2([
22068
22082
  firstHigh?.src,
22069
22083
  firstMed?.src,
@@ -22072,7 +22086,8 @@ var BottlenecksContent = ({
22072
22086
  firstBestCycle?.src,
22073
22087
  firstWorstCycle?.src,
22074
22088
  firstSOPDeviation?.src,
22075
- firstCycleCompletion?.src
22089
+ firstCycleCompletion?.src,
22090
+ firstLongCycleTime?.src
22076
22091
  ].filter(Boolean));
22077
22092
  }
22078
22093
  setAllVideos(videos);
@@ -22098,7 +22113,7 @@ var BottlenecksContent = ({
22098
22113
  if (activeFilter === "worst_cycle_time") return video.type === "worst_cycle_time";
22099
22114
  if (activeFilter === "cycle_completions") return video.type === "cycle_completions";
22100
22115
  if (activeFilter === "long_cycle_time") {
22101
- return video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time");
22116
+ return video.type === "long_cycle_time";
22102
22117
  }
22103
22118
  return video.type === "bottleneck" && video.severity === activeFilter;
22104
22119
  });
@@ -22116,11 +22131,6 @@ var BottlenecksContent = ({
22116
22131
  const selectedCategory = sopCategories.find((cat) => cat.id === activeFilter);
22117
22132
  if (selectedCategory) {
22118
22133
  filtered = allVideos.filter((video) => video.type === selectedCategory.id);
22119
- if (selectedCategory.id === "long_cycle_time") {
22120
- filtered = allVideos.filter(
22121
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22122
- );
22123
- }
22124
22134
  }
22125
22135
  } else {
22126
22136
  if (activeFilter === "low_value") {
@@ -22134,9 +22144,7 @@ var BottlenecksContent = ({
22134
22144
  } else if (activeFilter === "cycle_completions") {
22135
22145
  filtered = allVideos.filter((video) => video.type === "cycle_completions");
22136
22146
  } else if (activeFilter === "long_cycle_time") {
22137
- filtered = allVideos.filter(
22138
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22139
- );
22147
+ filtered = allVideos.filter((video) => video.type === "long_cycle_time");
22140
22148
  } else {
22141
22149
  filtered = allVideos.filter((video) => video.type === "bottleneck" && video.severity === activeFilter);
22142
22150
  }
@@ -22423,13 +22431,7 @@ var BottlenecksContent = ({
22423
22431
  const counts = { total: allVideos.length };
22424
22432
  if (sopCategories && sopCategories.length > 0) {
22425
22433
  sopCategories.forEach((category) => {
22426
- if (category.id === "long_cycle_time") {
22427
- counts[category.id] = allVideos.filter(
22428
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22429
- ).length;
22430
- } else {
22431
- counts[category.id] = allVideos.filter((video) => video.type === category.id).length;
22432
- }
22434
+ counts[category.id] = allVideos.filter((video) => video.type === category.id).length;
22433
22435
  });
22434
22436
  } else {
22435
22437
  counts.bottlenecks = allVideos.filter((video) => video.type === "bottleneck").length;
@@ -22440,9 +22442,7 @@ var BottlenecksContent = ({
22440
22442
  counts.sopDeviations = allVideos.filter((video) => video.type === "missing_quality_check").length;
22441
22443
  counts.bestCycleTimes = allVideos.filter((video) => video.type === "best_cycle_time").length;
22442
22444
  counts.worstCycleTimes = allVideos.filter((video) => video.type === "worst_cycle_time").length;
22443
- counts.longCycleTimes = allVideos.filter(
22444
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22445
- ).length;
22445
+ counts.longCycleTimes = allVideos.filter((video) => video.type === "long_cycle_time").length;
22446
22446
  counts.cycleCompletions = allVideos.filter((video) => video.type === "cycle_completions").length;
22447
22447
  }
22448
22448
  return counts;
@@ -22468,7 +22468,7 @@ var BottlenecksContent = ({
22468
22468
  return "Cycle Completion";
22469
22469
  case "bottleneck":
22470
22470
  default:
22471
- return "Bottleneck";
22471
+ return "";
22472
22472
  }
22473
22473
  };
22474
22474
  const getColorClasses = (color2) => {
@@ -27386,11 +27386,17 @@ function HomeView({
27386
27386
  lineId: selectedLineId,
27387
27387
  onLineMetricsUpdate
27388
27388
  });
27389
+ const lineIdsForBreaks = React14.useMemo(() => {
27390
+ if (selectedLineId === factoryViewId) {
27391
+ return allLineIds;
27392
+ }
27393
+ return [selectedLineId];
27394
+ }, [selectedLineId, factoryViewId, allLineIds]);
27389
27395
  const {
27390
27396
  activeBreaks,
27391
27397
  isLoading: breaksLoading,
27392
27398
  error: breaksError
27393
- } = useActiveBreaks([selectedLineId]);
27399
+ } = useActiveBreaks(lineIdsForBreaks);
27394
27400
  const memoizedWorkspaceMetrics = React14.useMemo(() => workspaceMetrics, [
27395
27401
  // Only update reference if meaningful properties change
27396
27402
  workspaceMetrics.length,
@@ -28657,8 +28663,7 @@ var MobileWorkspaceCard = React14.memo(({
28657
28663
  cardClass,
28658
28664
  onWorkspaceClick,
28659
28665
  getMedalIcon,
28660
- line1Id,
28661
- line2Id
28666
+ getLineName
28662
28667
  }) => /* @__PURE__ */ jsxRuntime.jsxs(
28663
28668
  "div",
28664
28669
  {
@@ -28676,7 +28681,7 @@ var MobileWorkspaceCard = React14.memo(({
28676
28681
  ] }),
28677
28682
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
28678
28683
  /* @__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 })
28684
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: getLineName(workspace.line_id) })
28680
28685
  ] })
28681
28686
  ] }),
28682
28687
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right", children: [
@@ -28714,8 +28719,7 @@ var DesktopWorkspaceRow = React14.memo(({
28714
28719
  rowClass,
28715
28720
  onWorkspaceClick,
28716
28721
  getMedalIcon,
28717
- line1Id,
28718
- line2Id
28722
+ getLineName
28719
28723
  }) => /* @__PURE__ */ jsxRuntime.jsxs(
28720
28724
  "tr",
28721
28725
  {
@@ -28727,7 +28731,7 @@ var DesktopWorkspaceRow = React14.memo(({
28727
28731
  getMedalIcon(index + 1)
28728
28732
  ] }) }),
28729
28733
  /* @__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 }) }),
28734
+ /* @__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
28735
  /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base font-medium whitespace-nowrap", children: [
28732
28736
  (workspace.efficiency || 0).toFixed(1),
28733
28737
  "%"
@@ -28755,10 +28759,19 @@ var LeaderboardDetailView = React14.memo(({
28755
28759
  onWorkspaceClick,
28756
28760
  line1Id = "",
28757
28761
  line2Id = "",
28762
+ lineNames = {},
28758
28763
  className = ""
28759
28764
  }) => {
28760
28765
  const navigation = useNavigation();
28761
28766
  const [sortAscending, setSortAscending] = React14.useState(false);
28767
+ const getLineName = React14.useCallback((lineId2) => {
28768
+ if (lineNames[lineId2]) {
28769
+ return lineNames[lineId2];
28770
+ }
28771
+ if (lineId2 === line1Id) return "Line 1";
28772
+ if (lineId2 === line2Id) return "Line 2";
28773
+ return lineId2;
28774
+ }, [lineNames, line1Id, line2Id]);
28762
28775
  const handleSortToggle = React14.useCallback(() => {
28763
28776
  setSortAscending(!sortAscending);
28764
28777
  }, [sortAscending]);
@@ -28932,8 +28945,7 @@ var LeaderboardDetailView = React14.memo(({
28932
28945
  cardClass,
28933
28946
  onWorkspaceClick: handleWorkspaceClick,
28934
28947
  getMedalIcon,
28935
- line1Id,
28936
- line2Id
28948
+ getLineName
28937
28949
  },
28938
28950
  ws.workspace_uuid
28939
28951
  );
@@ -28958,8 +28970,7 @@ var LeaderboardDetailView = React14.memo(({
28958
28970
  rowClass,
28959
28971
  onWorkspaceClick: handleWorkspaceClick,
28960
28972
  getMedalIcon,
28961
- line1Id,
28962
- line2Id
28973
+ getLineName
28963
28974
  },
28964
28975
  ws.workspace_uuid
28965
28976
  );
@@ -28968,7 +28979,7 @@ var LeaderboardDetailView = React14.memo(({
28968
28979
  ] })
28969
28980
  ] });
28970
28981
  }, (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;
28982
+ 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
28983
  });
28973
28984
  LeaderboardDetailView.displayName = "LeaderboardDetailView";
28974
28985
  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
@@ -861,17 +864,18 @@ var dashboardService = {
861
864
  const queryShiftId = shiftProp !== void 0 ? shiftProp : currentShiftResult.shiftId;
862
865
  try {
863
866
  if (lineIdToQuery === factoryViewId) {
864
- if (!defaultLineId || !secondaryLineId || !companyId) {
865
- throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
867
+ if (!defaultLineId || !companyId) {
868
+ throw new Error("Factory View requires at least defaultLineId and companyId to be configured.");
866
869
  }
867
870
  const { data: line1Data, error: line1Error } = await supabase.from(linesTable).select("id, line_name, factory_id, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", defaultLineId).single();
868
871
  if (line1Error) throw line1Error;
869
872
  if (!line1Data) {
870
873
  throw new Error(`Default line ${defaultLineId} for Factory View not found.`);
871
874
  }
872
- const { data: metricsData, error: metricsError2 } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", queryShiftId).eq("date", queryDate);
875
+ const lineIdsToQuery = [defaultLineId, secondaryLineId].filter((id3) => id3 !== void 0 && id3 !== null);
876
+ const { data: metricsData, error: metricsError2 } = await supabase.from(lineMetricsTable).select("*").in("line_id", lineIdsToQuery).eq("shift_id", queryShiftId).eq("date", queryDate);
873
877
  if (metricsError2) throw metricsError2;
874
- const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", queryShiftId).eq("date", queryDate);
878
+ const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", lineIdsToQuery).eq("shift_id", queryShiftId).eq("date", queryDate);
875
879
  if (performanceError2) throw performanceError2;
876
880
  const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
877
881
  const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
@@ -5096,19 +5100,23 @@ var useActiveBreaks = (lineIds) => {
5096
5100
  const checkActiveBreaks = useCallback(async () => {
5097
5101
  try {
5098
5102
  setError(null);
5099
- if (!lineIds || lineIds.length === 0) {
5103
+ const validLineIds = lineIds.filter(
5104
+ (id3) => id3 && id3 !== "factory" && id3 !== "all" && // Basic UUID format check
5105
+ id3.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
5106
+ );
5107
+ if (!validLineIds || validLineIds.length === 0) {
5100
5108
  setActiveBreaks([]);
5101
5109
  setIsLoading(false);
5102
5110
  return;
5103
5111
  }
5104
5112
  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);
5113
+ 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);
5114
+ 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
5115
  if (dayError || nightError) {
5108
5116
  throw new Error("Failed to fetch shift configurations");
5109
5117
  }
5110
5118
  const foundActiveBreaks = [];
5111
- for (const lineId of lineIds) {
5119
+ for (const lineId of validLineIds) {
5112
5120
  const dayShift = dayShifts?.find((s) => s.line_id === lineId);
5113
5121
  const nightShift = nightShifts?.find((s) => s.line_id === lineId);
5114
5122
  if (!dayShift || !nightShift) continue;
@@ -5592,12 +5600,18 @@ var FALLBACK_DISPLAY_NAMES = {
5592
5600
  "WS03": "Filling station"
5593
5601
  // ... Add others if known defaults are useful
5594
5602
  };
5595
- var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5603
+ var getConfigurableWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5604
+ if (lineId && workspaceConfig?.lineDisplayNames?.[lineId]) {
5605
+ const lineDisplayNames = workspaceConfig.lineDisplayNames[lineId];
5606
+ if (lineDisplayNames[workspaceId]) {
5607
+ return lineDisplayNames[workspaceId];
5608
+ }
5609
+ }
5596
5610
  const displayNames = workspaceConfig?.displayNames || FALLBACK_DISPLAY_NAMES;
5597
5611
  return displayNames[workspaceId] || workspaceId.replace(/^WS/i, "") || workspaceId;
5598
5612
  };
5599
- var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig) => {
5600
- const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig);
5613
+ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, lineId) => {
5614
+ const fullName = getConfigurableWorkspaceDisplayName(workspaceId, workspaceConfig, lineId);
5601
5615
  const match = fullName.match(/([A-Z]\d+(?:-\w+)?)/i);
5602
5616
  if (match && match[0]) {
5603
5617
  return match[0];
@@ -17602,18 +17616,6 @@ var VideoCard = React14__default.memo(({
17602
17616
  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
17617
  });
17604
17618
  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
17619
  var DEFAULT_HLS_URL = "https://192.168.5.9:8443/cam1.m3u8";
17618
17620
  var VideoGridView = React14__default.memo(({
17619
17621
  workspaces,
@@ -17627,13 +17629,20 @@ var VideoGridView = React14__default.memo(({
17627
17629
  const [gridCols, setGridCols] = useState(4);
17628
17630
  const [visibleWorkspaces, setVisibleWorkspaces] = useState(/* @__PURE__ */ new Set());
17629
17631
  const videoConfig = useVideoConfig();
17630
- const { cropping, canvasConfig } = videoConfig;
17632
+ const { cropping, canvasConfig, hlsUrls } = videoConfig;
17631
17633
  const mergedVideoSources = {
17632
- defaultHlsUrl: videoSources.defaultHlsUrl || DEFAULT_HLS_URL,
17633
- workspaceHlsUrls: { ...DEFAULT_WORKSPACE_HLS_URLS, ...videoSources.workspaceHlsUrls }
17634
+ defaultHlsUrl: videoSources.defaultHlsUrl || hlsUrls?.defaultHlsUrl || DEFAULT_HLS_URL,
17635
+ workspaceHlsUrls: { ...videoSources.workspaceHlsUrls, ...hlsUrls?.workspaceHlsUrls },
17636
+ lineWorkspaceHlsUrls: hlsUrls?.lineWorkspaceHlsUrls || {}
17634
17637
  };
17635
- const getWorkspaceHlsUrl = useCallback((workspaceName) => {
17638
+ const getWorkspaceHlsUrl = useCallback((workspaceName, lineId) => {
17636
17639
  const wsName = workspaceName.toUpperCase();
17640
+ if (lineId && mergedVideoSources.lineWorkspaceHlsUrls[lineId]) {
17641
+ const lineUrls = mergedVideoSources.lineWorkspaceHlsUrls[lineId];
17642
+ if (lineUrls[wsName]) {
17643
+ return lineUrls[wsName];
17644
+ }
17645
+ }
17637
17646
  return mergedVideoSources.workspaceHlsUrls[wsName] || mergedVideoSources.defaultHlsUrl;
17638
17647
  }, [mergedVideoSources]);
17639
17648
  const getWorkspaceCropping = useCallback((workspaceName) => {
@@ -17793,7 +17802,7 @@ var VideoGridView = React14__default.memo(({
17793
17802
  VideoCard,
17794
17803
  {
17795
17804
  workspace,
17796
- hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name),
17805
+ hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id),
17797
17806
  shouldPlay: isVisible,
17798
17807
  onClick: () => handleWorkspaceClick(workspace),
17799
17808
  onFatalError: throttledReloadDashboard,
@@ -21446,7 +21455,7 @@ function parseS3Uri(s3Uri, sopCategories) {
21446
21455
  break;
21447
21456
  case "long_cycle_time":
21448
21457
  severity = "high";
21449
- type = "bottleneck";
21458
+ type = "long_cycle_time";
21450
21459
  description = "Long Cycle Time Detected";
21451
21460
  break;
21452
21461
  case "best_cycle_time":
@@ -21490,7 +21499,7 @@ function parseS3Uri(s3Uri, sopCategories) {
21490
21499
  severity = "low";
21491
21500
  description = "Best Cycle Time Performance";
21492
21501
  } else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
21493
- type = "bottleneck";
21502
+ type = "long_cycle_time";
21494
21503
  severity = "high";
21495
21504
  description = "Long Cycle Time Detected";
21496
21505
  } else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
@@ -21517,6 +21526,11 @@ var S3ClipsService = class {
21517
21526
  if (!config.s3Config) {
21518
21527
  throw new Error("S3 configuration is required");
21519
21528
  }
21529
+ const processing = config.s3Config.processing || {};
21530
+ this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
21531
+ this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
21532
+ this.concurrencyLimit = processing.concurrencyLimit || 10;
21533
+ this.maxInitialFetch = processing.maxInitialFetch || 60;
21520
21534
  const region = this.validateAndSanitizeRegion(config.s3Config.region);
21521
21535
  console.log(`S3ClipsService: Using AWS region: ${region}`);
21522
21536
  this.s3Client = new S3Client({
@@ -21801,9 +21815,9 @@ var S3ClipsService = class {
21801
21815
  }
21802
21816
  return summary;
21803
21817
  }
21804
- const limitPerCategory = limit ? Math.min(Math.max(limit, 1), 1e3) : 30;
21818
+ const limitPerCategory = limit ? Math.min(Math.max(limit, 1), this.maxLimitPerCategory) : this.defaultLimitPerCategory;
21805
21819
  const shouldFetchAll = category === "missing_quality_check" || category === "low_value";
21806
- const initialFetchLimit = shouldFetchAll ? void 0 : category ? limitPerCategory * 3 : void 0;
21820
+ const initialFetchLimit = shouldFetchAll ? void 0 : category ? Math.min(limitPerCategory * 3, this.maxInitialFetch) : void 0;
21807
21821
  const s3Uris = await this.listS3Clips({ workspaceId, date, shiftId, maxKeys: initialFetchLimit });
21808
21822
  if (s3Uris.length === 0) {
21809
21823
  console.log(`S3ClipsService: No HLS playlists found for workspace ${workspaceId} on date ${date}, shift ${shiftId}`);
@@ -21871,12 +21885,11 @@ var S3ClipsService = class {
21871
21885
  }
21872
21886
  console.log(`S3ClipsService: Total filtered URIs across all categories: ${filteredUris.length}`);
21873
21887
  }
21874
- const concurrencyLimit = 10;
21875
21888
  let processedCount = 0;
21876
21889
  const videoResults = [];
21877
21890
  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);
21891
+ for (let i = 0; i < filteredUris.length; i += this.concurrencyLimit) {
21892
+ const batch = filteredUris.slice(i, i + this.concurrencyLimit);
21880
21893
  const batchPromises = batch.map(async (uri, batchIndex) => {
21881
21894
  const index = i + batchIndex;
21882
21895
  const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false, includeMetadata || (!!timestampStart || !!timestampEnd));
@@ -22035,6 +22048,7 @@ var BottlenecksContent = ({
22035
22048
  const firstWorstCycle = videos.find((v) => v.type === "worst_cycle_time");
22036
22049
  const firstSOPDeviation = videos.find((v) => v.type === "missing_quality_check");
22037
22050
  const firstCycleCompletion = videos.find((v) => v.type === "cycle_completions");
22051
+ const firstLongCycleTime = videos.find((v) => v.type === "long_cycle_time");
22038
22052
  preloadVideosUrl2([
22039
22053
  firstHigh?.src,
22040
22054
  firstMed?.src,
@@ -22043,7 +22057,8 @@ var BottlenecksContent = ({
22043
22057
  firstBestCycle?.src,
22044
22058
  firstWorstCycle?.src,
22045
22059
  firstSOPDeviation?.src,
22046
- firstCycleCompletion?.src
22060
+ firstCycleCompletion?.src,
22061
+ firstLongCycleTime?.src
22047
22062
  ].filter(Boolean));
22048
22063
  }
22049
22064
  setAllVideos(videos);
@@ -22069,7 +22084,7 @@ var BottlenecksContent = ({
22069
22084
  if (activeFilter === "worst_cycle_time") return video.type === "worst_cycle_time";
22070
22085
  if (activeFilter === "cycle_completions") return video.type === "cycle_completions";
22071
22086
  if (activeFilter === "long_cycle_time") {
22072
- return video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time");
22087
+ return video.type === "long_cycle_time";
22073
22088
  }
22074
22089
  return video.type === "bottleneck" && video.severity === activeFilter;
22075
22090
  });
@@ -22087,11 +22102,6 @@ var BottlenecksContent = ({
22087
22102
  const selectedCategory = sopCategories.find((cat) => cat.id === activeFilter);
22088
22103
  if (selectedCategory) {
22089
22104
  filtered = allVideos.filter((video) => video.type === selectedCategory.id);
22090
- if (selectedCategory.id === "long_cycle_time") {
22091
- filtered = allVideos.filter(
22092
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22093
- );
22094
- }
22095
22105
  }
22096
22106
  } else {
22097
22107
  if (activeFilter === "low_value") {
@@ -22105,9 +22115,7 @@ var BottlenecksContent = ({
22105
22115
  } else if (activeFilter === "cycle_completions") {
22106
22116
  filtered = allVideos.filter((video) => video.type === "cycle_completions");
22107
22117
  } else if (activeFilter === "long_cycle_time") {
22108
- filtered = allVideos.filter(
22109
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22110
- );
22118
+ filtered = allVideos.filter((video) => video.type === "long_cycle_time");
22111
22119
  } else {
22112
22120
  filtered = allVideos.filter((video) => video.type === "bottleneck" && video.severity === activeFilter);
22113
22121
  }
@@ -22394,13 +22402,7 @@ var BottlenecksContent = ({
22394
22402
  const counts = { total: allVideos.length };
22395
22403
  if (sopCategories && sopCategories.length > 0) {
22396
22404
  sopCategories.forEach((category) => {
22397
- if (category.id === "long_cycle_time") {
22398
- counts[category.id] = allVideos.filter(
22399
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22400
- ).length;
22401
- } else {
22402
- counts[category.id] = allVideos.filter((video) => video.type === category.id).length;
22403
- }
22405
+ counts[category.id] = allVideos.filter((video) => video.type === category.id).length;
22404
22406
  });
22405
22407
  } else {
22406
22408
  counts.bottlenecks = allVideos.filter((video) => video.type === "bottleneck").length;
@@ -22411,9 +22413,7 @@ var BottlenecksContent = ({
22411
22413
  counts.sopDeviations = allVideos.filter((video) => video.type === "missing_quality_check").length;
22412
22414
  counts.bestCycleTimes = allVideos.filter((video) => video.type === "best_cycle_time").length;
22413
22415
  counts.worstCycleTimes = allVideos.filter((video) => video.type === "worst_cycle_time").length;
22414
- counts.longCycleTimes = allVideos.filter(
22415
- (video) => video.type === "bottleneck" && video.description.toLowerCase().includes("cycle time")
22416
- ).length;
22416
+ counts.longCycleTimes = allVideos.filter((video) => video.type === "long_cycle_time").length;
22417
22417
  counts.cycleCompletions = allVideos.filter((video) => video.type === "cycle_completions").length;
22418
22418
  }
22419
22419
  return counts;
@@ -22439,7 +22439,7 @@ var BottlenecksContent = ({
22439
22439
  return "Cycle Completion";
22440
22440
  case "bottleneck":
22441
22441
  default:
22442
- return "Bottleneck";
22442
+ return "";
22443
22443
  }
22444
22444
  };
22445
22445
  const getColorClasses = (color2) => {
@@ -27357,11 +27357,17 @@ function HomeView({
27357
27357
  lineId: selectedLineId,
27358
27358
  onLineMetricsUpdate
27359
27359
  });
27360
+ const lineIdsForBreaks = useMemo(() => {
27361
+ if (selectedLineId === factoryViewId) {
27362
+ return allLineIds;
27363
+ }
27364
+ return [selectedLineId];
27365
+ }, [selectedLineId, factoryViewId, allLineIds]);
27360
27366
  const {
27361
27367
  activeBreaks,
27362
27368
  isLoading: breaksLoading,
27363
27369
  error: breaksError
27364
- } = useActiveBreaks([selectedLineId]);
27370
+ } = useActiveBreaks(lineIdsForBreaks);
27365
27371
  const memoizedWorkspaceMetrics = useMemo(() => workspaceMetrics, [
27366
27372
  // Only update reference if meaningful properties change
27367
27373
  workspaceMetrics.length,
@@ -28628,8 +28634,7 @@ var MobileWorkspaceCard = memo(({
28628
28634
  cardClass,
28629
28635
  onWorkspaceClick,
28630
28636
  getMedalIcon,
28631
- line1Id,
28632
- line2Id
28637
+ getLineName
28633
28638
  }) => /* @__PURE__ */ jsxs(
28634
28639
  "div",
28635
28640
  {
@@ -28647,7 +28652,7 @@ var MobileWorkspaceCard = memo(({
28647
28652
  ] }),
28648
28653
  /* @__PURE__ */ jsxs("div", { children: [
28649
28654
  /* @__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 })
28655
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: getLineName(workspace.line_id) })
28651
28656
  ] })
28652
28657
  ] }),
28653
28658
  /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
@@ -28685,8 +28690,7 @@ var DesktopWorkspaceRow = memo(({
28685
28690
  rowClass,
28686
28691
  onWorkspaceClick,
28687
28692
  getMedalIcon,
28688
- line1Id,
28689
- line2Id
28693
+ getLineName
28690
28694
  }) => /* @__PURE__ */ jsxs(
28691
28695
  "tr",
28692
28696
  {
@@ -28698,7 +28702,7 @@ var DesktopWorkspaceRow = memo(({
28698
28702
  getMedalIcon(index + 1)
28699
28703
  ] }) }),
28700
28704
  /* @__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 }) }),
28705
+ /* @__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
28706
  /* @__PURE__ */ jsxs("td", { className: "px-3 py-2.5 sm:p-4 text-sm sm:text-base font-medium whitespace-nowrap", children: [
28703
28707
  (workspace.efficiency || 0).toFixed(1),
28704
28708
  "%"
@@ -28726,10 +28730,19 @@ var LeaderboardDetailView = memo(({
28726
28730
  onWorkspaceClick,
28727
28731
  line1Id = "",
28728
28732
  line2Id = "",
28733
+ lineNames = {},
28729
28734
  className = ""
28730
28735
  }) => {
28731
28736
  const navigation = useNavigation();
28732
28737
  const [sortAscending, setSortAscending] = useState(false);
28738
+ const getLineName = useCallback((lineId2) => {
28739
+ if (lineNames[lineId2]) {
28740
+ return lineNames[lineId2];
28741
+ }
28742
+ if (lineId2 === line1Id) return "Line 1";
28743
+ if (lineId2 === line2Id) return "Line 2";
28744
+ return lineId2;
28745
+ }, [lineNames, line1Id, line2Id]);
28733
28746
  const handleSortToggle = useCallback(() => {
28734
28747
  setSortAscending(!sortAscending);
28735
28748
  }, [sortAscending]);
@@ -28903,8 +28916,7 @@ var LeaderboardDetailView = memo(({
28903
28916
  cardClass,
28904
28917
  onWorkspaceClick: handleWorkspaceClick,
28905
28918
  getMedalIcon,
28906
- line1Id,
28907
- line2Id
28919
+ getLineName
28908
28920
  },
28909
28921
  ws.workspace_uuid
28910
28922
  );
@@ -28929,8 +28941,7 @@ var LeaderboardDetailView = memo(({
28929
28941
  rowClass,
28930
28942
  onWorkspaceClick: handleWorkspaceClick,
28931
28943
  getMedalIcon,
28932
- line1Id,
28933
- line2Id
28944
+ getLineName
28934
28945
  },
28935
28946
  ws.workspace_uuid
28936
28947
  );
@@ -28939,7 +28950,7 @@ var LeaderboardDetailView = memo(({
28939
28950
  ] })
28940
28951
  ] });
28941
28952
  }, (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;
28953
+ 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
28954
  });
28944
28955
  LeaderboardDetailView.displayName = "LeaderboardDetailView";
28945
28956
  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.3",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",