@optifye/dashboard-core 6.12.9 → 6.12.11

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.css CHANGED
@@ -899,9 +899,6 @@ body {
899
899
  .mt-2 {
900
900
  margin-top: 0.5rem;
901
901
  }
902
- .mt-2\.5 {
903
- margin-top: 0.625rem;
904
- }
905
902
  .mt-3 {
906
903
  margin-top: 0.75rem;
907
904
  }
@@ -1265,6 +1262,9 @@ body {
1265
1262
  .min-h-\[80px\] {
1266
1263
  min-height: 80px;
1267
1264
  }
1265
+ .min-h-\[92px\] {
1266
+ min-height: 92px;
1267
+ }
1268
1268
  .min-h-\[calc\(100dvh-12rem\)\] {
1269
1269
  min-height: calc(100dvh - 12rem);
1270
1270
  }
@@ -1406,9 +1406,6 @@ body {
1406
1406
  .w-\[22vw\] {
1407
1407
  width: 22vw;
1408
1408
  }
1409
- .w-\[260px\] {
1410
- width: 260px;
1411
- }
1412
1409
  .w-\[27vw\] {
1413
1410
  width: 27vw;
1414
1411
  }
@@ -1737,10 +1734,6 @@ body {
1737
1734
  --tw-rotate: 180deg;
1738
1735
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1739
1736
  }
1740
- .rotate-45 {
1741
- --tw-rotate: 45deg;
1742
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1743
- }
1744
1737
  .scale-0 {
1745
1738
  --tw-scale-x: 0;
1746
1739
  --tw-scale-y: 0;
@@ -1902,6 +1895,9 @@ body {
1902
1895
  .flex-nowrap {
1903
1896
  flex-wrap: nowrap;
1904
1897
  }
1898
+ .content-start {
1899
+ align-content: flex-start;
1900
+ }
1905
1901
  .items-start {
1906
1902
  align-items: flex-start;
1907
1903
  }
@@ -2256,9 +2252,6 @@ body {
2256
2252
  .border-b-\[6px\] {
2257
2253
  border-bottom-width: 6px;
2258
2254
  }
2259
- .border-l {
2260
- border-left-width: 1px;
2261
- }
2262
2255
  .border-l-2 {
2263
2256
  border-left-width: 2px;
2264
2257
  }
@@ -7567,10 +7560,6 @@ input[type=range]:active::-moz-range-thumb {
7567
7560
  font-size: 1.875rem;
7568
7561
  line-height: 2.25rem;
7569
7562
  }
7570
- .xl\:text-4xl {
7571
- font-size: 2.25rem;
7572
- line-height: 2.5rem;
7573
- }
7574
7563
  .xl\:text-5xl {
7575
7564
  font-size: 3rem;
7576
7565
  line-height: 1;
@@ -7604,10 +7593,6 @@ input[type=range]:active::-moz-range-thumb {
7604
7593
  font-size: 1.875rem;
7605
7594
  line-height: 2.25rem;
7606
7595
  }
7607
- .\32xl\:text-5xl {
7608
- font-size: 3rem;
7609
- line-height: 1;
7610
- }
7611
7596
  .\32xl\:text-6xl {
7612
7597
  font-size: 3.75rem;
7613
7598
  line-height: 1;
package/dist/index.js CHANGED
@@ -37759,6 +37759,8 @@ var VideoCard = React144__namespace.default.memo(({
37759
37759
  VideoCard.displayName = "VideoCard";
37760
37760
  var DEFAULT_HLS_URL = "https://192.168.5.9:8443/cam1.m3u8";
37761
37761
  var DEBUG_DASHBOARD_LOGS2 = process.env.NEXT_PUBLIC_DEBUG_DASHBOARD === "true";
37762
+ var MOBILE_SCROLL_THRESHOLD = 15;
37763
+ var MOBILE_BREAKPOINT_PX = 640;
37762
37764
  var logDebug2 = (...args) => {
37763
37765
  if (!DEBUG_DASHBOARD_LOGS2) return;
37764
37766
  console.log(...args);
@@ -37780,6 +37782,7 @@ var VideoGridView = React144__namespace.default.memo(({
37780
37782
  const observerRef = React144.useRef(null);
37781
37783
  const [gridCols, setGridCols] = React144.useState(4);
37782
37784
  const [gridRows, setGridRows] = React144.useState(1);
37785
+ const [isMobileScrollableGrid, setIsMobileScrollableGrid] = React144.useState(false);
37783
37786
  const [visibleWorkspaces, setVisibleWorkspaces] = React144.useState(/* @__PURE__ */ new Set());
37784
37787
  const [failedStreams, setFailedStreams] = React144.useState(/* @__PURE__ */ new Set());
37785
37788
  const [r2FallbackWorkspaces, setR2FallbackWorkspaces] = React144.useState(/* @__PURE__ */ new Set());
@@ -37893,13 +37896,17 @@ var VideoGridView = React144__namespace.default.memo(({
37893
37896
  const calculateOptimalGrid = React144.useCallback(() => {
37894
37897
  if (!containerRef.current) return;
37895
37898
  const containerPadding = 16;
37896
- const containerWidth = containerRef.current.clientWidth - containerPadding;
37899
+ const rawContainerWidth = containerRef.current.clientWidth;
37900
+ const containerWidth = rawContainerWidth - containerPadding;
37897
37901
  const containerHeight = containerRef.current.clientHeight - containerPadding;
37898
37902
  const count = filteredWorkspaces.length;
37899
37903
  if (count === 0) {
37900
37904
  setGridCols(1);
37905
+ setGridRows(1);
37906
+ setIsMobileScrollableGrid(false);
37901
37907
  return;
37902
37908
  }
37909
+ const shouldUseMobileScroll = rawContainerWidth < MOBILE_BREAKPOINT_PX && count >= MOBILE_SCROLL_THRESHOLD;
37903
37910
  const optimalLayouts = {
37904
37911
  1: 1,
37905
37912
  2: 2,
@@ -37937,11 +37944,12 @@ var VideoGridView = React144__namespace.default.memo(({
37937
37944
  const availableWidth = containerWidth - gap * (bestCols - 1);
37938
37945
  const cellWidth = availableWidth / bestCols;
37939
37946
  if (cellWidth < minCellWidth && bestCols > 1) {
37940
- bestCols = Math.floor((containerWidth + gap) / (minCellWidth + gap));
37947
+ bestCols = Math.max(1, Math.floor((containerWidth + gap) / (minCellWidth + gap)));
37941
37948
  }
37942
37949
  const rows = Math.ceil(count / bestCols);
37943
37950
  setGridCols(bestCols);
37944
37951
  setGridRows(rows);
37952
+ setIsMobileScrollableGrid(shouldUseMobileScroll);
37945
37953
  }, [filteredWorkspaces.length, selectedLine]);
37946
37954
  React144.useEffect(() => {
37947
37955
  calculateOptimalGrid();
@@ -38155,21 +38163,27 @@ var VideoGridView = React144__namespace.default.memo(({
38155
38163
  resolveWorkspaceDisplayName,
38156
38164
  selectedLine
38157
38165
  ]);
38158
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative overflow-hidden h-full w-full bg-slate-50/30 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx(
38166
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative h-full min-h-0 w-full overflow-hidden bg-slate-50/30 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx(
38159
38167
  "div",
38160
38168
  {
38161
38169
  ref: containerRef,
38162
- className: "h-full w-full px-1 sm:px-2 py-1 sm:py-2 overflow-y-auto",
38170
+ "data-testid": "video-grid-scroll-container",
38171
+ "data-mobile-scrollable": isMobileScrollableGrid ? "true" : "false",
38172
+ className: `absolute inset-0 w-full px-1 py-1 sm:px-2 sm:py-2 ${isMobileScrollableGrid ? "overflow-y-auto" : "overflow-hidden"}`,
38163
38173
  children: /* @__PURE__ */ jsxRuntime.jsx(
38164
38174
  "div",
38165
38175
  {
38166
- className: "grid h-full w-full gap-1.5 sm:gap-2",
38176
+ "data-testid": "video-grid-layout",
38177
+ className: `grid w-full gap-1.5 sm:gap-2 ${isMobileScrollableGrid ? "content-start" : "h-full"}`,
38167
38178
  style: {
38168
38179
  gridTemplateColumns: `repeat(${gridCols}, 1fr)`,
38169
- gridTemplateRows: `repeat(${gridRows}, 1fr)`,
38180
+ gridTemplateRows: isMobileScrollableGrid ? void 0 : `repeat(${gridRows}, 1fr)`,
38170
38181
  gridAutoFlow: "row"
38171
38182
  },
38172
- children: workspaceCards.map((card) => renderWorkspaceCard(card, "workspace-card relative w-full h-full"))
38183
+ children: workspaceCards.map((card) => renderWorkspaceCard(
38184
+ card,
38185
+ isMobileScrollableGrid ? "workspace-card relative w-full aspect-video min-h-[92px]" : "workspace-card relative w-full h-full"
38186
+ ))
38173
38187
  }
38174
38188
  )
38175
38189
  }
@@ -50348,7 +50362,7 @@ var LineMonthlyHistory = ({
50348
50362
  { name: "Idle", value: idlePercent }
50349
50363
  ];
50350
50364
  }, [analysisMonthlyData, selectedShiftId, isUptimeMode]);
50351
- if (isLoading) {
50365
+ if (isLoading && analysisMonthlyData.length === 0) {
50352
50366
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100vh-10rem)]", children: /* @__PURE__ */ jsxRuntime.jsx(
50353
50367
  OptifyeLogoLoader_default,
50354
50368
  {
@@ -51379,19 +51393,12 @@ var fitTextWithEllipsis = (doc, text, maxWidth) => {
51379
51393
  var LinePdfGenerator = ({
51380
51394
  lineInfo,
51381
51395
  workspaceData,
51382
- issueResolutionSummary,
51383
51396
  shiftName,
51384
51397
  className,
51385
51398
  shiftBreaks = [],
51386
51399
  reportTimezone = "Asia/Kolkata"
51387
51400
  }) => {
51388
51401
  const [isGenerating, setIsGenerating] = React144.useState(false);
51389
- const formatResolutionDuration2 = (seconds) => {
51390
- if (seconds === null || seconds === void 0 || seconds <= 0) {
51391
- return "-";
51392
- }
51393
- return formatIdleTime(Math.max(0, Math.floor(seconds)));
51394
- };
51395
51402
  const generatePDF = async () => {
51396
51403
  setIsGenerating(true);
51397
51404
  try {
@@ -51714,7 +51721,7 @@ var LinePdfGenerator = ({
51714
51721
  doc.line(20, 53, 190, 53);
51715
51722
  const perfOverviewStartY = 58;
51716
51723
  doc.setFillColor(245, 245, 245);
51717
- doc.roundedRect(15, perfOverviewStartY, 180, 50, 3, 3, "F");
51724
+ doc.roundedRect(15, perfOverviewStartY, 180, 40, 3, 3, "F");
51718
51725
  doc.setFontSize(18);
51719
51726
  doc.setFont("helvetica", "bold");
51720
51727
  doc.setTextColor(40, 40, 40);
@@ -51730,20 +51737,13 @@ var LinePdfGenerator = ({
51730
51737
  doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
51731
51738
  createKPIBox(kpiStartY + kpiSpacing);
51732
51739
  doc.setFont("helvetica", "normal");
51733
- doc.text("Issue Resolution Time:", 25, kpiStartY + kpiSpacing);
51734
- doc.setFont("helvetica", "bold");
51735
- const resolutionSeconds = issueResolutionSummary ? issueResolutionSummary.mean_resolution_seconds ?? issueResolutionSummary.median_resolution_seconds : null;
51736
- const displayValue = resolutionSeconds !== null ? formatResolutionDuration2(resolutionSeconds) : "-";
51737
- doc.text(displayValue, 120, kpiStartY + kpiSpacing);
51738
- createKPIBox(kpiStartY + kpiSpacing * 2);
51739
- doc.setFont("helvetica", "normal");
51740
- doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing * 2);
51740
+ doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing);
51741
51741
  doc.setFont("helvetica", "bold");
51742
- doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, kpiStartY + kpiSpacing * 2);
51742
+ doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, kpiStartY + kpiSpacing);
51743
51743
  doc.setDrawColor(180, 180, 180);
51744
51744
  doc.setLineWidth(0.8);
51745
- doc.line(20, 123, 190, 123);
51746
- const hourlyOverviewStartY = 128;
51745
+ doc.line(20, 113, 190, 113);
51746
+ const hourlyOverviewStartY = 118;
51747
51747
  const formatMinutesLabel = (totalMinutes) => {
51748
51748
  const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
51749
51749
  const hour = Math.floor(normalized / 60);
@@ -51933,8 +51933,8 @@ var LinePdfGenerator = ({
51933
51933
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
51934
51934
  });
51935
51935
  }
51936
- const tableHeaderY = 151;
51937
- const tableStartY = 158;
51936
+ const tableHeaderY = 141;
51937
+ const tableStartY = 148;
51938
51938
  const rowSpacing = 8;
51939
51939
  const bottomPadding = 8;
51940
51940
  const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
@@ -51944,10 +51944,10 @@ var LinePdfGenerator = ({
51944
51944
  doc.setFontSize(18);
51945
51945
  doc.setFont("helvetica", "bold");
51946
51946
  doc.setTextColor(40, 40, 40);
51947
- doc.text("Hourly Performance", 20, 138);
51947
+ doc.text("Hourly Performance", 20, 128);
51948
51948
  doc.setTextColor(0, 0, 0);
51949
- const gridTopY = 144;
51950
- const headerBottomY = 153;
51949
+ const gridTopY = 134;
51950
+ const headerBottomY = 143;
51951
51951
  const colBoundaries = [20, 70, 100, 130, 155, 190];
51952
51952
  const totalRows = hourlyTimeRanges.length;
51953
51953
  const gridBottomY = headerBottomY + totalRows * rowSpacing;
@@ -66422,25 +66422,8 @@ var formatLocalDate = (date) => {
66422
66422
  };
66423
66423
  return date.toLocaleDateString("en-US", options);
66424
66424
  };
66425
- var formatResolutionDuration = (seconds) => {
66426
- if (seconds === null || seconds === void 0 || seconds <= 0) {
66427
- return "-";
66428
- }
66429
- return formatIdleTime(Math.max(0, Math.floor(seconds)));
66430
- };
66431
- var getIssueResolutionSecondaryText = (summary) => {
66432
- if (!summary) {
66433
- return "No issue data";
66434
- }
66435
- const parts = [`${summary.resolved_issue_count} resolved`];
66436
- if ((summary.open_issue_count || 0) > 0) {
66437
- parts.push(`oldest open ${formatResolutionDuration(summary.oldest_open_issue_age_seconds)}`);
66438
- }
66439
- return parts.join(" \u2022 ");
66440
- };
66441
66425
  var MetricCards = React144.memo(({
66442
66426
  lineInfo,
66443
- issueResolutionSummary,
66444
66427
  idleTimeData,
66445
66428
  showIdleTime,
66446
66429
  efficiencyLegend,
@@ -66452,13 +66435,7 @@ var MetricCards = React144.memo(({
66452
66435
  }) => {
66453
66436
  const efficiency = lineInfo?.metrics.avg_efficiency || 0;
66454
66437
  const efficiencyColorClass = getEfficiencyTextColorClasses(efficiency, efficiencyLegend);
66455
- const showIssueResolutionCard = Boolean(issueResolutionSummary);
66456
- const largeGridColumns = showIdleTime ? showIssueResolutionCard ? "lg:grid-cols-5" : "lg:grid-cols-4" : showIssueResolutionCard ? "lg:grid-cols-4" : "lg:grid-cols-3";
66457
- const shouldMaskIssueResolution = efficiency < 5;
66458
- const issueResolutionValue = shouldMaskIssueResolution ? "-" : formatResolutionDuration(
66459
- issueResolutionSummary?.mean_resolution_seconds ?? issueResolutionSummary?.median_resolution_seconds
66460
- );
66461
- getIssueResolutionSecondaryText(issueResolutionSummary);
66438
+ const largeGridColumns = showIdleTime ? "lg:grid-cols-4" : "lg:grid-cols-3";
66462
66439
  const outputTarget = lineInfo?.metrics.output_target_recalculated ?? lineInfo?.metrics.line_threshold ?? 0;
66463
66440
  return /* @__PURE__ */ jsxRuntime.jsxs(
66464
66441
  motion.div,
@@ -66499,28 +66476,6 @@ var MetricCards = React144.memo(({
66499
66476
  "%"
66500
66477
  ] }) })
66501
66478
  ] }),
66502
- showIssueResolutionCard && /* @__PURE__ */ jsxRuntime.jsxs(
66503
- motion.div,
66504
- {
66505
- variants: itemVariants,
66506
- 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",
66507
- children: [
66508
- /* @__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: [
66509
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Issue Resolution Time" }),
66510
- /* @__PURE__ */ jsxRuntime.jsx(outline.InformationCircleIcon, { className: "w-4 h-4 text-gray-400 cursor-help flex-shrink-0" }),
66511
- /* @__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: [
66512
- /* @__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." }),
66513
- /* @__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: [
66514
- ((issueResolutionSummary?.resolved_issue_count || 0) + (issueResolutionSummary?.open_issue_count || 0)).toLocaleString(),
66515
- " recorded issues"
66516
- ] }) }),
66517
- /* @__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" })
66518
- ] })
66519
- ] }),
66520
- /* @__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 }) })
66521
- ]
66522
- }
66523
- ),
66524
66479
  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: [
66525
66480
  /* @__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" }) }),
66526
66481
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -66577,8 +66532,6 @@ var MetricCards = React144.memo(({
66577
66532
  if (prevSkuSignature !== nextSkuSignature) return false;
66578
66533
  const prevMetrics = prevProps.lineInfo.metrics;
66579
66534
  const nextMetrics = nextProps.lineInfo.metrics;
66580
- const prevIssueSummary = prevProps.issueResolutionSummary;
66581
- const nextIssueSummary = nextProps.issueResolutionSummary;
66582
66535
  const prevIdleChartSignature = JSON.stringify(
66583
66536
  (prevProps.idleTimeData?.chartData || []).map((entry) => ({
66584
66537
  name: entry.name,
@@ -66597,9 +66550,6 @@ var MetricCards = React144.memo(({
66597
66550
  );
66598
66551
  const idleTimeChanged = prevProps.idleTimeData?.isLoading !== nextProps.idleTimeData?.isLoading || prevProps.idleTimeData?.error !== nextProps.idleTimeData?.error || prevIdleChartSignature !== nextIdleChartSignature || prevProps.idleTimeData?.data?.total_idle_time_seconds !== nextProps.idleTimeData?.data?.total_idle_time_seconds || prevProps.idleTimeData?.data?.scope_work_seconds !== nextProps.idleTimeData?.data?.scope_work_seconds;
66599
66552
  if (idleTimeChanged) return false;
66600
- if (prevIssueSummary?.mean_resolution_seconds !== nextIssueSummary?.mean_resolution_seconds || prevIssueSummary?.median_resolution_seconds !== nextIssueSummary?.median_resolution_seconds || prevIssueSummary?.resolved_issue_count !== nextIssueSummary?.resolved_issue_count || prevIssueSummary?.open_issue_count !== nextIssueSummary?.open_issue_count || prevIssueSummary?.oldest_open_issue_age_seconds !== nextIssueSummary?.oldest_open_issue_age_seconds || prevIssueSummary?.total_issue_seconds !== nextIssueSummary?.total_issue_seconds) {
66601
- return false;
66602
- }
66603
66553
  return prevMetrics.current_output === nextMetrics.current_output && prevMetrics.line_threshold === nextMetrics.line_threshold && prevMetrics.output_target_recalculated === nextMetrics.output_target_recalculated && prevMetrics.underperforming_workspaces === nextMetrics.underperforming_workspaces && prevMetrics.total_workspaces === nextMetrics.total_workspaces && prevMetrics.avg_efficiency === nextMetrics.avg_efficiency;
66604
66554
  });
66605
66555
  MetricCards.displayName = "MetricCards";
@@ -68410,7 +68360,6 @@ var KPIDetailView = ({
68410
68360
  MetricCards,
68411
68361
  {
68412
68362
  lineInfo: displayLineInfo ?? resolvedLineInfo,
68413
- issueResolutionSummary,
68414
68363
  idleTimeData,
68415
68364
  showIdleTime: idleTimeVlmEnabled,
68416
68365
  efficiencyLegend,
@@ -68525,7 +68474,6 @@ var KPIDetailView = ({
68525
68474
  MetricCards,
68526
68475
  {
68527
68476
  lineInfo: displayLineInfo ?? resolvedLineInfo,
68528
- issueResolutionSummary,
68529
68477
  idleTimeData,
68530
68478
  showIdleTime: idleTimeVlmEnabled,
68531
68479
  efficiencyLegend,
package/dist/index.mjs CHANGED
@@ -16,7 +16,7 @@ import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, ReferenceLine, Too
16
16
  import { Slot } from '@radix-ui/react-slot';
17
17
  import * as SelectPrimitive from '@radix-ui/react-select';
18
18
  import { DayPicker, useNavigation as useNavigation$1 } from 'react-day-picker';
19
- import { AdjustmentsHorizontalIcon, ClockIcon, UsersIcon, UserCircleIcon, TicketIcon, CurrencyDollarIcon, QuestionMarkCircleIcon, XMarkIcon, InformationCircleIcon, ArrowRightIcon, Bars3Icon, HomeIcon, VideoCameraIcon, TrophyIcon, ChartBarIcon, LightBulbIcon, CubeIcon, HeartIcon, Cog6ToothIcon, ChevronRightIcon, ArrowRightStartOnRectangleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, CalendarIcon, ChevronDownIcon, ChevronLeftIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ArrowDownTrayIcon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, FunnelIcon, EyeIcon, ArrowLeftIcon, PlayCircleIcon } from '@heroicons/react/24/outline';
19
+ import { AdjustmentsHorizontalIcon, ClockIcon, UsersIcon, UserCircleIcon, TicketIcon, CurrencyDollarIcon, QuestionMarkCircleIcon, XMarkIcon, ArrowRightIcon, Bars3Icon, HomeIcon, VideoCameraIcon, TrophyIcon, ChartBarIcon, LightBulbIcon, CubeIcon, HeartIcon, Cog6ToothIcon, ChevronRightIcon, ArrowRightStartOnRectangleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, CalendarIcon, ChevronDownIcon, ChevronLeftIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ArrowDownTrayIcon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, FunnelIcon, EyeIcon, InformationCircleIcon, ArrowLeftIcon, PlayCircleIcon } from '@heroicons/react/24/outline';
20
20
  import { CheckIcon } from '@heroicons/react/24/solid';
21
21
  import html2canvas from 'html2canvas';
22
22
  import jsPDF, { jsPDF as jsPDF$1 } from 'jspdf';
@@ -37730,6 +37730,8 @@ var VideoCard = React144__default.memo(({
37730
37730
  VideoCard.displayName = "VideoCard";
37731
37731
  var DEFAULT_HLS_URL = "https://192.168.5.9:8443/cam1.m3u8";
37732
37732
  var DEBUG_DASHBOARD_LOGS2 = process.env.NEXT_PUBLIC_DEBUG_DASHBOARD === "true";
37733
+ var MOBILE_SCROLL_THRESHOLD = 15;
37734
+ var MOBILE_BREAKPOINT_PX = 640;
37733
37735
  var logDebug2 = (...args) => {
37734
37736
  if (!DEBUG_DASHBOARD_LOGS2) return;
37735
37737
  console.log(...args);
@@ -37751,6 +37753,7 @@ var VideoGridView = React144__default.memo(({
37751
37753
  const observerRef = useRef(null);
37752
37754
  const [gridCols, setGridCols] = useState(4);
37753
37755
  const [gridRows, setGridRows] = useState(1);
37756
+ const [isMobileScrollableGrid, setIsMobileScrollableGrid] = useState(false);
37754
37757
  const [visibleWorkspaces, setVisibleWorkspaces] = useState(/* @__PURE__ */ new Set());
37755
37758
  const [failedStreams, setFailedStreams] = useState(/* @__PURE__ */ new Set());
37756
37759
  const [r2FallbackWorkspaces, setR2FallbackWorkspaces] = useState(/* @__PURE__ */ new Set());
@@ -37864,13 +37867,17 @@ var VideoGridView = React144__default.memo(({
37864
37867
  const calculateOptimalGrid = useCallback(() => {
37865
37868
  if (!containerRef.current) return;
37866
37869
  const containerPadding = 16;
37867
- const containerWidth = containerRef.current.clientWidth - containerPadding;
37870
+ const rawContainerWidth = containerRef.current.clientWidth;
37871
+ const containerWidth = rawContainerWidth - containerPadding;
37868
37872
  const containerHeight = containerRef.current.clientHeight - containerPadding;
37869
37873
  const count = filteredWorkspaces.length;
37870
37874
  if (count === 0) {
37871
37875
  setGridCols(1);
37876
+ setGridRows(1);
37877
+ setIsMobileScrollableGrid(false);
37872
37878
  return;
37873
37879
  }
37880
+ const shouldUseMobileScroll = rawContainerWidth < MOBILE_BREAKPOINT_PX && count >= MOBILE_SCROLL_THRESHOLD;
37874
37881
  const optimalLayouts = {
37875
37882
  1: 1,
37876
37883
  2: 2,
@@ -37908,11 +37915,12 @@ var VideoGridView = React144__default.memo(({
37908
37915
  const availableWidth = containerWidth - gap * (bestCols - 1);
37909
37916
  const cellWidth = availableWidth / bestCols;
37910
37917
  if (cellWidth < minCellWidth && bestCols > 1) {
37911
- bestCols = Math.floor((containerWidth + gap) / (minCellWidth + gap));
37918
+ bestCols = Math.max(1, Math.floor((containerWidth + gap) / (minCellWidth + gap)));
37912
37919
  }
37913
37920
  const rows = Math.ceil(count / bestCols);
37914
37921
  setGridCols(bestCols);
37915
37922
  setGridRows(rows);
37923
+ setIsMobileScrollableGrid(shouldUseMobileScroll);
37916
37924
  }, [filteredWorkspaces.length, selectedLine]);
37917
37925
  useEffect(() => {
37918
37926
  calculateOptimalGrid();
@@ -38126,21 +38134,27 @@ var VideoGridView = React144__default.memo(({
38126
38134
  resolveWorkspaceDisplayName,
38127
38135
  selectedLine
38128
38136
  ]);
38129
- return /* @__PURE__ */ jsx("div", { className: `relative overflow-hidden h-full w-full bg-slate-50/30 ${className}`, children: /* @__PURE__ */ jsx(
38137
+ return /* @__PURE__ */ jsx("div", { className: `relative h-full min-h-0 w-full overflow-hidden bg-slate-50/30 ${className}`, children: /* @__PURE__ */ jsx(
38130
38138
  "div",
38131
38139
  {
38132
38140
  ref: containerRef,
38133
- className: "h-full w-full px-1 sm:px-2 py-1 sm:py-2 overflow-y-auto",
38141
+ "data-testid": "video-grid-scroll-container",
38142
+ "data-mobile-scrollable": isMobileScrollableGrid ? "true" : "false",
38143
+ className: `absolute inset-0 w-full px-1 py-1 sm:px-2 sm:py-2 ${isMobileScrollableGrid ? "overflow-y-auto" : "overflow-hidden"}`,
38134
38144
  children: /* @__PURE__ */ jsx(
38135
38145
  "div",
38136
38146
  {
38137
- className: "grid h-full w-full gap-1.5 sm:gap-2",
38147
+ "data-testid": "video-grid-layout",
38148
+ className: `grid w-full gap-1.5 sm:gap-2 ${isMobileScrollableGrid ? "content-start" : "h-full"}`,
38138
38149
  style: {
38139
38150
  gridTemplateColumns: `repeat(${gridCols}, 1fr)`,
38140
- gridTemplateRows: `repeat(${gridRows}, 1fr)`,
38151
+ gridTemplateRows: isMobileScrollableGrid ? void 0 : `repeat(${gridRows}, 1fr)`,
38141
38152
  gridAutoFlow: "row"
38142
38153
  },
38143
- children: workspaceCards.map((card) => renderWorkspaceCard(card, "workspace-card relative w-full h-full"))
38154
+ children: workspaceCards.map((card) => renderWorkspaceCard(
38155
+ card,
38156
+ isMobileScrollableGrid ? "workspace-card relative w-full aspect-video min-h-[92px]" : "workspace-card relative w-full h-full"
38157
+ ))
38144
38158
  }
38145
38159
  )
38146
38160
  }
@@ -50319,7 +50333,7 @@ var LineMonthlyHistory = ({
50319
50333
  { name: "Idle", value: idlePercent }
50320
50334
  ];
50321
50335
  }, [analysisMonthlyData, selectedShiftId, isUptimeMode]);
50322
- if (isLoading) {
50336
+ if (isLoading && analysisMonthlyData.length === 0) {
50323
50337
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-[calc(100vh-10rem)]", children: /* @__PURE__ */ jsx(
50324
50338
  OptifyeLogoLoader_default,
50325
50339
  {
@@ -51350,19 +51364,12 @@ var fitTextWithEllipsis = (doc, text, maxWidth) => {
51350
51364
  var LinePdfGenerator = ({
51351
51365
  lineInfo,
51352
51366
  workspaceData,
51353
- issueResolutionSummary,
51354
51367
  shiftName,
51355
51368
  className,
51356
51369
  shiftBreaks = [],
51357
51370
  reportTimezone = "Asia/Kolkata"
51358
51371
  }) => {
51359
51372
  const [isGenerating, setIsGenerating] = useState(false);
51360
- const formatResolutionDuration2 = (seconds) => {
51361
- if (seconds === null || seconds === void 0 || seconds <= 0) {
51362
- return "-";
51363
- }
51364
- return formatIdleTime(Math.max(0, Math.floor(seconds)));
51365
- };
51366
51373
  const generatePDF = async () => {
51367
51374
  setIsGenerating(true);
51368
51375
  try {
@@ -51685,7 +51692,7 @@ var LinePdfGenerator = ({
51685
51692
  doc.line(20, 53, 190, 53);
51686
51693
  const perfOverviewStartY = 58;
51687
51694
  doc.setFillColor(245, 245, 245);
51688
- doc.roundedRect(15, perfOverviewStartY, 180, 50, 3, 3, "F");
51695
+ doc.roundedRect(15, perfOverviewStartY, 180, 40, 3, 3, "F");
51689
51696
  doc.setFontSize(18);
51690
51697
  doc.setFont("helvetica", "bold");
51691
51698
  doc.setTextColor(40, 40, 40);
@@ -51701,20 +51708,13 @@ var LinePdfGenerator = ({
51701
51708
  doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
51702
51709
  createKPIBox(kpiStartY + kpiSpacing);
51703
51710
  doc.setFont("helvetica", "normal");
51704
- doc.text("Issue Resolution Time:", 25, kpiStartY + kpiSpacing);
51705
- doc.setFont("helvetica", "bold");
51706
- const resolutionSeconds = issueResolutionSummary ? issueResolutionSummary.mean_resolution_seconds ?? issueResolutionSummary.median_resolution_seconds : null;
51707
- const displayValue = resolutionSeconds !== null ? formatResolutionDuration2(resolutionSeconds) : "-";
51708
- doc.text(displayValue, 120, kpiStartY + kpiSpacing);
51709
- createKPIBox(kpiStartY + kpiSpacing * 2);
51710
- doc.setFont("helvetica", "normal");
51711
- doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing * 2);
51711
+ doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing);
51712
51712
  doc.setFont("helvetica", "bold");
51713
- doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, kpiStartY + kpiSpacing * 2);
51713
+ doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, kpiStartY + kpiSpacing);
51714
51714
  doc.setDrawColor(180, 180, 180);
51715
51715
  doc.setLineWidth(0.8);
51716
- doc.line(20, 123, 190, 123);
51717
- const hourlyOverviewStartY = 128;
51716
+ doc.line(20, 113, 190, 113);
51717
+ const hourlyOverviewStartY = 118;
51718
51718
  const formatMinutesLabel = (totalMinutes) => {
51719
51719
  const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
51720
51720
  const hour = Math.floor(normalized / 60);
@@ -51904,8 +51904,8 @@ var LinePdfGenerator = ({
51904
51904
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
51905
51905
  });
51906
51906
  }
51907
- const tableHeaderY = 151;
51908
- const tableStartY = 158;
51907
+ const tableHeaderY = 141;
51908
+ const tableStartY = 148;
51909
51909
  const rowSpacing = 8;
51910
51910
  const bottomPadding = 8;
51911
51911
  const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
@@ -51915,10 +51915,10 @@ var LinePdfGenerator = ({
51915
51915
  doc.setFontSize(18);
51916
51916
  doc.setFont("helvetica", "bold");
51917
51917
  doc.setTextColor(40, 40, 40);
51918
- doc.text("Hourly Performance", 20, 138);
51918
+ doc.text("Hourly Performance", 20, 128);
51919
51919
  doc.setTextColor(0, 0, 0);
51920
- const gridTopY = 144;
51921
- const headerBottomY = 153;
51920
+ const gridTopY = 134;
51921
+ const headerBottomY = 143;
51922
51922
  const colBoundaries = [20, 70, 100, 130, 155, 190];
51923
51923
  const totalRows = hourlyTimeRanges.length;
51924
51924
  const gridBottomY = headerBottomY + totalRows * rowSpacing;
@@ -66393,25 +66393,8 @@ var formatLocalDate = (date) => {
66393
66393
  };
66394
66394
  return date.toLocaleDateString("en-US", options);
66395
66395
  };
66396
- var formatResolutionDuration = (seconds) => {
66397
- if (seconds === null || seconds === void 0 || seconds <= 0) {
66398
- return "-";
66399
- }
66400
- return formatIdleTime(Math.max(0, Math.floor(seconds)));
66401
- };
66402
- var getIssueResolutionSecondaryText = (summary) => {
66403
- if (!summary) {
66404
- return "No issue data";
66405
- }
66406
- const parts = [`${summary.resolved_issue_count} resolved`];
66407
- if ((summary.open_issue_count || 0) > 0) {
66408
- parts.push(`oldest open ${formatResolutionDuration(summary.oldest_open_issue_age_seconds)}`);
66409
- }
66410
- return parts.join(" \u2022 ");
66411
- };
66412
66396
  var MetricCards = memo$1(({
66413
66397
  lineInfo,
66414
- issueResolutionSummary,
66415
66398
  idleTimeData,
66416
66399
  showIdleTime,
66417
66400
  efficiencyLegend,
@@ -66423,13 +66406,7 @@ var MetricCards = memo$1(({
66423
66406
  }) => {
66424
66407
  const efficiency = lineInfo?.metrics.avg_efficiency || 0;
66425
66408
  const efficiencyColorClass = getEfficiencyTextColorClasses(efficiency, efficiencyLegend);
66426
- const showIssueResolutionCard = Boolean(issueResolutionSummary);
66427
- const largeGridColumns = showIdleTime ? showIssueResolutionCard ? "lg:grid-cols-5" : "lg:grid-cols-4" : showIssueResolutionCard ? "lg:grid-cols-4" : "lg:grid-cols-3";
66428
- const shouldMaskIssueResolution = efficiency < 5;
66429
- const issueResolutionValue = shouldMaskIssueResolution ? "-" : formatResolutionDuration(
66430
- issueResolutionSummary?.mean_resolution_seconds ?? issueResolutionSummary?.median_resolution_seconds
66431
- );
66432
- getIssueResolutionSecondaryText(issueResolutionSummary);
66409
+ const largeGridColumns = showIdleTime ? "lg:grid-cols-4" : "lg:grid-cols-3";
66433
66410
  const outputTarget = lineInfo?.metrics.output_target_recalculated ?? lineInfo?.metrics.line_threshold ?? 0;
66434
66411
  return /* @__PURE__ */ jsxs(
66435
66412
  motion.div,
@@ -66470,28 +66447,6 @@ var MetricCards = memo$1(({
66470
66447
  "%"
66471
66448
  ] }) })
66472
66449
  ] }),
66473
- showIssueResolutionCard && /* @__PURE__ */ jsxs(
66474
- motion.div,
66475
- {
66476
- variants: itemVariants,
66477
- 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",
66478
- children: [
66479
- /* @__PURE__ */ 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: [
66480
- /* @__PURE__ */ jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Issue Resolution Time" }),
66481
- /* @__PURE__ */ jsx(InformationCircleIcon, { className: "w-4 h-4 text-gray-400 cursor-help flex-shrink-0" }),
66482
- /* @__PURE__ */ 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: [
66483
- /* @__PURE__ */ 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." }),
66484
- /* @__PURE__ */ jsx("div", { className: "bg-gray-50 rounded-md py-1.5 border border-gray-100/80", children: /* @__PURE__ */ jsxs("p", { className: "text-center font-medium text-[11px] text-gray-500", children: [
66485
- ((issueResolutionSummary?.resolved_issue_count || 0) + (issueResolutionSummary?.open_issue_count || 0)).toLocaleString(),
66486
- " recorded issues"
66487
- ] }) }),
66488
- /* @__PURE__ */ 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" })
66489
- ] })
66490
- ] }),
66491
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center flex-1 min-h-0", children: /* @__PURE__ */ 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 }) })
66492
- ]
66493
- }
66494
- ),
66495
66450
  showIdleTime && /* @__PURE__ */ 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: [
66496
66451
  /* @__PURE__ */ jsx("div", { className: "h-10 sm:h-12 flex items-start justify-center mb-2 mt-1", children: /* @__PURE__ */ jsx("h2", { className: "text-sm sm:text-base font-bold text-gray-700 text-center", children: "Idle Time Breakdown" }) }),
66497
66452
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(
@@ -66548,8 +66503,6 @@ var MetricCards = memo$1(({
66548
66503
  if (prevSkuSignature !== nextSkuSignature) return false;
66549
66504
  const prevMetrics = prevProps.lineInfo.metrics;
66550
66505
  const nextMetrics = nextProps.lineInfo.metrics;
66551
- const prevIssueSummary = prevProps.issueResolutionSummary;
66552
- const nextIssueSummary = nextProps.issueResolutionSummary;
66553
66506
  const prevIdleChartSignature = JSON.stringify(
66554
66507
  (prevProps.idleTimeData?.chartData || []).map((entry) => ({
66555
66508
  name: entry.name,
@@ -66568,9 +66521,6 @@ var MetricCards = memo$1(({
66568
66521
  );
66569
66522
  const idleTimeChanged = prevProps.idleTimeData?.isLoading !== nextProps.idleTimeData?.isLoading || prevProps.idleTimeData?.error !== nextProps.idleTimeData?.error || prevIdleChartSignature !== nextIdleChartSignature || prevProps.idleTimeData?.data?.total_idle_time_seconds !== nextProps.idleTimeData?.data?.total_idle_time_seconds || prevProps.idleTimeData?.data?.scope_work_seconds !== nextProps.idleTimeData?.data?.scope_work_seconds;
66570
66523
  if (idleTimeChanged) return false;
66571
- if (prevIssueSummary?.mean_resolution_seconds !== nextIssueSummary?.mean_resolution_seconds || prevIssueSummary?.median_resolution_seconds !== nextIssueSummary?.median_resolution_seconds || prevIssueSummary?.resolved_issue_count !== nextIssueSummary?.resolved_issue_count || prevIssueSummary?.open_issue_count !== nextIssueSummary?.open_issue_count || prevIssueSummary?.oldest_open_issue_age_seconds !== nextIssueSummary?.oldest_open_issue_age_seconds || prevIssueSummary?.total_issue_seconds !== nextIssueSummary?.total_issue_seconds) {
66572
- return false;
66573
- }
66574
66524
  return prevMetrics.current_output === nextMetrics.current_output && prevMetrics.line_threshold === nextMetrics.line_threshold && prevMetrics.output_target_recalculated === nextMetrics.output_target_recalculated && prevMetrics.underperforming_workspaces === nextMetrics.underperforming_workspaces && prevMetrics.total_workspaces === nextMetrics.total_workspaces && prevMetrics.avg_efficiency === nextMetrics.avg_efficiency;
66575
66525
  });
66576
66526
  MetricCards.displayName = "MetricCards";
@@ -68381,7 +68331,6 @@ var KPIDetailView = ({
68381
68331
  MetricCards,
68382
68332
  {
68383
68333
  lineInfo: displayLineInfo ?? resolvedLineInfo,
68384
- issueResolutionSummary,
68385
68334
  idleTimeData,
68386
68335
  showIdleTime: idleTimeVlmEnabled,
68387
68336
  efficiencyLegend,
@@ -68496,7 +68445,6 @@ var KPIDetailView = ({
68496
68445
  MetricCards,
68497
68446
  {
68498
68447
  lineInfo: displayLineInfo ?? resolvedLineInfo,
68499
- issueResolutionSummary,
68500
68448
  idleTimeData,
68501
68449
  showIdleTime: idleTimeVlmEnabled,
68502
68450
  efficiencyLegend,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.12.9",
3
+ "version": "6.12.11",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",