@optifye/dashboard-core 6.11.23 → 6.11.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -13284,7 +13284,7 @@ var parseEfficiencyLegend = (legend) => {
13284
13284
  critical_threshold: coerce(legend.critical_threshold, DEFAULT_EFFICIENCY_LEGEND.critical_threshold)
13285
13285
  };
13286
13286
  };
13287
- var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds }) => {
13287
+ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, lineIds, userAccessibleLineIds }) => {
13288
13288
  const { supabaseUrl, supabaseKey } = useDashboardConfig();
13289
13289
  const entityConfig = useEntityConfig();
13290
13290
  const databaseConfig = useDatabaseConfig();
@@ -13301,9 +13301,9 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13301
13301
  return getConfiguredLineIds(entityConfig);
13302
13302
  }, [entityConfig]);
13303
13303
  const targetFactoryLineIds = React142.useMemo(() => {
13304
- const sourceLineIds = userAccessibleLineIds !== void 0 ? userAccessibleLineIds : configuredLineIds;
13304
+ const sourceLineIds = lineIds !== void 0 ? lineIds : userAccessibleLineIds !== void 0 ? userAccessibleLineIds : configuredLineIds;
13305
13305
  return Array.from(new Set((sourceLineIds || []).filter(Boolean)));
13306
- }, [userAccessibleLineIds, configuredLineIds]);
13306
+ }, [lineIds, userAccessibleLineIds, configuredLineIds]);
13307
13307
  const { shiftConfig: staticShiftConfig } = useDashboardConfig();
13308
13308
  const {
13309
13309
  shiftConfigMap: multiLineShiftConfigMap,
@@ -13356,6 +13356,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13356
13356
  const operationalShiftKeyRef = React142.useRef(operationalShiftKey);
13357
13357
  const configuredLineIdsRef = React142.useRef(configuredLineIds);
13358
13358
  const userAccessibleLineIdsRef = React142.useRef(userAccessibleLineIds);
13359
+ const explicitLineIdsRef = React142.useRef(lineIds);
13359
13360
  React142.useEffect(() => {
13360
13361
  onLineMetricsUpdateRef.current = onLineMetricsUpdate;
13361
13362
  }, [onLineMetricsUpdate]);
@@ -13371,6 +13372,9 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13371
13372
  React142.useEffect(() => {
13372
13373
  userAccessibleLineIdsRef.current = userAccessibleLineIds;
13373
13374
  }, [userAccessibleLineIds]);
13375
+ React142.useEffect(() => {
13376
+ explicitLineIdsRef.current = lineIds;
13377
+ }, [lineIds]);
13374
13378
  const companySpecificMetricsTable = React142.useMemo(
13375
13379
  () => getCompanyMetricsTableName(entityConfig.companyId, "performance_metrics"),
13376
13380
  [entityConfig.companyId]
@@ -13721,6 +13725,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13721
13725
  isFactoryView,
13722
13726
  multiLineShiftConfigMap,
13723
13727
  staticShiftConfig,
13728
+ lineIds,
13724
13729
  userAccessibleLineIds,
13725
13730
  effectiveWorkspaceConfig,
13726
13731
  shiftLoading,
@@ -13838,7 +13843,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13838
13843
  logDebug("[useDashboardMetrics] Setting up group subscriptions:", {
13839
13844
  groupCount: currentShiftGroups.length
13840
13845
  });
13841
- const targetFactoryLineIdsForSubscriptions = currentUserAccessibleLineIds !== void 0 ? currentUserAccessibleLineIds : currentConfiguredLineIds;
13846
+ const targetFactoryLineIdsForSubscriptions = explicitLineIdsRef.current !== void 0 ? explicitLineIdsRef.current : currentUserAccessibleLineIds !== void 0 ? currentUserAccessibleLineIds : currentConfiguredLineIds;
13842
13847
  const targetFactoryLineIdSet = new Set(
13843
13848
  (targetFactoryLineIdsForSubscriptions || []).filter(Boolean)
13844
13849
  );
@@ -14009,7 +14014,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
14009
14014
  });
14010
14015
  const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : getCurrentShift(defaultTimezone, staticShiftConfig);
14011
14016
  const operationalDateForSubscription = currentShiftDetails.date;
14012
- const targetLineIds = isFactory ? currentUserAccessibleLineIds || currentConfiguredLineIds : [currentLineIdToUse];
14017
+ const targetLineIds = isFactory ? explicitLineIdsRef.current || currentUserAccessibleLineIds || currentConfiguredLineIds : [currentLineIdToUse];
14013
14018
  const filteredLineIds = targetLineIds.filter((id3) => id3 && id3 !== factoryViewIdentifier);
