@optifye/dashboard-core 6.12.14 → 6.12.16

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
@@ -5596,8 +5596,9 @@ var dashboardService = {
5596
5596
  total_output: item.total_output || 0,
5597
5597
  avg_cycle_time: item.avg_cycle_time || 0,
5598
5598
  ideal_cycle_time: item.ideal_cycle_time ?? 0,
5599
- ideal_output: item.ideal_output || 0,
5600
- total_day_output: item.total_day_output ?? item.ideal_output ?? 0,
5599
+ ideal_output: item.target_output ?? item.ideal_output ?? 0,
5600
+ total_day_output: item.target_output ?? item.total_day_output ?? item.ideal_output ?? 0,
5601
+ target_output: item.target_output ?? item.total_day_output ?? item.ideal_output ?? 0,
5601
5602
  avg_pph: item.avg_pph || 0,
5602
5603
  pph_threshold: item.pph_threshold || 0,
5603
5604
  workspace_rank: item.workspace_rank || 0,
@@ -13996,7 +13997,7 @@ var useLineWorkspaceMetrics = (lineId, options) => {
13996
13997
  trend: item.trend_score === 1 ? 2 : 0,
13997
13998
  predicted_output: item.ideal_output || 0,
13998
13999
  efficiency: item.efficiency || 0,
13999
- action_threshold: item.total_day_output || 0,
14000
+ action_threshold: item.target_output ?? item.total_day_output_recalculated ?? item.total_day_output ?? 0,
14000
14001
  idle_time: item.idle_time ?? void 0,
14001
14002
  idle_time_hourly: item.idle_time_hourly || null,
14002
14003
  shift_start: item.shift_start || void 0,
@@ -14677,7 +14678,7 @@ var transformMonitorWorkspaceMetrics = ({
14677
14678
  trend: item.trend_score === 1 ? 2 : 0,
14678
14679
  predicted_output: item.ideal_output || 0,
14679
14680
  efficiency: item.efficiency || 0,
14680
- action_threshold: item.total_day_output || 0,
14681
+ action_threshold: item.target_output ?? item.total_day_output_recalculated ?? item.total_day_output ?? 0,
14681
14682
  monitoring_mode: item.monitoring_mode ?? void 0,
14682
14683
  idle_time: idleTimeSeconds,
14683
14684
  idle_time_hourly: item.idle_time_hourly ?? null,
@@ -16847,6 +16848,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
16847
16848
  return url.includes(proxyBaseUrl);
16848
16849
  }
16849
16850
  };
16851
+ const isSnapshotStreamUrl = (url) => url.includes("/api/automation/snapshot/stream/");
16850
16852
  const getR2CameraUuid = (url) => {
16851
16853
  try {
16852
16854
  const parsed = new URL(url);
@@ -17435,11 +17437,16 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17435
17437
  return;
17436
17438
  }
17437
17439
  if (Hls__default.default.isSupported() && !isNativeHlsRef.current) {
17440
+ const usesSnapshotStream = isSnapshotStreamUrl(resolvedHlsSrc);
17438
17441
  const mergedConfig = {
17439
17442
  ...HLS_CONFIG,
17440
17443
  ...hlsConfig,
17444
+ enableWorker: usesSnapshotStream ? false : hlsConfig?.enableWorker ?? HLS_CONFIG.enableWorker,
17441
17445
  xhrSetup: (xhr, url) => {
17442
17446
  const usesProxy = isProxyUrl(url);
17447
+ if (isSnapshotStreamUrl(url)) {
17448
+ xhr.withCredentials = true;
17449
+ }
17443
17450
  if (isR2WorkerUrl(url, r2WorkerDomain) || usesProxy) {
17444
17451
  if (isManifestUrl(url)) {
17445
17452
  xhr.open("GET", buildCacheBustedUrl(url), true);
@@ -17453,7 +17460,11 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17453
17460
  const isR2Url = isR2WorkerUrl(context.url, r2WorkerDomain);
17454
17461
  const isManifestRequest = isManifestUrl(context.url);
17455
17462
  const usesProxy = isProxyUrl(context.url);
17463
+ const isSnapshotStream = isSnapshotStreamUrl(context.url);
17456
17464
  let requestUrl = context.url;
17465
+ if (isSnapshotStream) {
17466
+ initParams.credentials = "include";
17467
+ }
17457
17468
  if (isR2Url || usesProxy) {
17458
17469
  if (authToken) {
17459
17470
  initParams.headers = {
@@ -18858,6 +18869,7 @@ var useAllWorkspaceMetrics = (options) => {
18858
18869
  trend_score,
18859
18870
  line_id,
18860
18871
  total_day_output,
18872
+ total_day_output_recalculated,
18861
18873
  workspace_display_name
18862
18874
  `).eq("date", fallbackQueryDate).eq("shift_id", fallbackQueryShiftId).in("workspace_id", allEnabledWorkspaceIds).order("efficiency", { ascending: false });
18863
18875
  if (fetchError) throw fetchError;
@@ -18876,7 +18888,7 @@ var useAllWorkspaceMetrics = (options) => {
18876
18888
  trend: item.trend_score === 1 ? 2 : 0,
18877
18889
  predicted_output: 0,
18878
18890
  efficiency: item.efficiency || 0,
18879
- action_threshold: item.total_day_output || 0
18891
+ action_threshold: item.target_output ?? item.total_day_output_recalculated ?? item.total_day_output ?? 0
18880
18892
  }));
18881
18893
  } else if (shiftGroups.length > 0) {
18882
18894
  console.log(`[useAllWorkspaceMetrics] \u{1F3ED} Fetching per-shift: ${shiftGroups.length} group(s)`);
@@ -18903,6 +18915,7 @@ var useAllWorkspaceMetrics = (options) => {
18903
18915
  trend_score,
18904
18916
  line_id,
18905
18917
  total_day_output,
18918
+ total_day_output_recalculated,
18906
18919
  workspace_display_name
18907
18920
  `).eq("date", group.date).eq("shift_id", group.shiftId).in("workspace_id", groupWorkspaceIds);
18908
18921
  if (fetchError) throw fetchError;
@@ -18921,7 +18934,7 @@ var useAllWorkspaceMetrics = (options) => {
18921
18934
  trend: item.trend_score === 1 ? 2 : 0,
18922
18935
  predicted_output: 0,
18923
18936
  efficiency: item.efficiency || 0,
18924
- action_threshold: item.total_day_output || 0
18937
+ action_threshold: item.target_output ?? item.total_day_output_recalculated ?? item.total_day_output ?? 0
18925
18938
  }));
18926
18939
  });
18927
18940
  const results = await Promise.all(queryPromises);
@@ -51735,12 +51748,15 @@ var LinePdfGenerator = ({
51735
51748
  doc.setTextColor(0, 0, 0);
51736
51749
  const kpiStartY = 80;
51737
51750
  const kpiSpacing = 10;
51751
+ const dayTargetOutput = Math.round(Number(
51752
+ lineInfo.metrics.output_target_recalculated ?? lineInfo.metrics.line_threshold ?? 0
51753
+ ));
51738
51754
  createKPIBox(kpiStartY);
51739
51755
  doc.setFontSize(11);
51740
51756
  doc.setFont("helvetica", "normal");
51741
51757
  doc.text("Output:", 25, kpiStartY);
51742
51758
  doc.setFont("helvetica", "bold");
51743
- doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
51759
+ doc.text(`${lineInfo.metrics.current_output} / ${dayTargetOutput}`, 120, kpiStartY);
51744
51760
  createKPIBox(kpiStartY + kpiSpacing);
51745
51761
  doc.setFont("helvetica", "normal");
51746
51762
  doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing);
@@ -51775,6 +51791,18 @@ var LinePdfGenerator = ({
51775
51791
  fallbackHours: Math.max(hourlyTimeRanges.length, 1),
51776
51792
  rounding: "floor"
51777
51793
  });
51794
+ const backendHourlyTargetOutput = lineInfo.metrics.hourly_target_output;
51795
+ const shouldUseBackendHourlyTargets = backendHourlyTargetOutput !== void 0;
51796
+ const resolveTargetForRange = (index) => {
51797
+ if (shouldUseBackendHourlyTargets) {
51798
+ if (!Array.isArray(backendHourlyTargetOutput)) return null;
51799
+ const targetValue = backendHourlyTargetOutput[index];
51800
+ if (targetValue === null || targetValue === void 0) return null;
51801
+ const numericTarget = Number(targetValue);
51802
+ return Number.isFinite(numericTarget) ? numericTarget : null;
51803
+ }
51804
+ return targetPlan.targets[index] ?? 0;
51805
+ };
51778
51806
  const skuRemarksByIndex = {};
51779
51807
  if (lineInfo.metrics.sku_segments && lineInfo.metrics.sku_segments.length > 0) {
51780
51808
  lineInfo.metrics.sku_segments.forEach((segment, segmentIndex) => {
@@ -52004,8 +52032,9 @@ var LinePdfGenerator = ({
52004
52032
  doc.setDrawColor(200, 200, 200);
52005
52033
  doc.line(20, rowBottomY, 190, rowBottomY);
52006
52034
  }
52007
- const targetForRange = targetPlan.targets[index] ?? 0;
52008
- const targetStr = targetForRange.toString();
52035
+ const targetForRange = resolveTargetForRange(index);
52036
+ const roundedTargetForRange = targetForRange === null ? null : Math.round(targetForRange);
52037
+ const targetStr = roundedTargetForRange === null ? "-" : roundedTargetForRange.toString();
52009
52038
  let remarkText = targetPlan.breakRemarks[index] || "";
52010
52039
  if (skuRemarksByIndex[index] && skuRemarksByIndex[index].length > 0) {
52011
52040
  const skuRemarks = skuRemarksByIndex[index];
@@ -52027,7 +52056,10 @@ var LinePdfGenerator = ({
52027
52056
  if (!dataCollected) {
52028
52057
  doc.setTextColor(100, 100, 100);
52029
52058
  doc.text("-", 135, yPos);
52030
- } else if (actualOutput >= targetForRange) {
52059
+ } else if (roundedTargetForRange === null) {
52060
+ doc.setTextColor(100, 100, 100);
52061
+ doc.text("-", 135, yPos);
52062
+ } else if (actualOutput >= roundedTargetForRange) {
52031
52063
  doc.setTextColor(0, 171, 69);
52032
52064
  doc.setFont("ZapfDingbats", "normal");
52033
52065
  doc.text("4", 135, yPos);
@@ -53789,6 +53821,19 @@ var WorkspacePdfGenerator = ({
53789
53821
  fallbackHours: Math.max(hourlyData.length, 1),
53790
53822
  rounding: "floor"
53791
53823
  }) : null;
53824
+ const backendHourlyTargetOutput = workspace.hourly_target_output;
53825
+ const shouldUseBackendHourlyTargets = backendHourlyTargetOutput !== void 0;
53826
+ const resolveTargetForRange = (index) => {
53827
+ if (!outputTargetPlan) return null;
53828
+ if (shouldUseBackendHourlyTargets) {
53829
+ if (!Array.isArray(backendHourlyTargetOutput)) return null;
53830
+ const targetValue = backendHourlyTargetOutput[index];
53831
+ if (targetValue === null || targetValue === void 0) return null;
53832
+ const numericTarget = Number(targetValue);
53833
+ return Number.isFinite(numericTarget) ? numericTarget : null;
53834
+ }
53835
+ return outputTargetPlan.targets[index] ?? hourlyTarget;
53836
+ };
53792
53837
  const skuRemarksByIndex = {};
53793
53838
  if (workspace.sku_segments && workspace.sku_segments.length > 0) {
53794
53839
  workspace.sku_segments.forEach((segment, segmentIndex) => {
@@ -53915,7 +53960,7 @@ var WorkspacePdfGenerator = ({
53915
53960
  const idleValue = isUptimeMode ? entry.idleMinutes ?? 0 : 0;
53916
53961
  const uptimePercent = isUptimeMode ? entry.uptimePercent ?? 0 : 0;
53917
53962
  const outputStr = dataCollected ? outputValue.toString() : "TBD";
53918
- const effectiveTarget = outputTargetPlan?.targets[index] ?? hourlyTarget;
53963
+ const effectiveTarget = resolveTargetForRange(index);
53919
53964
  let remarkText = outputTargetPlan?.breakRemarks[index] || "";
53920
53965
  if (skuRemarksByIndex[index] && skuRemarksByIndex[index].length > 0) {
53921
53966
  const skuRemarks = skuRemarksByIndex[index];
@@ -53923,7 +53968,8 @@ var WorkspacePdfGenerator = ({
53923
53968
  const compactSkuRemark = skuRemarks.length === 1 ? `${latestSku} started` : `${latestSku} started, +${skuRemarks.length - 1}`;
53924
53969
  remarkText = remarkText ? `${remarkText}, ${compactSkuRemark}` : compactSkuRemark;
53925
53970
  }
53926
- const targetStr = isUptimeMode ? dataCollected ? idleValue.toString() : "TBD" : effectiveTarget.toString();
53971
+ const roundedEffectiveTarget = effectiveTarget === null ? null : Math.round(effectiveTarget);
53972
+ const targetStr = isUptimeMode ? dataCollected ? idleValue.toString() : "TBD" : roundedEffectiveTarget === null ? "-" : roundedEffectiveTarget.toString();
53927
53973
  if (index < totalRows - 1) {
53928
53974
  const rowBottomY = headerBottomY + (index + 1) * rowHeight;
53929
53975
  doc.setDrawColor(200, 200, 200);
@@ -53966,7 +54012,10 @@ var WorkspacePdfGenerator = ({
53966
54012
  if (!dataCollected) {
53967
54013
  doc.setTextColor(100, 100, 100);
53968
54014
  doc.text("-", 135, yPos);
53969
- } else if (outputValue >= effectiveTarget) {
54015
+ } else if (roundedEffectiveTarget === null) {
54016
+ doc.setTextColor(100, 100, 100);
54017
+ doc.text("-", 135, yPos);
54018
+ } else if (outputValue >= roundedEffectiveTarget) {
53970
54019
  doc.setTextColor(0, 171, 69);
53971
54020
  doc.setFont("ZapfDingbats", "normal");
53972
54021
  doc.text("4", 135, yPos);
@@ -65535,6 +65584,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
65535
65584
  output_array: metrics2.output_array || [],
65536
65585
  output_hourly: metrics2.output_hourly,
65537
65586
  hourly_target_output: metrics2.hourly_target_output,
65587
+ hourly_target_output_by_sku: metrics2.hourly_target_output_by_sku,
65538
65588
  line_threshold: metrics2.line_threshold ?? 0,
65539
65589
  output_target_recalculated: metrics2.output_target_recalculated ?? null,
65540
65590
  threshold_pph: metrics2.threshold_pph ?? 0,
@@ -65568,7 +65618,7 @@ var transformWorkspaceMetrics = (workspaceData, lineId, companyId, queryDate, qu
65568
65618
  trend: item.trend_score === 1 ? 2 : 0,
65569
65619
  predicted_output: item.ideal_output || 0,
65570
65620
  efficiency: item.efficiency || 0,
65571
- action_threshold: item.total_day_output || 0,
65621
+ action_threshold: item.target_output ?? item.total_day_output_recalculated ?? item.total_day_output ?? 0,
65572
65622
  idle_time: item.idle_time ?? void 0,
65573
65623
  idle_time_hourly: item.idle_time_hourly || null,
65574
65624
  shift_start: item.shift_start || void 0,
@@ -67363,9 +67413,9 @@ var KPIDetailView = ({
67363
67413
  output: metric.current_output || 0,
67364
67414
  // LineMonthlyHistory expects `idealOutput` for the red dotted target ReferenceLine.
67365
67415
  // `line_threshold` comes from `line_metrics` (numeric -> string via PostgREST), so coerce.
67366
- idealOutput: Number(metric.line_threshold ?? metric.ideal_output ?? 0),
67416
+ idealOutput: Number(metric.target_output ?? metric.line_threshold ?? metric.ideal_output ?? 0),
67367
67417
  // Keep legacy field in case any downstream code still reads it.
67368
- targetOutput: Number(metric.line_threshold ?? metric.ideal_output ?? 0),
67418
+ targetOutput: Number(metric.target_output ?? metric.line_threshold ?? metric.ideal_output ?? 0),
67369
67419
  compliance_percentage: 95 + Math.random() * 5,
67370
67420
  // Mock data: random value between 95-100%
67371
67421
  hasData: isUptimeMode ? hasUptimeMetricData : true,
@@ -67452,6 +67502,7 @@ var KPIDetailView = ({
67452
67502
  output_array: metrics2.output_array || [],
67453
67503
  output_hourly: metrics2.output_hourly,
67454
67504
  hourly_target_output: metrics2.hourly_target_output,
67505
+ hourly_target_output_by_sku: metrics2.hourly_target_output_by_sku,
67455
67506
  line_threshold: metrics2.line_threshold ?? 0,
67456
67507
  output_target_recalculated: metrics2.output_target_recalculated ?? null,
67457
67508
  threshold_pph: metrics2.threshold_pph ?? 0,
@@ -67579,6 +67630,19 @@ var KPIDetailView = ({
67579
67630
  if (selectedSkuId === "all") return null;
67580
67631
  return realSkuOptions.find((item) => item.sku_id === selectedSkuId) ?? null;
67581
67632
  }, [realSkuOptions, selectedSkuId]);
67633
+ const selectedHourlyTargetOutput = React144.useMemo(() => {
67634
+ const aggregateTarget = chartMetrics?.hourly_target_output;
67635
+ if (!normalizedSelectedSkuId) return aggregateTarget;
67636
+ const targetBySku = chartMetrics?.hourly_target_output_by_sku;
67637
+ if (targetBySku && Object.prototype.hasOwnProperty.call(targetBySku, normalizedSelectedSkuId)) {
67638
+ return targetBySku[normalizedSelectedSkuId];
67639
+ }
67640
+ return aggregateTarget;
67641
+ }, [
67642
+ chartMetrics?.hourly_target_output,
67643
+ chartMetrics?.hourly_target_output_by_sku,
67644
+ normalizedSelectedSkuId
67645
+ ]);
67582
67646
  const displayLineInfo = React144.useMemo(() => {
67583
67647
  if (!resolvedLineInfo) return null;
67584
67648
  if (!selectedSkuRow) return resolvedLineInfo;
@@ -68420,7 +68484,7 @@ var KPIDetailView = ({
68420
68484
  workspaceDisplayNames,
68421
68485
  hourlyOutputData,
68422
68486
  hourlyThreshold,
68423
- hourlyTargetOutput: chartMetrics?.hourly_target_output,
68487
+ hourlyTargetOutput: selectedHourlyTargetOutput,
68424
68488
  shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
68425
68489
  idleTimeHourly: chartMetrics?.idle_time_hourly,
68426
68490
  timezone: lineTimezone,
@@ -75822,8 +75886,8 @@ var WorkspaceDetailView = ({
75822
75886
  idealCycleTime: Number(metric.ideal_cycle_time || 0),
75823
75887
  pph: metric.avg_pph || 0,
75824
75888
  pphThreshold: metric.pph_threshold || 0,
75825
- idealOutput: Number(metric.ideal_output || 0),
75826
- targetOutput: Number(metric.total_day_output || 0),
75889
+ idealOutput: Number(metric.target_output ?? metric.ideal_output ?? 0),
75890
+ targetOutput: Number(metric.target_output ?? metric.total_day_output ?? 0),
75827
75891
  rank: metric.workspace_rank || 0,
75828
75892
  idleTime: idleTimeSeconds || 0,
75829
75893
  activeTimeSeconds,
@@ -85793,6 +85857,246 @@ var streamProxyConfig = {
85793
85857
  responseLimit: false
85794
85858
  }
85795
85859
  };
85860
+ var MOBILE_SCROLL_THRESHOLD2 = 15;
85861
+ var MOBILE_BREAKPOINT_PX2 = 640;
85862
+ var sortWorkspaces = (left, right) => {
85863
+ if (left.line_id !== right.line_id) {
85864
+ return left.line_id.localeCompare(right.line_id);
85865
+ }
85866
+ const leftMatch = left.workspace_name.match(/WS(\d+)/);
85867
+ const rightMatch = right.workspace_name.match(/WS(\d+)/);
85868
+ if (leftMatch && rightMatch) {
85869
+ return parseInt(leftMatch[1], 10) - parseInt(rightMatch[1], 10);
85870
+ }
85871
+ return left.workspace_name.localeCompare(right.workspace_name, void 0, { numeric: true });
85872
+ };
85873
+ var RecentFlowSnapshotGrid = ({
85874
+ workspaces,
85875
+ videoStreamsByWorkspaceId,
85876
+ legend,
85877
+ className = ""
85878
+ }) => {
85879
+ const containerRef = React144.useRef(null);
85880
+ const readinessRef = React144.useRef(null);
85881
+ const [gridCols, setGridCols] = React144.useState(4);
85882
+ const [gridRows, setGridRows] = React144.useState(1);
85883
+ const [isMobileScrollableGrid, setIsMobileScrollableGrid] = React144.useState(false);
85884
+ const sortedWorkspaces = React144.useMemo(
85885
+ () => [...workspaces || []].sort(sortWorkspaces),
85886
+ [workspaces]
85887
+ );
85888
+ const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
85889
+ const displayMinuteBucket = Math.floor(Date.now() / 6e4);
85890
+ const expectedVideoCount = React144.useMemo(
85891
+ () => sortedWorkspaces.filter((workspace) => {
85892
+ const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
85893
+ return Boolean(workspaceId && videoStreamsByWorkspaceId[workspaceId]?.hls_url);
85894
+ }).length,
85895
+ [sortedWorkspaces, videoStreamsByWorkspaceId]
85896
+ );
85897
+ const publishSnapshotReadiness = React144.useCallback((readyVideoCount) => {
85898
+ const status = {
85899
+ expectedVideoCount,
85900
+ readyVideoCount,
85901
+ status: expectedVideoCount === 0 ? "no_videos" : readyVideoCount >= expectedVideoCount ? "ready" : "loading",
85902
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
85903
+ };
85904
+ const isReady = expectedVideoCount > 0 && status.status === "ready";
85905
+ if (typeof window !== "undefined") {
85906
+ window.__OPTIFYE_SNAPSHOT_READY__ = isReady;
85907
+ window.__OPTIFYE_SNAPSHOT_VIDEO_STATUS__ = status;
85908
+ }
85909
+ const marker = readinessRef.current;
85910
+ if (marker) {
85911
+ marker.dataset.ready = isReady ? "true" : "false";
85912
+ marker.dataset.status = status.status;
85913
+ marker.dataset.expectedVideoCount = String(status.expectedVideoCount);
85914
+ marker.dataset.readyVideoCount = String(status.readyVideoCount);
85915
+ marker.dataset.updatedAt = status.updatedAt;
85916
+ }
85917
+ }, [expectedVideoCount]);
85918
+ const calculateOptimalGrid = React144.useCallback(() => {
85919
+ if (!containerRef.current) return;
85920
+ const containerPadding = 16;
85921
+ const rawContainerWidth = containerRef.current.clientWidth;
85922
+ const containerWidth = rawContainerWidth - containerPadding;
85923
+ const containerHeight = containerRef.current.clientHeight - containerPadding;
85924
+ const count = sortedWorkspaces.length;
85925
+ if (count === 0) {
85926
+ setGridCols(1);
85927
+ setGridRows(1);
85928
+ setIsMobileScrollableGrid(false);
85929
+ return;
85930
+ }
85931
+ const shouldUseMobileScroll = rawContainerWidth < MOBILE_BREAKPOINT_PX2 && count >= MOBILE_SCROLL_THRESHOLD2;
85932
+ const optimalLayouts = {
85933
+ 1: 1,
85934
+ 2: 2,
85935
+ 3: 3,
85936
+ 4: 2,
85937
+ 5: 3,
85938
+ 6: 3,
85939
+ 7: 4,
85940
+ 8: 4,
85941
+ 9: 3,
85942
+ 10: 5,
85943
+ 11: 4,
85944
+ 12: 4,
85945
+ 13: 5,
85946
+ 14: 5,
85947
+ 15: 5,
85948
+ 16: 4,
85949
+ 17: 6,
85950
+ 18: 6,
85951
+ 19: 5,
85952
+ 20: 5,
85953
+ 21: 7,
85954
+ 22: 6,
85955
+ 23: 6,
85956
+ 24: 6
85957
+ };
85958
+ let bestCols = optimalLayouts[count] || Math.ceil(Math.sqrt(count));
85959
+ const containerAspectRatio = containerWidth / containerHeight;
85960
+ const targetAspectRatio = 16 / 9;
85961
+ const gap = 8;
85962
+ if (containerAspectRatio > targetAspectRatio * 1.5 && count > 6) {
85963
+ bestCols = Math.min(bestCols + 1, Math.ceil(count / 2));
85964
+ }
85965
+ const minCellWidth = 100;
85966
+ const availableWidth = containerWidth - gap * (bestCols - 1);
85967
+ const cellWidth = availableWidth / bestCols;
85968
+ if (cellWidth < minCellWidth && bestCols > 1) {
85969
+ bestCols = Math.max(1, Math.floor((containerWidth + gap) / (minCellWidth + gap)));
85970
+ }
85971
+ setGridCols(bestCols);
85972
+ setGridRows(Math.ceil(count / bestCols));
85973
+ setIsMobileScrollableGrid(shouldUseMobileScroll);
85974
+ }, [sortedWorkspaces.length]);
85975
+ React144.useEffect(() => {
85976
+ calculateOptimalGrid();
85977
+ window.addEventListener("resize", calculateOptimalGrid);
85978
+ return () => window.removeEventListener("resize", calculateOptimalGrid);
85979
+ }, [calculateOptimalGrid]);
85980
+ React144.useEffect(() => {
85981
+ const attachedVideos = /* @__PURE__ */ new Set();
85982
+ const videoEvents = ["loadeddata", "canplay", "playing", "timeupdate", "error", "stalled"];
85983
+ const hasDecodedFrame = (video) => video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && video.videoWidth > 0 && video.videoHeight > 0;
85984
+ const updateReadiness = () => {
85985
+ const videos = Array.from(containerRef.current?.querySelectorAll("video") ?? []);
85986
+ const readyVideoCount = videos.filter(hasDecodedFrame).length;
85987
+ publishSnapshotReadiness(Math.min(readyVideoCount, expectedVideoCount));
85988
+ for (const video of videos) {
85989
+ if (attachedVideos.has(video)) continue;
85990
+ attachedVideos.add(video);
85991
+ for (const eventName of videoEvents) {
85992
+ video.addEventListener(eventName, updateReadiness);
85993
+ }
85994
+ }
85995
+ };
85996
+ publishSnapshotReadiness(0);
85997
+ updateReadiness();
85998
+ const intervalId = window.setInterval(updateReadiness, 250);
85999
+ return () => {
86000
+ window.clearInterval(intervalId);
86001
+ for (const video of attachedVideos) {
86002
+ for (const eventName of videoEvents) {
86003
+ video.removeEventListener(eventName, updateReadiness);
86004
+ }
86005
+ }
86006
+ if (typeof window !== "undefined") {
86007
+ window.__OPTIFYE_SNAPSHOT_READY__ = false;
86008
+ window.__OPTIFYE_SNAPSHOT_VIDEO_STATUS__ = void 0;
86009
+ }
86010
+ };
86011
+ }, [expectedVideoCount, publishSnapshotReadiness]);
86012
+ if (!sortedWorkspaces.length) {
86013
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
86014
+ /* @__PURE__ */ jsxRuntime.jsx(
86015
+ "div",
86016
+ {
86017
+ ref: readinessRef,
86018
+ "data-testid": "snapshot-video-readiness",
86019
+ "data-ready": "false",
86020
+ "data-status": "no_videos",
86021
+ "data-expected-video-count": "0",
86022
+ "data-ready-video-count": "0",
86023
+ hidden: true
86024
+ }
86025
+ ),
86026
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[320px] items-center justify-center rounded-md border border-dashed border-slate-300 bg-slate-50 text-sm font-medium text-slate-500", children: "No workstation snapshot available" })
86027
+ ] });
86028
+ }
86029
+ return /* @__PURE__ */ jsxRuntime.jsxs(
86030
+ "div",
86031
+ {
86032
+ "aria-label": "Recent-flow workstation snapshot",
86033
+ className: `relative h-full min-h-0 w-full overflow-hidden bg-slate-50/30 ${className}`,
86034
+ children: [
86035
+ /* @__PURE__ */ jsxRuntime.jsx(
86036
+ "div",
86037
+ {
86038
+ ref: readinessRef,
86039
+ "data-testid": "snapshot-video-readiness",
86040
+ "data-ready": "false",
86041
+ "data-status": expectedVideoCount === 0 ? "no_videos" : "loading",
86042
+ "data-expected-video-count": expectedVideoCount,
86043
+ "data-ready-video-count": "0",
86044
+ hidden: true
86045
+ }
86046
+ ),
86047
+ /* @__PURE__ */ jsxRuntime.jsx(
86048
+ "div",
86049
+ {
86050
+ ref: containerRef,
86051
+ "data-testid": "video-grid-scroll-container",
86052
+ "data-mobile-scrollable": isMobileScrollableGrid ? "true" : "false",
86053
+ className: `absolute inset-0 w-full overflow-x-hidden px-1 py-1 sm:px-2 sm:py-2 ${isMobileScrollableGrid ? "overflow-y-auto" : "overflow-hidden"}`,
86054
+ children: /* @__PURE__ */ jsxRuntime.jsx(
86055
+ "div",
86056
+ {
86057
+ "data-testid": "video-grid-layout",
86058
+ className: `grid min-w-0 w-full gap-1.5 sm:gap-2 ${isMobileScrollableGrid ? "content-start" : "h-full"}`,
86059
+ style: {
86060
+ gridTemplateColumns: `repeat(${gridCols}, minmax(0, 1fr))`,
86061
+ gridTemplateRows: isMobileScrollableGrid ? void 0 : `repeat(${gridRows}, 1fr)`,
86062
+ gridAutoFlow: "row"
86063
+ },
86064
+ children: sortedWorkspaces.map((workspace) => {
86065
+ const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
86066
+ const stream = workspaceId ? videoStreamsByWorkspaceId[workspaceId] : null;
86067
+ const hlsUrl = stream?.hls_url || "";
86068
+ return /* @__PURE__ */ jsxRuntime.jsx(
86069
+ "div",
86070
+ {
86071
+ "data-workspace-id": workspaceId,
86072
+ className: isMobileScrollableGrid ? "workspace-card relative min-w-0 w-full aspect-video min-h-[92px]" : "workspace-card relative min-w-0 w-full h-full",
86073
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsxRuntime.jsx(
86074
+ VideoCard,
86075
+ {
86076
+ workspace,
86077
+ hlsUrl,
86078
+ shouldPlay: Boolean(hlsUrl),
86079
+ legend: effectiveLegend,
86080
+ cropping: stream?.crop || void 0,
86081
+ canvasFps: 10,
86082
+ useRAF: false,
86083
+ displayMinuteBucket,
86084
+ displayName: workspace.displayName || workspace.workspace_name,
86085
+ compact: true
86086
+ }
86087
+ ) })
86088
+ },
86089
+ `${workspace.line_id}-${workspaceId}`
86090
+ );
86091
+ })
86092
+ }
86093
+ )
86094
+ }
86095
+ )
86096
+ ]
86097
+ }
86098
+ );
86099
+ };
85796
86100
 
85797
86101
  exports.ACTION_FAMILIES = ACTION_FAMILIES;
85798
86102
  exports.ACTION_NAMES = ACTION_NAMES;
@@ -85949,6 +86253,7 @@ exports.PrefetchStatus = PrefetchStatus;
85949
86253
  exports.PrefetchTimeoutError = PrefetchTimeoutError;
85950
86254
  exports.ProfileView = ProfileView_default;
85951
86255
  exports.ROOT_DASHBOARD_EVENT_NAMES = ROOT_DASHBOARD_EVENT_NAMES;
86256
+ exports.RecentFlowSnapshotGrid = RecentFlowSnapshotGrid;
85952
86257
  exports.RegistryProvider = RegistryProvider;
85953
86258
  exports.RoleBadge = RoleBadge;
85954
86259
  exports.S3ClipsService = S3ClipsSupabaseService;