14014
14019
  if (filteredLineIds.length === 0) {
14015
14020
  logDebug("[useDashboardMetrics] Realtime setup skipped: no line IDs after filtering", {
@@ -20423,13 +20428,15 @@ var buildKPIsFromLineMetricsRow = (row) => {
20423
20428
  };
20424
20429
  var aggregateKPIsFromLineMetricsRows = (rows) => {
20425
20430
  if (!rows || rows.length === 0) return createDefaultKPIs();
20426
- const currentOutputSum = rows.reduce((sum, row) => sum + toNumber(row.current_output), 0);
20427
- const lineThresholdSum = rows.reduce((sum, row) => sum + toNumber(row.line_threshold), 0);
20428
- const idealOutputSum = rows.reduce(
20431
+ const eligibleRows = rows.filter((row) => toNumber(row?.avg_efficiency) >= 5);
20432
+ if (eligibleRows.length === 0) return createDefaultKPIs();
20433
+ const currentOutputSum = eligibleRows.reduce((sum, row) => sum + toNumber(row.current_output), 0);
20434
+ const lineThresholdSum = eligibleRows.reduce((sum, row) => sum + toNumber(row.line_threshold), 0);
20435
+ const idealOutputSum = eligibleRows.reduce(
20429
20436
  (sum, row) => sum + (toNumber(row.ideal_output) || toNumber(row.line_threshold)),
20430
20437
  0
20431
20438
  );
20432
- const efficiencyValues = rows.map((row) => {
20439
+ const efficiencyValues = eligibleRows.map((row) => {
20433
20440
  const value = row?.avg_efficiency;
20434
20441
  if (typeof value === "number" && Number.isFinite(value)) return value;
20435
20442
  if (typeof value === "string" && value.trim() !== "") {
@@ -20439,10 +20446,10 @@ var aggregateKPIsFromLineMetricsRows = (rows) => {
20439
20446
  return null;
20440
20447
  }).filter((value) => value !== null);
20441
20448
  const avgEfficiency = efficiencyValues.length > 0 ? efficiencyValues.reduce((sum, value) => sum + value, 0) / efficiencyValues.length : 0;
20442
- const numLines = rows.length;
20443
- const avgCycleTime = numLines > 0 ? rows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
20444
- const totalUnderperforming = rows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
20445
- const totalWorkspaces = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
20449
+ const numLines = eligibleRows.length;
20450
+ const avgCycleTime = numLines > 0 ? eligibleRows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
20451
+ const totalUnderperforming = eligibleRows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
20452
+ const totalWorkspaces = eligibleRows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
20446
20453
  return {
20447
20454
  underperformingWorkers: {
20448
20455
  current: totalUnderperforming,
@@ -34598,6 +34605,8 @@ var logDebug2 = (...args) => {
34598
34605
  var VideoGridView = React142__namespace.default.memo(({
34599
34606
  workspaces,
34600
34607
  selectedLine,
34608
+ lineNames = {},
34609
+ lineOrder = [],
34601
34610
  className = "",
34602
34611
  legend,
34603
34612
  videoSources = {},
@@ -34700,6 +34709,44 @@ var VideoGridView = React142__namespace.default.memo(({
34700
34709
  }
34701
34710
  }) : workspaces;
34702
34711
  }, [workspaces, selectedLine]);
34712
+ const sortedWorkspaces = React142.useMemo(() => {
34713
+ return [...filteredWorkspaces].sort((a, b) => {
34714
+ if (a.line_id !== b.line_id) {
34715
+ return (a.line_id || "").localeCompare(b.line_id || "");
34716
+ }
34717
+ const aMatch = a.workspace_name.match(/WS(\d+)/);
34718
+ const bMatch = b.workspace_name.match(/WS(\d+)/);
34719
+ if (aMatch && bMatch) {
34720
+ const aNum = parseInt(aMatch[1], 10);
34721
+ const bNum = parseInt(bMatch[1], 10);
34722
+ return aNum - bNum;
34723
+ }
34724
+ return a.workspace_name.localeCompare(b.workspace_name);
34725
+ });
34726
+ }, [filteredWorkspaces]);
34727
+ const lineGroups = React142.useMemo(() => {
34728
+ const grouped = /* @__PURE__ */ new Map();
34729
+ sortedWorkspaces.forEach((workspace) => {
34730
+ const lineId = workspace.line_id || "unknown";
34731
+ const existing = grouped.get(lineId);
34732
+ if (existing) {
34733
+ existing.push(workspace);
34734
+ return;
34735
+ }
34736
+ grouped.set(lineId, [workspace]);
34737
+ });
34738
+ const sortedRemainingLineIds = Array.from(grouped.keys()).sort((a, b) => a.localeCompare(b));
34739
+ const orderedLineIds = [
34740
+ ...lineOrder.filter((lineId) => grouped.has(lineId)),
34741
+ ...sortedRemainingLineIds.filter((lineId) => !lineOrder.includes(lineId))
34742
+ ];
34743
+ return orderedLineIds.map((lineId) => ({
34744
+ lineId,
34745
+ lineName: lineNames[lineId] || `Line ${lineId.substring(0, 4)}`,
34746
+ workspaces: grouped.get(lineId) || []
34747
+ }));
34748
+ }, [sortedWorkspaces, lineOrder, lineNames]);
34749
+ lineGroups.length > 1;
34703
34750
  const streamsReady = !videoStreamsLoading;
34704
34751
  const calculateOptimalGrid = React142.useCallback(() => {
34705
34752
  if (!containerRef.current) return;
@@ -34863,108 +34910,130 @@ var VideoGridView = React142__namespace.default.memo(({
34863
34910
  stream_source: isR2Stream ? "r2" : "media_config"
34864
34911
  });
34865
34912
  }, []);
34913
+ const workspaceCards = React142.useMemo(() => {
34914
+ return sortedWorkspaces.map((workspace) => {
34915
+ const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
34916
+ const workspaceKey = `${workspace.line_id || "unknown"}-${workspaceId}`;
34917
+ const isVisible = visibleWorkspaces.has(workspaceId);
34918
+ const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
34919
+ const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
34920
+ const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
34921
+ const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
34922
+ const r2Url = workspaceStream?.hls_url;
34923
+ const fallbackUrl = getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id);
34924
+ const hasR2Stream = Boolean(r2Url);
34925
+ const useFallback = r2FallbackWorkspaces.has(workspaceId) || streamsReady && !hasR2Stream;
34926
+ const hlsUrl = useFallback ? fallbackUrl : r2Url ?? "";
34927
+ const isR2Stream = !useFallback && hasR2Stream;
34928
+ const canAttemptR2 = hasR2Stream && !r2FallbackWorkspaces.has(workspaceId);
34929
+ const shouldPlay = isVisible && Boolean(hlsUrl) && (!failedStreams.has(workspaceId) || canAttemptR2);
34930
+ return {
34931
+ workspace,
34932
+ workspaceId,
34933
+ workspaceKey,
34934
+ isVisible,
34935
+ isVeryLowEfficiency,
34936
+ workspaceCropping,
34937
+ fallbackUrl,
34938
+ hlsUrl,
34939
+ isR2Stream,
34940
+ shouldPlay,
34941
+ lastSeenLabel
34942
+ };
34943
+ });
34944
+ }, [
34945
+ sortedWorkspaces,
34946
+ visibleWorkspaces,
34947
+ getWorkspaceCropping,
34948
+ videoStreamsByWorkspaceId,
34949
+ lastSeenByWorkspaceId,
34950
+ getWorkspaceHlsUrl,
34951
+ r2FallbackWorkspaces,
34952
+ streamsReady,
34953
+ failedStreams
34954
+ ]);
34955
+ React142.useMemo(() => {
34956
+ const map = /* @__PURE__ */ new Map();
34957
+ workspaceCards.forEach((card) => {
34958
+ map.set(card.workspaceKey, card);
34959
+ });
34960
+ return map;
34961
+ }, [workspaceCards]);
34962
+ const croppedActiveCount = React142.useMemo(() => {
34963
+ return workspaceCards.reduce((count, card) => {
34964
+ if (card.shouldPlay && card.workspaceCropping) {
34965
+ return count + 1;
34966
+ }
34967
+ return count;
34968
+ }, 0);
34969
+ }, [workspaceCards]);
34970
+ const throttleCropping = croppedActiveCount > 10;
34971
+ const effectiveCanvasFps = throttleCropping ? 10 : canvasConfig?.fps;
34972
+ const effectiveUseRAF = throttleCropping ? false : canvasConfig?.useRAF;
34866
34973
  const displayMinuteBucket = Math.floor(Date.now() / 6e4);
34867
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative overflow-hidden h-full w-full ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "h-full w-full p-3 sm:p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
34974
+ const renderWorkspaceCard = React142.useCallback((card, className2) => /* @__PURE__ */ jsxRuntime.jsx(
34868
34975
  "div",
34869
34976
  {
34870
- className: "grid h-full w-full gap-3 sm:gap-2",
34871
- style: {
34872
- gridTemplateColumns: `repeat(${gridCols}, 1fr)`,
34873
- gridTemplateRows: `repeat(${gridRows}, 1fr)`,
34874
- gridAutoFlow: "row"
34875
- },
34876
- children: (() => {
34877
- const sortedWorkspaces = [...filteredWorkspaces].sort((a, b) => {
34878
- if (a.line_id !== b.line_id) {
34879
- return (a.line_id || "").localeCompare(b.line_id || "");
34880
- }
34881
- const aMatch = a.workspace_name.match(/WS(\d+)/);
34882
- const bMatch = b.workspace_name.match(/WS(\d+)/);
34883
- if (aMatch && bMatch) {
34884
- const aNum = parseInt(aMatch[1]);
34885
- const bNum = parseInt(bMatch[1]);
34886
- return aNum - bNum;
34887
- }
34888
- return a.workspace_name.localeCompare(b.workspace_name);
34889
- });
34890
- const workspaceCards = sortedWorkspaces.map((workspace) => {
34891
- const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
34892
- `${workspace.line_id || "unknown"}-${workspaceId}`;
34893
- const isVisible = visibleWorkspaces.has(workspaceId);
34894
- const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
34895
- const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
34896
- const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
34897
- const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
34898
- const r2Url = workspaceStream?.hls_url;
34899
- const fallbackUrl = getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id);
34900
- const hasR2Stream = Boolean(r2Url);
34901
- const useFallback = r2FallbackWorkspaces.has(workspaceId) || streamsReady && !hasR2Stream;
34902
- const hlsUrl = useFallback ? fallbackUrl : r2Url ?? "";
34903
- const isR2Stream = !useFallback && hasR2Stream;
34904
- const canAttemptR2 = hasR2Stream && !r2FallbackWorkspaces.has(workspaceId);
34905
- const shouldPlay = isVisible && Boolean(hlsUrl) && (!failedStreams.has(workspaceId) || canAttemptR2);
34906
- return {
34907
- workspace,
34908
- workspaceId,
34909
- isVisible,
34910
- isVeryLowEfficiency,
34911
- workspaceCropping,
34912
- fallbackUrl,
34913
- hlsUrl,
34914
- isR2Stream,
34915
- shouldPlay,
34916
- lastSeenLabel
34917
- };
34918
- });
34919
- const croppedActiveCount = workspaceCards.reduce((count, card) => {
34920
- if (card.shouldPlay && card.workspaceCropping) {
34921
- return count + 1;
34922
- }
34923
- return count;
34924
- }, 0);
34925
- const throttleCropping = croppedActiveCount > 10;
34926
- const effectiveCanvasFps = throttleCropping ? 10 : canvasConfig?.fps;
34927
- const effectiveUseRAF = throttleCropping ? false : canvasConfig?.useRAF;
34928
- return workspaceCards.map((card) => /* @__PURE__ */ jsxRuntime.jsx(
34929
- "div",
34930
- {
34931
- "data-workspace-id": card.workspaceId,
34932
- className: "workspace-card relative w-full h-full",
34933
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsxRuntime.jsx(
34934
- VideoCard,
34935
- {
34936
- workspace: card.workspace,
34937
- hlsUrl: card.hlsUrl,
34938
- shouldPlay: card.shouldPlay,
34939
- onClick: () => handleWorkspaceClick(card.workspace),
34940
- onFatalError: () => handleStreamError(card.workspaceId, {
34941
- isR2Stream: card.isR2Stream,
34942
- fallbackUrl: card.fallbackUrl
34943
- }),
34944
- isVeryLowEfficiency: card.isVeryLowEfficiency,
34945
- legend: effectiveLegend,
34946
- cropping: card.workspaceCropping,
34947
- canvasFps: effectiveCanvasFps,
34948
- displayName: (
34949
- // Create line-aware lookup key: lineId_workspaceName
34950
- // This ensures correct mapping when multiple lines have same workspace names
34951
- displayNames[`${card.workspace.line_id}_${card.workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
34952
- getWorkspaceDisplayName(card.workspace.workspace_name, card.workspace.line_id)
34953
- ),
34954
- lastSeenLabel: card.lastSeenLabel,
34955
- useRAF: effectiveUseRAF,
34956
- displayMinuteBucket,
34957
- compact: !selectedLine,
34958
- onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(card.workspaceId) : void 0,
34959
- onMouseLeave: onWorkspaceHoverEnd ? () => onWorkspaceHoverEnd(card.workspaceId) : void 0
34960
- }
34961
- ) })
34977
+ "data-workspace-id": card.workspaceId,
34978
+ className: className2,
34979
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsxRuntime.jsx(
34980
+ VideoCard,
34981
+ {
34982
+ workspace: card.workspace,
34983
+ hlsUrl: card.hlsUrl,
34984
+ shouldPlay: card.shouldPlay,
34985
+ onClick: () => handleWorkspaceClick(card.workspace),
34986
+ onFatalError: () => handleStreamError(card.workspaceId, {
34987
+ isR2Stream: card.isR2Stream,
34988
+ fallbackUrl: card.fallbackUrl
34989
+ }),
34990
+ isVeryLowEfficiency: card.isVeryLowEfficiency,
34991
+ legend: effectiveLegend,
34992
+ cropping: card.workspaceCropping,
34993
+ canvasFps: effectiveCanvasFps,
34994
+ displayName: displayNames[`${card.workspace.line_id}_${card.workspace.workspace_name}`] || getWorkspaceDisplayName(card.workspace.workspace_name, card.workspace.line_id),
34995
+ lastSeenLabel: card.lastSeenLabel,
34996
+ useRAF: effectiveUseRAF,
34997
+ displayMinuteBucket,
34998
+ compact: !selectedLine,
34999
+ onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(card.workspaceId) : void 0,
35000
+ onMouseLeave: onWorkspaceHoverEnd ? () => onWorkspaceHoverEnd(card.workspaceId) : void 0
35001
+ }
35002
+ ) })
35003
+ },
35004
+ card.workspaceKey
35005
+ ), [
35006
+ displayNames,
35007
+ displayMinuteBucket,
35008
+ effectiveCanvasFps,
35009
+ effectiveLegend,
35010
+ effectiveUseRAF,
35011
+ getWorkspaceDisplayName,
35012
+ handleStreamError,
35013
+ handleWorkspaceClick,
35014
+ onWorkspaceHover,
35015
+ onWorkspaceHoverEnd,
35016
+ selectedLine
35017
+ ]);
35018
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative overflow-hidden h-full w-full bg-slate-50/30 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx(
35019
+ "div",
35020
+ {
35021
+ ref: containerRef,
35022
+ className: "h-full w-full px-1 sm:px-2 py-1 sm:py-2",
35023
+ children: /* @__PURE__ */ jsxRuntime.jsx(
35024
+ "div",
35025
+ {
35026
+ className: "grid h-full w-full gap-1.5 sm:gap-2",
35027
+ style: {
35028
+ gridTemplateColumns: `repeat(${gridCols}, 1fr)`,
35029
+ gridTemplateRows: `repeat(${gridRows}, 1fr)`,
35030
+ gridAutoFlow: "row"
34962
35031
  },
34963
- card.workspaceId
34964
- ));
34965
- })()
35032
+ children: workspaceCards.map((card) => renderWorkspaceCard(card, "workspace-card relative w-full h-full"))
35033
+ }
35034
+ )
34966
35035
  }
34967
- ) }) });
35036
+ ) });
34968
35037
  });
34969
35038
  VideoGridView.displayName = "VideoGridView";
34970
35039
  var MapGridView = React142__namespace.default.memo(({
@@ -47677,7 +47746,7 @@ var LinePdfGenerator = ({
47677
47746
  doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
47678
47747
  createKPIBox(kpiStartY + kpiSpacing);
47679
47748
  doc.setFont("helvetica", "normal");
47680
- doc.text("Average Issue Resolution Time:", 25, kpiStartY + kpiSpacing);
47749
+ doc.text("Issue Resolution Time:", 25, kpiStartY + kpiSpacing);
47681
47750
  doc.setFont("helvetica", "bold");
47682
47751
  const resolutionSeconds = issueResolutionSummary ? issueResolutionSummary.mean_resolution_seconds ?? issueResolutionSummary.median_resolution_seconds : null;
47683
47752
  const displayValue = resolutionSeconds !== null ? formatResolutionDuration2(resolutionSeconds) : "-";
@@ -50387,29 +50456,29 @@ var Legend5 = ({
50387
50456
  }) => {
50388
50457
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
50389
50458
  const exclamationLabel = useBottleneckLabel ? "Bottleneck" : "<50% efficiency";
50390
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-1.5 sm:gap-3 bg-white/95 rounded-lg shadow-sm px-2 sm:px-4 py-1 sm:py-1.5 border border-gray-200/60 backdrop-blur-sm text-[10px] sm:text-sm", children: [
50459
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 sm:gap-4 text-xs font-medium text-slate-600", children: [
50391
50460
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-medium text-gray-700 hidden sm:block", children: [
50392
50461
  metricLabel,
50393
50462
  ":"
50394
50463
  ] }),
50395
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 sm:gap-4", children: [
50396
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50397
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 sm:w-2.5 sm:h-2.5 rounded-[12px] bg-[#00AB45]/90 ring-1 ring-[#00AB45]/20" }),
50398
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: formatPercentRange(effectiveLegend.green_min, effectiveLegend.green_max) })
50464
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:gap-4", children: [
50465
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50466
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#00AB45]" }),
50467
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPercentRange(effectiveLegend.green_min, effectiveLegend.green_max) })
50399
50468
  ] }),
50400
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50401
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 sm:w-2.5 sm:h-2.5 rounded-[12px] bg-[#FFB020]/90 ring-1 ring-[#FFB020]/20" }),
50402
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: formatPercentRange(effectiveLegend.yellow_min, effectiveLegend.yellow_max) })
50469
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50470
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#FFB020]" }),
50471
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPercentRange(effectiveLegend.yellow_min, effectiveLegend.yellow_max) })
50403
50472
  ] }),
50404
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50405
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 sm:w-2.5 sm:h-2.5 rounded-[12px] bg-[#E34329]/90 ring-1 ring-[#E34329]/20" }),
50406
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: formatPercentRange(effectiveLegend.red_min, effectiveLegend.red_max) })
50473
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50474
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#E34329]" }),
50475
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPercentRange(effectiveLegend.red_min, effectiveLegend.red_max) })
50407
50476
  ] })
50408
50477
  ] }),
50409
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-6 bg-gray-200 mx-1" }),
50410
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50411
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-3 h-3 sm:w-5 sm:h-5 bg-[#E34329]/90 rounded-full ring-1 ring-[#E34329]/20 text-white font-bold text-[8px] sm:text-xs", children: "!" }),
50412
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: exclamationLabel })
50478
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-slate-200 mx-1" }),
50479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50480
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-3 h-3 sm:w-4 sm:h-4 bg-[#E34329] rounded-full text-white font-bold text-[8px] sm:text-[10px]", children: "!" }),
50481
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: exclamationLabel })
50413
50482
  ] })
50414
50483
  ] });
50415
50484
  };
@@ -50519,6 +50588,7 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50519
50588
  isPdfMode = false,
50520
50589
  customWorkspacePositions,
50521
50590
  lineNames = {},
50591
+ lineOrder = [],
50522
50592
  factoryView = "factory",
50523
50593
  line2Uuid = "line-2",
50524
50594
  className = "",
@@ -50529,7 +50599,8 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50529
50599
  videoStreamsLoading = false,
50530
50600
  displayNames = {},
50531
50601
  onWorkspaceHover,
50532
- onWorkspaceHoverEnd
50602
+ onWorkspaceHoverEnd,
50603
+ toolbarRightContent
50533
50604
  }) => {
50534
50605
  const dashboardConfig = useDashboardConfig();
50535
50606
  const mapViewEnabled = dashboardConfig?.mapViewConfig?.enabled ?? false;
@@ -50563,29 +50634,30 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50563
50634
  () => viewMode === "video" ? getVideoGridLegendLabel(workspaces) : MAP_GRID_LEGEND_LABEL,
50564
50635
  [viewMode, workspaces]
50565
50636
  );
50566
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative w-full h-full overflow-hidden ${className}`, children: [
50567
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-0 left-2 sm:left-4 right-2 sm:right-8 z-20", children: [
50568
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-row items-center justify-between py-1 sm:py-1.5 gap-2", children: [
50569
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }),
50637
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col w-full h-full overflow-hidden bg-slate-50/50 ${className}`, children: [
50638
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-none px-4 py-3 z-20 flex flex-row items-center justify-between gap-4", children: [
50639
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex bg-white/95 rounded-lg shadow-sm px-4 py-2 border border-slate-200/60 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }) }),
50640
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-3 shrink-0", children: [
50641
+ toolbarRightContent,
50570
50642
  mapViewEnabled && /* @__PURE__ */ jsxRuntime.jsx(
50571
50643
  "button",
50572
50644
  {
50573
50645
  onClick: handleViewModeToggle,
50574
- className: "flex items-center gap-2 px-3 py-1.5 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 transition-colors duration-200",
50646
+ className: "flex items-center gap-2 px-3 py-1.5 bg-white border border-slate-200 rounded-md shadow-sm hover:bg-slate-50 transition-colors duration-200 text-slate-700",
50575
50647
  title: viewMode === "video" ? "Switch to Map View" : "Switch to Video View",
50576
50648
  children: viewMode === "video" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
50577
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Map, { className: "w-4 h-4 text-gray-600" }),
50578
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm text-gray-700", children: "Map View" })
50649
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Map, { className: "w-4 h-4 text-slate-500" }),
50650
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm font-medium", children: "Map View" })
50579
50651
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
50580
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { className: "w-4 h-4 text-gray-600" }),
50581
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm text-gray-700", children: "Video View" })
50652
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { className: "w-4 h-4 text-slate-500" }),
50653
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm font-medium", children: "Video View" })
50582
50654
  ] })
50583
50655
  }
50584
50656
  )
50585
- ] }),
50586
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden mt-1 mr-32", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) })
50657
+ ] })
50587
50658
  ] }),
50588
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-14 sm:top-16 left-0 right-0 bottom-0", children: /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { mode: "wait", children: viewMode === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
50659
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden px-3 py-2 bg-white border-b border-slate-200/60 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }),
50660
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { mode: "wait", children: viewMode === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
50589
50661
  motion.div,
50590
50662
  {
50591
50663
  initial: { opacity: 0 },
@@ -50597,6 +50669,8 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50597
50669
  VideoGridViewComponent,
50598
50670
  {
50599
50671
  workspaces,
50672
+ lineNames,
50673
+ lineOrder,
50600
50674
  videoSources,
50601
50675
  videoStreamsByWorkspaceId,
50602
50676
  videoStreamsLoading,
@@ -59459,6 +59533,7 @@ function HomeView({
59459
59533
  [dbLines]
59460
59534
  );
59461
59535
  const isSupervisor = user?.role_level === "supervisor";
59536
+ const hasUser = Boolean(user);
59462
59537
  const visibleLineIds = React142.useMemo(() => {
59463
59538
  const scoped = Array.from(new Set(allLineIds.filter(Boolean)));
59464
59539
  if (enabledLineIdSet.size === 0) {
@@ -59468,57 +59543,100 @@ function HomeView({
59468
59543
  }, [allLineIds, enabledLineIdSet]);
59469
59544
  const fallbackLineId = visibleLineIds[0] || defaultLineId;
59470
59545
  const defaultHomeLineId = fallbackLineId;
59471
- const availableLineIds = React142.useMemo(() => {
59472
- if (visibleLineIds.length > 1) {
59473
- return [...visibleLineIds, factoryViewId];
59474
- }
59475
- return visibleLineIds;
59476
- }, [visibleLineIds, factoryViewId]);
59477
- const LINE_FILTER_STORAGE_KEY = "optifye_home_line_filter";
59478
- const [selectedLineId, setSelectedLineId] = React142.useState(() => {
59546
+ const visibleLineIdsKey = visibleLineIds.join(",");
59547
+ const normalizeSelectedLineIds = (lineIdsToNormalize) => {
59548
+ const allowedLineIds = new Set(visibleLineIds);
59549
+ const requestedLineIds = new Set((lineIdsToNormalize || []).filter(Boolean));
59550
+ const normalized = visibleLineIds.filter((lineId) => requestedLineIds.has(lineId) && allowedLineIds.has(lineId));
59551
+ if (normalized.length > 0) {
59552
+ return normalized;
59553
+ }
59554
+ if (defaultHomeLineId && allowedLineIds.has(defaultHomeLineId)) {
59555
+ return [defaultHomeLineId];
59556
+ }
59557
+ return visibleLineIds.length > 0 ? [visibleLineIds[0]] : [];
59558
+ };
59559
+ const selectedLineIdsEqual = (left, right) => {
59560
+ if (left.length !== right.length) return false;
59561
+ return left.every((value, index) => value === right[index]);
59562
+ };
59563
+ const isAllLinesSelection = (lineIdsToCheck) => visibleLineIds.length > 0 && lineIdsToCheck.length === visibleLineIds.length;
59564
+ const readPersistedSelectedLineIds = () => {
59479
59565
  if (typeof window === "undefined") {
59480
- return defaultHomeLineId;
59566
+ return null;
59481
59567
  }
59482
59568
  try {
59483
- const savedLineId = sessionStorage.getItem(LINE_FILTER_STORAGE_KEY);
59484
- if (savedLineId) {
59485
- if (availableLineIds.includes(savedLineId)) {
59486
- return savedLineId;
59569
+ const savedLineIds = sessionStorage.getItem("optifye_home_line_filters_v2");
59570
+ if (savedLineIds) {
59571
+ const parsedLineIds = JSON.parse(savedLineIds);
59572
+ if (Array.isArray(parsedLineIds)) {
59573
+ return normalizeSelectedLineIds(parsedLineIds.map((lineId) => String(lineId)));
59574
+ }
59575
+ }
59576
+ const legacyLineId = sessionStorage.getItem("optifye_home_line_filter");
59577
+ if (legacyLineId) {
59578
+ if (legacyLineId === factoryViewId) {
59579
+ return normalizeSelectedLineIds(visibleLineIds);
59487
59580
  }
59581
+ return normalizeSelectedLineIds([legacyLineId]);
59488
59582
  }
59489
59583
  } catch (error) {
59490
59584
  console.warn("Failed to read line filter from sessionStorage:", error);
59491
59585
  }
59492
- return defaultHomeLineId;
59493
- });
59586
+ return null;
59587
+ };
59588
+ const [selectedLineIds, setSelectedLineIds] = React142.useState(() => readPersistedSelectedLineIds() || normalizeSelectedLineIds([defaultHomeLineId]));
59589
+ const [isLineSelectorOpen, setIsLineSelectorOpen] = React142.useState(false);
59590
+ const [pendingSelectedLineIds, setPendingSelectedLineIds] = React142.useState([]);
59591
+ const lineSelectorRef = React142.useRef(null);
59494
59592
  React142.useEffect(() => {
59495
- if (!user || availableLineIds.length === 0) {
59593
+ if (isLineSelectorOpen) {
59594
+ setPendingSelectedLineIds(selectedLineIds);
59595
+ }
59596
+ }, [isLineSelectorOpen, selectedLineIds]);
59597
+ React142.useEffect(() => {
59598
+ if (!hasUser || visibleLineIds.length === 0) {
59496
59599
  return;
59497
59600
  }
59498
- try {
59499
- const savedLineId = sessionStorage.getItem(LINE_FILTER_STORAGE_KEY);
59500
- if (savedLineId && availableLineIds.includes(savedLineId)) {
59501
- if (savedLineId !== selectedLineId) {
59502
- setSelectedLineId(savedLineId);
59503
- }
59504
- return;
59601
+ const restoredLineIds = readPersistedSelectedLineIds();
59602
+ setSelectedLineIds((previousSelectedLineIds) => {
59603
+ if (restoredLineIds) {
59604
+ return selectedLineIdsEqual(restoredLineIds, previousSelectedLineIds) ? previousSelectedLineIds : restoredLineIds;
59505
59605
  }
59606
+ const normalizedCurrentLineIds = normalizeSelectedLineIds(previousSelectedLineIds);
59607
+ return selectedLineIdsEqual(normalizedCurrentLineIds, previousSelectedLineIds) ? previousSelectedLineIds : normalizedCurrentLineIds;
59608
+ });
59609
+ }, [hasUser, visibleLineIdsKey, defaultHomeLineId, factoryViewId]);
59610
+ React142.useEffect(() => {
59611
+ try {
59612
+ sessionStorage.setItem("optifye_home_line_filters_v2", JSON.stringify(selectedLineIds));
59613
+ sessionStorage.removeItem("optifye_home_line_filter");
59506
59614
  } catch (error) {
59507
- console.warn("Failed to read line filter from sessionStorage:", error);
59615
+ console.warn("Failed to save line filter to sessionStorage:", error);
59508
59616
  }
59509
- if (availableLineIds.includes(selectedLineId)) {
59617
+ }, [selectedLineIds]);
59618
+ React142.useEffect(() => {
59619
+ if (!isLineSelectorOpen) {
59510
59620
  return;
59511
59621
  }
59512
- if (defaultHomeLineId !== selectedLineId) {
59513
- setSelectedLineId(defaultHomeLineId);
59514
- }
59515
- }, [
59516
- user,
59517
- availableLineIds,
59518
- defaultHomeLineId,
59519
- selectedLineId,
59520
- LINE_FILTER_STORAGE_KEY
59521
- ]);
59622
+ const handleClickOutside = (event) => {
59623
+ if (lineSelectorRef.current && !lineSelectorRef.current.contains(event.target)) {
59624
+ setIsLineSelectorOpen(false);
59625
+ }
59626
+ };
59627
+ document.addEventListener("mousedown", handleClickOutside);
59628
+ return () => {
59629
+ document.removeEventListener("mousedown", handleClickOutside);
59630
+ };
59631
+ }, [isLineSelectorOpen]);
59632
+ const primarySelectedLineId = selectedLineIds[0] || defaultHomeLineId;
59633
+ const isMultiLineSelection = selectedLineIds.length > 1;
59634
+ const selectedLineIdsKey = selectedLineIds.join(",");
59635
+ const selectedLineIdSet = React142.useMemo(
59636
+ () => new Set(selectedLineIds),
59637
+ [selectedLineIds]
59638
+ );
59639
+ const metricsScopeLineId = isMultiLineSelection ? factoryViewId : primarySelectedLineId;
59522
59640
  const userCompanyId = React142.useMemo(() => {
59523
59641
  return user?.properties?.company_id || user?.company_id || entityConfig.companyId;
59524
59642
  }, [user, entityConfig.companyId]);
@@ -59538,12 +59656,8 @@ function HomeView({
59538
59656
  React142.useEffect(() => {
59539
59657
  const initDisplayNames = async () => {
59540
59658
  try {
59541
- if (selectedLineId === factoryViewId) {
59542
- for (const lineId of visibleLineIds) {
59543
- await preInitializeWorkspaceDisplayNames(lineId);
59544
- }
59545
- } else {
59546
- await preInitializeWorkspaceDisplayNames(selectedLineId);
59659
+ for (const lineId of selectedLineIds) {
59660
+ await preInitializeWorkspaceDisplayNames(lineId);
59547
59661
  }
59548
59662
  setDisplayNamesInitialized(true);
59549
59663
  } catch (error) {
@@ -59552,8 +59666,8 @@ function HomeView({
59552
59666
  }
59553
59667
  };
59554
59668
  initDisplayNames();
59555
- }, [selectedLineId, factoryViewId, visibleLineIds]);
59556
- const displayNameLineId = selectedLineId === factoryViewId ? void 0 : selectedLineId;
59669
+ }, [selectedLineIdsKey]);
59670
+ const displayNameLineId = isMultiLineSelection ? void 0 : primarySelectedLineId;
59557
59671
  const {
59558
59672
  displayNames: workspaceDisplayNames,
59559
59673
  loading: displayNamesLoading,
@@ -59582,7 +59696,8 @@ function HomeView({
59582
59696
  error: metricsError,
59583
59697
  refetch: refetchMetrics
59584
59698
  } = useDashboardMetrics({
59585
- lineId: selectedLineId,
59699
+ lineId: metricsScopeLineId,
59700
+ lineIds: selectedLineIds,
59586
59701
  onLineMetricsUpdate: handleLineMetricsUpdate,
59587
59702
  userAccessibleLineIds: visibleLineIds
59588
59703
  // Pass user's accessible lines for supervisor filtering
@@ -59590,10 +59705,8 @@ function HomeView({
59590
59705
  const trendGroups = React142.useMemo(() => {
59591
59706
  const lineMetricsRows = lineMetrics || [];
59592
59707
  if (!lineMetricsRows.length) return null;
59593
- if (selectedLineId === factoryViewId) {
59594
- const candidateLineIds = visibleLineIds.filter((id3) => id3 && id3 !== factoryViewId);
59595
- if (!candidateLineIds.length) return null;
59596
- const rowsForLines = lineMetricsRows.filter((row2) => candidateLineIds.includes(row2?.line_id));
59708
+ if (selectedLineIds.length > 1) {
59709
+ const rowsForLines = lineMetricsRows.filter((row2) => selectedLineIdSet.has(row2?.line_id));
59597
59710
  if (!rowsForLines.length) return null;
59598
59711
  const groupsMap = /* @__PURE__ */ new Map();
59599
59712
  rowsForLines.forEach((row2) => {
@@ -59616,16 +59729,16 @@ function HomeView({
59616
59729
  shiftId: group.shiftId
59617
59730
  }));
59618
59731
  }
59619
- const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
59732
+ const row = lineMetricsRows.find((r2) => r2?.line_id === primarySelectedLineId);
59620
59733
  if (!row?.date || row?.shift_id === void 0 || row?.shift_id === null) {
59621
59734
  return null;
59622
59735
  }
59623
59736
  return [{
59624
- lineIds: [selectedLineId],
59737
+ lineIds: [primarySelectedLineId],
59625
59738
  date: row.date,
59626
59739
  shiftId: row.shift_id
59627
59740
  }];
59628
- }, [selectedLineId, factoryViewId, visibleLineIds, lineMetrics]);
59741
+ }, [lineMetrics, primarySelectedLineId, selectedLineIdSet, selectedLineIds.length]);
59629
59742
  const trendOptions = React142.useMemo(() => {
59630
59743
  if (!trendGroups || !userCompanyId) return null;
59631
59744
  return {
@@ -59653,17 +59766,18 @@ function HomeView({
59653
59766
  }, [workspaceMetrics, metricsLoading, metricsError]);
59654
59767
  const kpis = React142.useMemo(() => {
59655
59768
  const lineMetricsRows = lineMetrics || [];
59656
- if (selectedLineId === factoryViewId) {
59657
- if (metricsLoading && lineMetricsRows.length === 0) return null;
59658
- return aggregateKPIsFromLineMetricsRows(lineMetricsRows);
59769
+ if (selectedLineIds.length > 1) {
59770
+ const rowsForSelectedLines = lineMetricsRows.filter((row2) => selectedLineIdSet.has(row2?.line_id));
59771
+ if (metricsLoading && rowsForSelectedLines.length === 0) return null;
59772
+ return aggregateKPIsFromLineMetricsRows(rowsForSelectedLines);
59659
59773
  }
59660
- const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
59774
+ const row = lineMetricsRows.find((r2) => r2?.line_id === primarySelectedLineId);
59661
59775
  if (!row) {
59662
59776
  if (metricsLoading) return null;
59663
59777
  return buildKPIsFromLineMetricsRow(null);
59664
59778
  }
59665
59779
  return buildKPIsFromLineMetricsRow(row);
59666
- }, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
59780
+ }, [lineMetrics, metricsLoading, primarySelectedLineId, selectedLineIdSet, selectedLineIds.length]);
59667
59781
  const kpisWithTrend = React142.useMemo(() => {
59668
59782
  if (!kpis) return null;
59669
59783
  if (!kpiTrend) return kpis;
@@ -59688,30 +59802,30 @@ function HomeView({
59688
59802
  };
59689
59803
  }, [kpis, kpiTrend]);
59690
59804
  const selectedLineMeta = React142.useMemo(
59691
- () => dbLines.find((line) => line.id === selectedLineId),
59692
- [dbLines, selectedLineId]
59805
+ () => selectedLineIds.length === 1 ? dbLines.find((line) => line.id === primarySelectedLineId) : void 0,
59806
+ [dbLines, primarySelectedLineId, selectedLineIds.length]
59693
59807
  );
59694
- const selectedMonitoringMode = selectedLineId === factoryViewId ? "output" : selectedLineMeta?.monitoring_mode ?? "output";
59808
+ const selectedMonitoringMode = selectedLineIds.length === 1 ? selectedLineMeta?.monitoring_mode ?? "output" : "output";
59695
59809
  const isUptimeMode = selectedMonitoringMode === "uptime";
59696
59810
  const averageIdleTimeSeconds = React142.useMemo(() => {
59697
59811
  if (!isUptimeMode) return null;
59698
- const targetWorkspaces = selectedLineId === factoryViewId ? workspaceMetrics : workspaceMetrics.filter((ws) => ws.line_id === selectedLineId);
59812
+ const targetWorkspaces = workspaceMetrics.filter((ws) => ws.line_id === primarySelectedLineId);
59699
59813
  const idleValues = targetWorkspaces.map((ws) => ws.idle_time).filter((value) => Number.isFinite(value));
59700
59814
  if (idleValues.length === 0) return 0;
59701
59815
  const totalIdle = idleValues.reduce((sum, value) => sum + value, 0);
59702
59816
  return totalIdle / idleValues.length;
59703
- }, [isUptimeMode, selectedLineId, factoryViewId, workspaceMetrics]);
59817
+ }, [isUptimeMode, primarySelectedLineId, workspaceMetrics]);
59704
59818
  const {
59705
59819
  activeBreaks: allActiveBreaks,
59706
59820
  isLoading: breaksLoading,
59707
59821
  error: breaksError
59708
- } = useActiveBreaks(allLineIds);
59822
+ } = useActiveBreaks(visibleLineIds);
59709
59823
  const activeBreaks = React142.useMemo(() => {
59710
- if (selectedLineId === factoryViewId) {
59824
+ if (isAllLinesSelection(selectedLineIds)) {
59711
59825
  return allActiveBreaks;
59712
59826
  }
59713
- return allActiveBreaks.filter((breakItem) => breakItem.lineId === selectedLineId);
59714
- }, [allActiveBreaks, selectedLineId, factoryViewId]);
59827
+ return allActiveBreaks.filter((breakItem) => selectedLineIdSet.has(breakItem.lineId));
59828
+ }, [allActiveBreaks, selectedLineIdSet, selectedLineIds]);
59715
59829
  const activeBreakLineIds = React142.useMemo(
59716
59830
  () => new Set(activeBreaks.map((breakItem) => breakItem.lineId)),
59717
59831
  [activeBreaks]
@@ -60018,7 +60132,7 @@ function HomeView({
60018
60132
  // Round to 1 decimal
60019
60133
  kpisWithTrend?.avgCycleTime?.change,
60020
60134
  kpisWithTrend?.qualityCompliance?.value ? Math.round(kpisWithTrend.qualityCompliance.value) : null,
60021
- selectedLineId
60135
+ selectedLineIdsKey
60022
60136
  ]);
60023
60137
  React142.useEffect(() => {
60024
60138
  setIsHydrated(true);
@@ -60035,27 +60149,62 @@ function HomeView({
60035
60149
  setErrorMessage(null);
60036
60150
  }
60037
60151
  }, [metricsError]);
60038
- const handleLineChange = React142.useCallback((value) => {
60152
+ const getTrackedLineScope = React142.useCallback((lineIdsForScope) => {
60153
+ if (isAllLinesSelection(lineIdsForScope)) {
60154
+ return factoryViewId;
60155
+ }
60156
+ if (lineIdsForScope.length === 1) {
60157
+ return lineIdsForScope[0];
60158
+ }
60159
+ return "custom_multi";
60160
+ }, [factoryViewId, visibleLineIds.length]);
60161
+ const getLineSelectionLabel = React142.useCallback((lineIdsForScope) => {
60162
+ if (isAllLinesSelection(lineIdsForScope)) {
60163
+ return "All Lines";
60164
+ }
60165
+ if (lineIdsForScope.length === 1) {
60166
+ const lineId = lineIdsForScope[0];
60167
+ return mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}`;
60168
+ }
60169
+ return `${lineIdsForScope.length} Lines Selected`;
60170
+ }, [mergedLineNames, visibleLineIds.length]);
60171
+ const updateSelectedLineIds = React142.useCallback((nextLineIds) => {
60172
+ const normalizedLineIds = normalizeSelectedLineIds(nextLineIds);
60173
+ if (selectedLineIdsEqual(normalizedLineIds, selectedLineIds)) {
60174
+ return;
60175
+ }
60039
60176
  setIsChangingFilter(true);
60040
- setSelectedLineId(value);
60177
+ setSelectedLineIds(normalizedLineIds);
60041
60178
  trackCoreEvent("monitor line filter changed", {
60042
- previous_line_id: selectedLineId,
60043
- new_line_id: value,
60044
- line_name: mergedLineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
60179
+ previous_line_id: getTrackedLineScope(selectedLineIds),
60180
+ new_line_id: getTrackedLineScope(normalizedLineIds),
60181
+ previous_line_ids: selectedLineIds,
60182
+ new_line_ids: normalizedLineIds,
60183
+ selected_line_count: normalizedLineIds.length,
60184
+ selection_mode: isAllLinesSelection(normalizedLineIds) ? "all" : normalizedLineIds.length === 1 ? "single" : "custom",
60185
+ line_name: getLineSelectionLabel(normalizedLineIds)
60045
60186
  });
60046
- try {
60047
- sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
60048
- } catch (error) {
60049
- console.warn("Failed to save line filter to sessionStorage:", error);
60187
+ }, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
60188
+ React142.useCallback(() => {
60189
+ updateSelectedLineIds(visibleLineIds);
60190
+ }, [updateSelectedLineIds, visibleLineIds]);
60191
+ React142.useCallback((lineId) => {
60192
+ const currentSelection = new Set(selectedLineIds);
60193
+ if (currentSelection.has(lineId)) {
60194
+ if (currentSelection.size <= 1) {
60195
+ return;
60196
+ }
60197
+ currentSelection.delete(lineId);
60198
+ } else {
60199
+ currentSelection.add(lineId);
60050
60200
  }
60051
- }, [LINE_FILTER_STORAGE_KEY, selectedLineId, mergedLineNames, factoryViewId]);
60201
+ updateSelectedLineIds(Array.from(currentSelection));
60202
+ }, [selectedLineIds, updateSelectedLineIds]);
60052
60203
  React142.useEffect(() => {
60053
60204
  if (!metricsLoading && isChangingFilter) {
60054
- if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
60055
- setIsChangingFilter(false);
60056
- }
60205
+ setIsChangingFilter(false);
60057
60206
  }
60058
- }, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
60207
+ }, [metricsLoading, isChangingFilter]);
60059
60208
  React142.useEffect(() => {
60060
60209
  if (!metricsLoading && !hasInitialDataLoaded) {
60061
60210
  setHasInitialDataLoaded(true);
@@ -60074,11 +60223,107 @@ function HomeView({
60074
60223
  if (visibleLineIds.length <= 1) {
60075
60224
  return null;
60076
60225
  }
60077
- return /* @__PURE__ */ jsxRuntime.jsxs(Select, { onValueChange: handleLineChange, value: selectedLineId, children: [
60078
- /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[200px] bg-white border border-gray-200 shadow-sm rounded-md h-9 sm:h-9 text-xs sm:text-sm px-2 sm:px-3", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Select a line" }) }),
60079
- /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: mergedLineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
60226
+ const allLinesSelected = isAllLinesSelection(pendingSelectedLineIds);
60227
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: lineSelectorRef, className: "relative", children: [
60228
+ /* @__PURE__ */ jsxRuntime.jsxs(
60229
+ "button",
60230
+ {
60231
+ type: "button",
60232
+ onClick: () => setIsLineSelectorOpen((previous) => !previous),
60233
+ className: "flex min-w-[180px] items-center justify-between gap-2 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-left text-sm font-medium shadow-sm transition-colors hover:bg-slate-50 text-slate-700",
60234
+ "aria-haspopup": "menu",
60235
+ "aria-expanded": isLineSelectorOpen,
60236
+ "aria-label": "Select lines",
60237
+ children: [
60238
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: getLineSelectionLabel(selectedLineIds) }),
60239
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-4 w-4 text-slate-400 transition-transform ${isLineSelectorOpen ? "rotate-180" : ""}` })
60240
+ ]
60241
+ }
60242
+ ),
60243
+ isLineSelectorOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 top-full z-50 mt-2 w-[280px] rounded-lg border border-slate-200 bg-white p-3 shadow-xl flex flex-col gap-2", children: [
60244
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pb-2 border-b border-slate-100", children: [
60245
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-slate-800", children: "Select Lines" }),
60246
+ /* @__PURE__ */ jsxRuntime.jsx(
60247
+ "button",
60248
+ {
60249
+ type: "button",
60250
+ onClick: () => setPendingSelectedLineIds([]),
60251
+ className: "text-xs font-medium text-blue-600 hover:text-blue-700 transition-colors",
60252
+ children: "Clear All"
60253
+ }
60254
+ )
60255
+ ] }),
60256
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-sm font-medium text-slate-900 hover:bg-slate-50 transition-colors", children: [
60257
+ /* @__PURE__ */ jsxRuntime.jsx(
60258
+ "input",
60259
+ {
60260
+ type: "checkbox",
60261
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
60262
+ checked: allLinesSelected,
60263
+ onChange: () => setPendingSelectedLineIds(allLinesSelected ? [] : visibleLineIds)
60264
+ }
60265
+ ),
60266
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "All Lines" })
60267
+ ] }),
60268
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 space-y-0.5 overflow-y-auto pr-1", children: visibleLineIds.map((lineId) => {
60269
+ const isChecked = pendingSelectedLineIds.includes(lineId);
60270
+ return /* @__PURE__ */ jsxRuntime.jsxs(
60271
+ "label",
60272
+ {
60273
+ className: "flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors text-slate-700 hover:bg-slate-50",
60274
+ children: [
60275
+ /* @__PURE__ */ jsxRuntime.jsx(
60276
+ "input",
60277
+ {
60278
+ type: "checkbox",
60279
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
60280
+ checked: isChecked,
60281
+ onChange: () => {
60282
+ setPendingSelectedLineIds((prev) => {
60283
+ const current = new Set(prev);
60284
+ if (current.has(lineId)) {
60285
+ current.delete(lineId);
60286
+ } else {
60287
+ current.add(lineId);
60288
+ }
60289
+ return Array.from(current);
60290
+ });
60291
+ }
60292
+ }
60293
+ ),
60294
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}` })
60295
+ ]
60296
+ },
60297
+ lineId
60298
+ );
60299
+ }) }),
60300
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-3 pb-1 mt-1 border-t border-slate-100 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
60301
+ "button",
60302
+ {
60303
+ type: "button",
60304
+ onClick: () => {
60305
+ if (pendingSelectedLineIds.length > 0) {
60306
+ updateSelectedLineIds(pendingSelectedLineIds);
60307
+ }
60308
+ setIsLineSelectorOpen(false);
60309
+ },
60310
+ disabled: pendingSelectedLineIds.length === 0,
60311
+ className: "bg-blue-600 text-white px-4 py-1.5 rounded-md text-sm font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors w-full",
60312
+ children: "Apply"
60313
+ }
60314
+ ) })
60315
+ ] }) : null
60080
60316
  ] });
60081
- }, [availableLineIds, handleLineChange, selectedLineId, mergedLineNames, factoryViewId, visibleLineIds.length]);
60317
+ }, [
60318
+ getLineSelectionLabel,
60319
+ isLineSelectorOpen,
60320
+ mergedLineNames,
60321
+ selectedLineIds,
60322
+ pendingSelectedLineIds,
60323
+ visibleLineIds,
60324
+ updateSelectedLineIds,
60325
+ isAllLinesSelection
60326
+ ]);
60082
60327
  const useSmoothLoading = (isLoading, minDuration = 400) => {
60083
60328
  const [showLoading, setShowLoading] = React142.useState(isLoading);
60084
60329
  const loadingStartRef2 = React142.useRef(null);
@@ -60124,11 +60369,11 @@ function HomeView({
60124
60369
  const isDataLoading = metricsLoading || displayNamesLoading && workspaceMetrics.length === 0;
60125
60370
  const hasKpiDataReady = React142.useMemo(() => {
60126
60371
  const lineMetricsRows = lineMetrics || [];
60127
- if (selectedLineId === factoryViewId) {
60128
- return lineMetricsRows.length > 0;
60372
+ if (selectedLineIds.length > 1) {
60373
+ return lineMetricsRows.some((row) => selectedLineIdSet.has(row?.line_id));
60129
60374
  }
60130
- return lineMetricsRows.some((row) => row?.line_id === selectedLineId);
60131
- }, [lineMetrics, selectedLineId, factoryViewId]);
60375
+ return lineMetricsRows.some((row) => row?.line_id === primarySelectedLineId);
60376
+ }, [lineMetrics, primarySelectedLineId, selectedLineIdSet, selectedLineIds.length]);
60132
60377
  const isKpiLoading = !hasKpiDataReady;
60133
60378
  React142.useEffect(() => {
60134
60379
  const minLoadingDurationMs = 250;
@@ -60190,76 +60435,77 @@ function HomeView({
60190
60435
  DashboardHeader,
60191
60436
  {
60192
60437
  lineTitle,
60193
- lineId: selectedLineId === factoryViewId ? allLineIds[0] : selectedLineId,
60438
+ lineId: primarySelectedLineId,
60194
60439
  className: "w-full",
60195
60440
  headerControls: kpiSectionControl
60196
60441
  }
60197
60442
  ) }) }),
60198
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative", children: [
60199
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-2 top-1 sm:right-6 sm:top-3 z-30 flex items-center space-x-2", children: lineSelectorComponent && lineSelectorComponent }),
60200
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full sm:h-full min-h-[calc(100vh-100px)] sm:min-h-0", children: workspaceMetricsWithBreakState.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
60201
- motion.div,
60202
- {
60203
- initial: { opacity: 0, scale: 0.98 },
60204
- animate: { opacity: 1, scale: 1 },
60205
- transition: { duration: 0.3 },
60443
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative flex flex-col", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[calc(100vh-100px)] sm:min-h-0", children: workspaceMetricsWithBreakState.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
60444
+ motion.div,
60445
+ {
60446
+ initial: { opacity: 0, scale: 0.98 },
60447
+ animate: { opacity: 1, scale: 1 },
60448
+ transition: { duration: 0.3 },
60449
+ className: "h-full",
60450
+ children: React142__namespace.default.createElement(WorkspaceGrid, {
60451
+ workspaces: workspaceMetricsWithBreakState,
60452
+ lineNames: mergedLineNames,
60453
+ lineOrder: selectedLineIds,
60454
+ factoryView: factoryViewId,
60455
+ legend: efficiencyLegend,
60456
+ videoSources,
60457
+ videoStreamsByWorkspaceId,
60458
+ videoStreamsLoading,
60459
+ displayNames: workspaceDisplayNames,
60460
+ hasFlowBuffers,
60206
60461
  className: "h-full",
60207
- children: React142__namespace.default.createElement(WorkspaceGrid, {
60208
- workspaces: workspaceMetricsWithBreakState,
60209
- lineNames,
60210
- factoryView: factoryViewId,
60211
- legend: efficiencyLegend,
60212
- videoSources,
60213
- videoStreamsByWorkspaceId,
60214
- videoStreamsLoading,
60215
- displayNames: workspaceDisplayNames,
60216
- hasFlowBuffers,
60217
- className: "h-full",
60218
- onWorkspaceHover: handleWorkspaceHover,
60219
- onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60220
- })
60221
- },
60222
- selectedLineId
60223
- ) : !shouldShowDataLoading && hasInitialDataLoaded ? /* @__PURE__ */ jsxRuntime.jsx(
60224
- motion.div,
60225
- {
60226
- initial: { opacity: 0 },
60227
- animate: { opacity: 1 },
60228
- transition: { duration: 0.3 },
60229
- children: /* @__PURE__ */ jsxRuntime.jsx(NoWorkspaceData, { message: "No workspace data available. Select another line or check configurations." })
60230
- }
60231
- ) : /* @__PURE__ */ jsxRuntime.jsx(
60232
- motion.div,
60233
- {
60234
- initial: { opacity: 0, scale: 0.98 },
60235
- animate: { opacity: 1, scale: 1 },
60236
- transition: { duration: 0.3 },
60462
+ toolbarRightContent: lineSelectorComponent,
60463
+ onWorkspaceHover: handleWorkspaceHover,
60464
+ onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60465
+ })
60466
+ },
60467
+ selectedLineIdsKey
60468
+ ) : !shouldShowDataLoading && hasInitialDataLoaded ? /* @__PURE__ */ jsxRuntime.jsx(
60469
+ motion.div,
60470
+ {
60471
+ initial: { opacity: 0 },
60472
+ animate: { opacity: 1 },
60473
+ transition: { duration: 0.3 },
60474
+ children: /* @__PURE__ */ jsxRuntime.jsx(NoWorkspaceData, { message: "No workspace data available. Adjust the selected lines or check configurations." })
60475
+ }
60476
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
60477
+ motion.div,
60478
+ {
60479
+ initial: { opacity: 0, scale: 0.98 },
60480
+ animate: { opacity: 1, scale: 1 },
60481
+ transition: { duration: 0.3 },
60482
+ className: "h-full",
60483
+ children: React142__namespace.default.createElement(WorkspaceGrid, {
60484
+ workspaces: [],
60485
+ // Show empty grid while loading
60486
+ lineNames: mergedLineNames,
60487
+ lineOrder: selectedLineIds,
60488
+ factoryView: factoryViewId,
60489
+ legend: efficiencyLegend,
60490
+ videoSources,
60491
+ videoStreamsByWorkspaceId,
60492
+ videoStreamsLoading,
60493
+ displayNames: workspaceDisplayNames,
60494
+ hasFlowBuffers,
60237
60495
  className: "h-full",
60238
- children: React142__namespace.default.createElement(WorkspaceGrid, {
60239
- workspaces: [],
60240
- // Show empty grid while loading
60241
- lineNames,
60242
- factoryView: factoryViewId,
60243
- legend: efficiencyLegend,
60244
- videoSources,
60245
- videoStreamsByWorkspaceId,
60246
- videoStreamsLoading,
60247
- displayNames: workspaceDisplayNames,
60248
- hasFlowBuffers,
60249
- className: "h-full",
60250
- onWorkspaceHover: handleWorkspaceHover,
60251
- onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60252
- })
60253
- },
60254
- selectedLineId
60255
- ) })
60256
- ] })
60496
+ toolbarRightContent: lineSelectorComponent,
60497
+ onWorkspaceHover: handleWorkspaceHover,
60498
+ onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60499
+ })
60500
+ },
60501
+ selectedLineIdsKey
60502
+ ) }) })
60257
60503
  ] }),
60258
60504
  /* @__PURE__ */ jsxRuntime.jsx(
60259
60505
  BreakNotificationPopup,
60260
60506
  {
60261
60507
  activeBreaks,
60262
- lineNames,
60508
+ lineNames: mergedLineNames,
60263
60509
  isVisible: !breaksLoading && !breaksError && !breakNotificationsDismissed,
60264
60510
  onDismiss: () => setBreakNotificationsDismissed(true)
60265
60511
  }
@@ -61408,9 +61654,9 @@ var MetricCards = React142.memo(({
61408
61654
  animate: "animate",
61409
61655
  className: `grid grid-cols-1 sm:grid-cols-2 ${largeGridColumns} gap-3 sm:gap-4 mb-2 h-auto lg:flex-[2] lg:min-h-[250px]`,
61410
61656
  children: [
61411
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61412
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 mb-2 text-center", children: "Line Output" }),
61413
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
61657
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61658
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Line Output" }) }),
61659
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
61414
61660
  OutputProgressChart,
61415
61661
  {
61416
61662
  currentOutput: lineInfo?.metrics.current_output || 0,
@@ -61418,19 +61664,19 @@ var MetricCards = React142.memo(({
61418
61664
  }
61419
61665
  ) }) })
61420
61666
  ] }),
61421
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61422
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center mb-2", children: "Underperforming Workspaces" }),
61423
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: [
61424
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold text-red-600", children: lineInfo?.metrics.underperforming_workspaces }),
61425
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xl sm:text-2xl md:text-2xl lg:text-3xl text-gray-500 ml-1 sm:ml-2", children: [
61667
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61668
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Underperforming Workspaces" }) }),
61669
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline", children: [
61670
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap tracking-tight text-4xl sm:text-5xl md:text-5xl lg:text-4xl xl:text-5xl 2xl:text-6xl font-bold text-red-600", children: lineInfo?.metrics.underperforming_workspaces }),
61671
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xl sm:text-2xl md:text-2xl lg:text-xl xl:text-2xl 2xl:text-3xl text-gray-500 ml-1 sm:ml-2", children: [
61426
61672
  "/ ",
61427
61673
  lineInfo?.metrics.total_workspaces
61428
61674
  ] })
61429
- ] })
61675
+ ] }) })
61430
61676
  ] }),
61431
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61432
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center mb-2", children: "Average Efficiency" }),
61433
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `whitespace-nowrap tracking-tight text-3xl sm:text-4xl md:text-5xl lg:text-5xl xl:text-5xl 2xl:text-6xl font-bold ${efficiencyColorClass}`, children: [
61677
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61678
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Average Efficiency" }) }),
61679
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `whitespace-nowrap tracking-tight text-4xl sm:text-5xl md:text-5xl lg:text-4xl xl:text-5xl 2xl:text-6xl font-bold ${efficiencyColorClass}`, children: [
61434
61680
  efficiency.toFixed(1),
61435
61681
  "%"
61436
61682
  ] }) })
@@ -61439,11 +61685,11 @@ var MetricCards = React142.memo(({
61439
61685
  motion.div,
61440
61686
  {
61441
61687
  variants: itemVariants,
61442
- className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 h-[240px] sm:h-[260px] md:h-auto",
61688
+ className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto",
61443
61689
  children: [
61444
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-1.5 mb-2 relative group z-10", children: [
61445
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700", children: "Average Issue Resolution Time" }),
61446
- /* @__PURE__ */ jsxRuntime.jsx(outline.InformationCircleIcon, { className: "w-4 h-4 text-gray-400 cursor-help" }),
61690
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-10 sm:h-12 flex items-start justify-center gap-1.5 mb-2 mt-1 relative group z-10", children: [
61691
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Issue Resolution Time" }),
61692
+ /* @__PURE__ */ jsxRuntime.jsx(outline.InformationCircleIcon, { className: "w-4 h-4 text-gray-400 cursor-help flex-shrink-0" }),
61447
61693
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-full left-1/2 transform -translate-x-1/2 mt-2.5 w-[260px] p-3 bg-white rounded-lg shadow-xl border border-gray-200 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 pointer-events-none z-50", children: [
61448
61694
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center text-xs text-gray-600 leading-relaxed mb-2.5 px-1", children: "The average time a supervisor takes to resolve a workstation in red." }),
61449
61695
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-gray-50 rounded-md py-1.5 border border-gray-100/80", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-center font-medium text-[11px] text-gray-500", children: [
@@ -61453,13 +61699,13 @@ var MetricCards = React142.memo(({
61453
61699
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -top-1.5 left-1/2 transform -translate-x-1/2 w-3 h-3 bg-white border-l border-t border-gray-200 rotate-45" })
61454
61700
  ] })
61455
61701
  ] }),
61456
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-2.5rem)] flex flex-col items-center justify-center gap-3", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `whitespace-nowrap tracking-tight text-3xl sm:text-4xl md:text-5xl lg:text-5xl xl:text-5xl 2xl:text-6xl font-bold ${shouldMaskIssueResolution ? "text-gray-400" : "text-gray-900"}`, children: issueResolutionValue }) })
61702
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `whitespace-nowrap tracking-tight text-3xl sm:text-4xl md:text-4xl lg:text-3xl xl:text-4xl 2xl:text-5xl font-bold ${shouldMaskIssueResolution ? "text-gray-400" : "text-gray-900"}`, children: issueResolutionValue }) })
61457
61703
  ]
61458
61704
  }
61459
61705
  ),
61460
- showIdleTime && /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61461
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center mb-2", children: "Idle Time Breakdown" }),
61462
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx(
61706
+ showIdleTime && /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61707
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Idle Time Breakdown" }) }),
61708
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(
61463
61709
  IdleTimeReasonChart,
61464
61710
  {
61465
61711
  data: idleTimeData?.chartData,
@@ -61519,9 +61765,9 @@ var LineUptimeMetricCards = React142.memo(({
61519
61765
  animate: "animate",
61520
61766
  className: `grid grid-cols-1 sm:grid-cols-2 ${showIdleTime ? "lg:grid-cols-4" : "lg:grid-cols-3"} gap-3 sm:gap-4 mb-2 h-auto lg:flex-[2] lg:min-h-[250px]`,
61521
61767
  children: [
61522
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61523
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 mb-2 text-center", children: "Utilization" }),
61524
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
61768
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61769
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Utilization" }) }),
61770
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
61525
61771
  OutputProgressChart,
61526
61772
  {
61527
61773
  currentOutput: Number(utilizationText),
@@ -61529,17 +61775,17 @@ var LineUptimeMetricCards = React142.memo(({
61529
61775
  }
61530
61776
  ) }) })
61531
61777
  ] }),
61532
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61533
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center mb-2", children: "Stoppages" }),
61534
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold text-red-600", children: stoppages }) })
61778
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61779
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Stoppages" }) }),
61780
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap tracking-tight text-4xl sm:text-5xl md:text-5xl lg:text-4xl xl:text-5xl 2xl:text-6xl font-bold text-red-600", children: stoppages }) })
61535
61781
  ] }),
61536
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61537
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center mb-2", children: "Average Idle Time" }),
61538
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold ${idleTimeSeconds <= 0 ? "text-green-500" : idleTimeSeconds <= 300 ? "text-yellow-500" : "text-red-500"}`, children: formatIdleTime(idleTimeSeconds) }) })
61782
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61783
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Average Idle Time" }) }),
61784
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `whitespace-nowrap tracking-tight text-3xl sm:text-4xl md:text-5xl lg:text-4xl xl:text-5xl 2xl:text-6xl font-bold ${idleTimeSeconds <= 0 ? "text-green-500" : idleTimeSeconds <= 300 ? "text-yellow-500" : "text-red-500"}`, children: formatIdleTime(idleTimeSeconds) }) })
61539
61785
  ] }),
61540
- showIdleTime && /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61541
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center mb-2", children: "Idle Time Breakdown" }),
61542
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx(
61786
+ showIdleTime && /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 flex flex-col overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
61787
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Idle Time Breakdown" }) }),
61788
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(
61543
61789
  IdleTimeReasonChart,
61544
61790
  {
61545
61791
  data: idleTimeData?.chartData,