@optifye/dashboard-core 6.10.5 → 6.10.6

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.mjs CHANGED
@@ -28969,6 +28969,33 @@ var VideoControls = ({
28969
28969
  }
28970
28970
  );
28971
28971
  };
28972
+
28973
+ // src/lib/utils/r2Detection.ts
28974
+ function isR2WorkerUrl(url, r2WorkerDomain) {
28975
+ if (!url || !r2WorkerDomain) return false;
28976
+ try {
28977
+ const workerDomain = new URL(r2WorkerDomain).hostname;
28978
+ return url.includes(workerDomain);
28979
+ } catch {
28980
+ return url.includes(r2WorkerDomain.replace(/https?:\/\//, ""));
28981
+ }
28982
+ }
28983
+
28984
+ // src/lib/services/hlsAuthService.ts
28985
+ async function getAuthTokenForHls(supabase) {
28986
+ try {
28987
+ const { data: { session } } = await supabase.auth.getSession();
28988
+ if (!session?.access_token) {
28989
+ console.warn("[HLS Auth] No active session, R2 streaming may fail");
28990
+ return null;
28991
+ }
28992
+ console.log("[HLS Auth] Retrieved token for HLS.js requests");
28993
+ return session.access_token;
28994
+ } catch (error) {
28995
+ console.error("[HLS Auth] Error getting auth token:", error);
28996
+ return null;
28997
+ }
28998
+ }
28972
28999
  var ERROR_MAPPING = {
28973
29000
  "networkError": {
28974
29001
  code: 2,
@@ -29091,6 +29118,7 @@ var HlsVideoPlayer = forwardRef(({
29091
29118
  onSeeked,
29092
29119
  onClick
29093
29120
  }, ref) => {
29121
+ const supabase = useSupabase();
29094
29122
  const videoContainerRef = useRef(null);
29095
29123
  const videoRef = useRef(null);
29096
29124
  const hlsRef = useRef(null);
@@ -29218,14 +29246,49 @@ var HlsVideoPlayer = forwardRef(({
29218
29246
  dispose: () => dispose()
29219
29247
  };
29220
29248
  }, [dispose]);
29221
- const initializePlayer = useCallback(() => {
29249
+ const initializePlayer = useCallback(async () => {
29222
29250
  if (!videoRef.current || !src) return;
29223
29251
  const video = videoRef.current;
29224
29252
  const player = playerLikeObject();
29253
+ let authToken = null;
29254
+ try {
29255
+ authToken = await getAuthTokenForHls(supabase);
29256
+ if (authToken) {
29257
+ console.log("[HLS Auth] Retrieved token for R2 streaming");
29258
+ } else {
29259
+ console.warn("[HLS Auth] No active session - R2 streaming may fail");
29260
+ }
29261
+ } catch (error) {
29262
+ console.error("[HLS Auth] Error retrieving token:", error);
29263
+ }
29264
+ const r2WorkerDomain = "https://r2-stream-proxy.optifye-r2.workers.dev";
29225
29265
  const mergedHlsConfig = {
29226
29266
  ...BASE_HLS_CONFIG,
29227
29267
  ...stableHlsConfigRef.current || {},
29228
- ...stableOptionsRef.current || {}
29268
+ ...stableOptionsRef.current || {},
29269
+ // Modern HLS.js uses Fetch API - override fetch to add Authorization header
29270
+ fetchSetup: function(context, initParams) {
29271
+ const url = context.url;
29272
+ console.log("[HLS fetchSetup] Request URL:", url);
29273
+ console.log("[HLS fetchSetup] R2 Worker domain:", r2WorkerDomain);
29274
+ console.log("[HLS fetchSetup] Token available:", !!authToken);
29275
+ const isR2 = isR2WorkerUrl(url, r2WorkerDomain);
29276
+ console.log("[HLS fetchSetup] Is R2 Worker URL:", isR2);
29277
+ if (isR2) {
29278
+ if (authToken) {
29279
+ initParams.headers = {
29280
+ ...initParams.headers,
29281
+ "Authorization": `Bearer ${authToken}`
29282
+ };
29283
+ console.log("[HLS Auth] \u2705 Injected JWT for R2 request:", url);
29284
+ } else {
29285
+ console.warn("[HLS Auth] \u26A0\uFE0F No token available for R2 request:", url);
29286
+ }
29287
+ } else {
29288
+ console.log("[HLS Auth] CloudFront URL - no auth needed:", url);
29289
+ }
29290
+ return new Request(url, initParams);
29291
+ }
29229
29292
  };
29230
29293
  cleanupBlobUrl();
29231
29294
  const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
@@ -29264,9 +29327,20 @@ var HlsVideoPlayer = forwardRef(({
29264
29327
  let errorInfo;
29265
29328
  switch (data.type) {
29266
29329
  case ErrorTypes.NETWORK_ERROR:
29267
- errorInfo = ERROR_MAPPING.networkError;
29268
- console.log("[HlsVideoPlayer] Attempting to recover from network error");
29269
- hls.startLoad();
29330
+ if (data.response?.code === 401) {
29331
+ errorInfo = {
29332
+ code: 401,
29333
+ type: "recoverable",
29334
+ message: "Authentication expired. Please refresh the page.",
29335
+ canRetry: true,
29336
+ details: "JWT_EXPIRED"
29337
+ };
29338
+ console.error("[HLS Auth] 401 Unauthorized - token may be expired");
29339
+ } else {
29340
+ errorInfo = ERROR_MAPPING.networkError;
29341
+ console.log("[HlsVideoPlayer] Attempting to recover from network error");
29342
+ hls.startLoad();
29343
+ }
29270
29344
  break;
29271
29345
  case ErrorTypes.MEDIA_ERROR:
29272
29346
  errorInfo = ERROR_MAPPING.mediaError;
@@ -29311,8 +29385,19 @@ var HlsVideoPlayer = forwardRef(({
29311
29385
  let errorInfo;
29312
29386
  switch (data.type) {
29313
29387
  case ErrorTypes.NETWORK_ERROR:
29314
- errorInfo = ERROR_MAPPING.networkError;
29315
- hls.startLoad();
29388
+ if (data.response?.code === 401) {
29389
+ errorInfo = {
29390
+ code: 401,
29391
+ type: "recoverable",
29392
+ message: "Authentication expired. Please refresh the page.",
29393
+ canRetry: true,
29394
+ details: "JWT_EXPIRED"
29395
+ };
29396
+ console.error("[HLS Auth] 401 Unauthorized - token may be expired");
29397
+ } else {
29398
+ errorInfo = ERROR_MAPPING.networkError;
29399
+ hls.startLoad();
29400
+ }
29316
29401
  break;
29317
29402
  case ErrorTypes.MEDIA_ERROR:
29318
29403
  errorInfo = ERROR_MAPPING.mediaError;
@@ -29463,7 +29548,10 @@ var HlsVideoPlayer = forwardRef(({
29463
29548
  configVersion
29464
29549
  ]);
29465
29550
  useEffect(() => {
29466
- const cleanup = initializePlayer();
29551
+ let cleanup;
29552
+ initializePlayer().then((cleanupFn) => {
29553
+ cleanup = cleanupFn;
29554
+ });
29467
29555
  return () => {
29468
29556
  cleanup?.();
29469
29557
  if (hlsRef.current) {
@@ -31670,6 +31758,30 @@ var FileManagerFilters = ({
31670
31758
  if (node.type === "category" || node.type === "percentile-category") {
31671
31759
  toggleExpanded(node.id);
31672
31760
  onFilterChange(node.id);
31761
+ if (node.id === "fast-cycles") {
31762
+ trackCoreEvent("Fast Clips Clicked", {
31763
+ workspaceId,
31764
+ date,
31765
+ shift,
31766
+ count: node.count || 0,
31767
+ percentile: filterState.percentile
31768
+ });
31769
+ } else if (node.id === "slow-cycles") {
31770
+ trackCoreEvent("Slow Clips Clicked", {
31771
+ workspaceId,
31772
+ date,
31773
+ shift,
31774
+ count: node.count || 0,
31775
+ percentile: filterState.percentile
31776
+ });
31777
+ } else if (node.id === "cycle_completion") {
31778
+ trackCoreEvent("Cycle Completions Clicked", {
31779
+ workspaceId,
31780
+ date,
31781
+ shift,
31782
+ count: node.count || 0
31783
+ });
31784
+ }
31673
31785
  if (node.id !== "idle_time" && idleLabelFilter) {
31674
31786
  setIdleLabelFilter(null);
31675
31787
  }
@@ -37184,11 +37296,6 @@ var LinePdfGenerator = ({
37184
37296
  doc.setLineWidth(0.8);
37185
37297
  doc.line(20, 118, 190, 118);
37186
37298
  const hourlyOverviewStartY = 123;
37187
- doc.setFontSize(18);
37188
- doc.setFont("helvetica", "bold");
37189
- doc.setTextColor(40, 40, 40);
37190
- doc.text("Hourly Output Overview", 20, 133);
37191
- doc.setTextColor(0, 0, 0);
37192
37299
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
37193
37300
  const [hours, minutes] = startTimeStr.split(":");
37194
37301
  const startHour = parseInt(hours);
@@ -37379,23 +37486,43 @@ var LinePdfGenerator = ({
37379
37486
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
37380
37487
  });
37381
37488
  }
37382
- const tableHeaderY = 143;
37383
- const tableStartY = 151;
37489
+ const tableHeaderY = 146;
37490
+ const tableStartY = 153;
37384
37491
  const rowSpacing = 8;
37385
37492
  const bottomPadding = 8;
37386
37493
  const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
37387
37494
  const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
37388
37495
  doc.setFillColor(245, 245, 245);
37389
37496
  doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
37497
+ doc.setFontSize(18);
37498
+ doc.setFont("helvetica", "bold");
37499
+ doc.setTextColor(40, 40, 40);
37500
+ doc.text("Hourly Performance", 20, 133);
37501
+ doc.setTextColor(0, 0, 0);
37502
+ const gridTopY = 139;
37503
+ const headerBottomY = 148;
37504
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
37505
+ const totalRows = hourlyTimeRanges.length;
37506
+ const gridBottomY = headerBottomY + totalRows * rowSpacing;
37507
+ const tableHeight = gridBottomY - gridTopY;
37508
+ doc.setFillColor(255, 255, 255);
37509
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "F");
37510
+ doc.setDrawColor(230, 230, 230);
37511
+ doc.setLineWidth(0.2);
37512
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "S");
37513
+ doc.setDrawColor(200, 200, 200);
37514
+ doc.setLineWidth(0.3);
37515
+ doc.line(20, headerBottomY, 190, headerBottomY);
37516
+ colBoundaries.slice(1, -1).forEach((x) => {
37517
+ doc.line(x, gridTopY, x, gridBottomY);
37518
+ });
37390
37519
  doc.setFontSize(11);
37391
37520
  doc.setFont("helvetica", "bold");
37392
37521
  doc.text("Time Range", 25, tableHeaderY);
37393
- doc.text("Output", 80, tableHeaderY);
37394
- doc.text("Target", 125, tableHeaderY);
37395
- doc.text("Status", 170, tableHeaderY);
37396
- doc.setLineWidth(0.3);
37397
- doc.setDrawColor(200, 200, 200);
37398
- doc.line(20, 146, 190, 146);
37522
+ doc.text("Output", 75, tableHeaderY);
37523
+ doc.text("Target", 105, tableHeaderY);
37524
+ doc.text("Status", 135, tableHeaderY);
37525
+ doc.text("Remarks", 160, tableHeaderY);
37399
37526
  doc.setFont("helvetica", "normal");
37400
37527
  let yPos = tableStartY;
37401
37528
  const lineDateForTable = new Date(lineInfo.date);
@@ -37417,20 +37544,25 @@ var LinePdfGenerator = ({
37417
37544
  const dataCollected = !isTodayForTable || hourNumber < currentHour;
37418
37545
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
37419
37546
  const targetStr = targetOutputPerHour.toString();
37547
+ if (index < totalRows - 1) {
37548
+ const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
37549
+ doc.setDrawColor(200, 200, 200);
37550
+ doc.line(20, rowBottomY, 190, rowBottomY);
37551
+ }
37420
37552
  doc.text(timeRange, 25, yPos);
37421
- doc.text(outputStr, 80, yPos);
37422
- doc.text(targetStr, 125, yPos);
37553
+ doc.text(outputStr, 75, yPos);
37554
+ doc.text(targetStr, 105, yPos);
37423
37555
  if (!dataCollected) {
37424
37556
  doc.setTextColor(100, 100, 100);
37425
- doc.text("-", 170, yPos);
37557
+ doc.text("-", 135, yPos);
37426
37558
  } else if (actualOutput >= targetOutputPerHour) {
37427
37559
  doc.setTextColor(0, 171, 69);
37428
37560
  doc.setFont("ZapfDingbats", "normal");
37429
- doc.text("4", 170, yPos);
37561
+ doc.text("4", 135, yPos);
37430
37562
  doc.setFont("helvetica", "normal");
37431
37563
  } else {
37432
37564
  doc.setTextColor(227, 67, 41);
37433
- doc.text("\xD7", 170, yPos);
37565
+ doc.text("\xD7", 135, yPos);
37434
37566
  }
37435
37567
  doc.setTextColor(0, 0, 0);
37436
37568
  yPos += rowSpacing;
@@ -37438,7 +37570,7 @@ var LinePdfGenerator = ({
37438
37570
  doc.addPage();
37439
37571
  yPos = addHeaderPage2();
37440
37572
  const workspaceSectionStartY = yPos;
37441
- const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
37573
+ const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 3);
37442
37574
  const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
37443
37575
  const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
37444
37576
  doc.setFillColor(245, 245, 245);
@@ -37446,28 +37578,45 @@ var LinePdfGenerator = ({
37446
37578
  doc.setFontSize(18);
37447
37579
  doc.setFont("helvetica", "bold");
37448
37580
  doc.setTextColor(40, 40, 40);
37449
- doc.text("Poorest Performing Workspaces", 20, yPos);
37581
+ doc.text("Poorest Performing Workspaces", 20, yPos + 10);
37450
37582
  doc.setTextColor(0, 0, 0);
37451
- yPos += 10;
37583
+ yPos += 20;
37584
+ const wsGridTopY = yPos - 5;
37585
+ const wsHeaderBottomY = wsGridTopY + 8;
37586
+ const wsColBoundaries = [20, 80, 140, 190];
37587
+ const wsGridBottomY = wsHeaderBottomY + wsRowCount * 8;
37588
+ const wsTableTotalHeight = wsGridBottomY - wsGridTopY;
37589
+ doc.setFillColor(255, 255, 255);
37590
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "F");
37591
+ doc.setDrawColor(230, 230, 230);
37592
+ doc.setLineWidth(0.2);
37593
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "S");
37594
+ doc.setDrawColor(200, 200, 200);
37595
+ doc.setLineWidth(0.3);
37596
+ doc.line(20, wsHeaderBottomY, 190, wsHeaderBottomY);
37597
+ wsColBoundaries.slice(1, -1).forEach((x) => {
37598
+ doc.line(x, wsGridTopY, x, wsGridBottomY);
37599
+ });
37452
37600
  doc.setFontSize(11);
37453
37601
  doc.setFont("helvetica", "bold");
37454
- yPos += 5;
37602
+ yPos = wsGridTopY + 5.5;
37455
37603
  doc.text("Workspace", 25, yPos);
37456
37604
  doc.text("Current/Target", 85, yPos);
37457
37605
  doc.text("Efficiency", 145, yPos);
37458
- yPos += 3;
37459
- doc.setLineWidth(0.3);
37460
- doc.setDrawColor(200, 200, 200);
37461
- doc.line(20, yPos, 190, yPos);
37462
37606
  doc.setFont("helvetica", "normal");
37463
- yPos += 7;
37607
+ yPos = wsHeaderBottomY + 5.5;
37464
37608
  if (sortedWorkspaces.length === 0) {
37465
37609
  doc.text("No workspace data available", 25, yPos);
37466
- yPos += 10;
37610
+ yPos += 8;
37467
37611
  } else {
37468
37612
  sortedWorkspaces.forEach((ws, index) => {
37469
37613
  const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
37470
37614
  const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
37615
+ if (index < wsRowCount - 1) {
37616
+ const rowBottomY = wsHeaderBottomY + (index + 1) * 8;
37617
+ doc.setDrawColor(200, 200, 200);
37618
+ doc.line(20, rowBottomY, 190, rowBottomY);
37619
+ }
37471
37620
  doc.text(truncatedName, 25, yPos);
37472
37621
  doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
37473
37622
  doc.text(`${(ws.efficiency || 0).toFixed(1)}%`, 145, yPos);
@@ -38810,18 +38959,27 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38810
38959
  const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38811
38960
  doc.text("Hourly Performance", 20, hourlyTitleY);
38812
38961
  doc.setTextColor(0, 0, 0);
38813
- doc.setFontSize(headerFontSize);
38814
- doc.setFont("helvetica", "bold");
38815
38962
  const baseHeaderY = hasIdleTimeReason ? 208 : 198;
38816
38963
  const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
38817
- doc.text("Time Range", 25, headerY);
38818
- doc.text("Output", 95, headerY);
38819
- doc.text("Target", 135, headerY);
38820
- doc.text("Status", 170, headerY);
38821
- doc.setLineWidth(0.3);
38964
+ const gridTopY = headerY - 5;
38965
+ const headerBottomY = gridTopY + 8;
38966
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
38967
+ const totalRows = hourlyData.length;
38968
+ const gridBottomY = headerBottomY + totalRows * rowHeight;
38822
38969
  doc.setDrawColor(200, 200, 200);
38823
- const separatorY = headerY + 3;
38824
- doc.line(20, separatorY, 190, separatorY);
38970
+ doc.setLineWidth(0.3);
38971
+ doc.line(20, gridTopY, 190, gridTopY);
38972
+ doc.line(20, headerBottomY, 190, headerBottomY);
38973
+ colBoundaries.forEach((x) => {
38974
+ doc.line(x, gridTopY, x, gridBottomY);
38975
+ });
38976
+ doc.setFontSize(headerFontSize);
38977
+ doc.setFont("helvetica", "bold");
38978
+ doc.text("Time Range", 25, headerY);
38979
+ doc.text("Output", 75, headerY);
38980
+ doc.text("Target", 105, headerY);
38981
+ doc.text("Status", 135, headerY);
38982
+ doc.text("Remarks", 160, headerY);
38825
38983
  doc.setFontSize(contentFontSize);
38826
38984
  doc.setFont("helvetica", "normal");
38827
38985
  let yPos = tableStartY;
@@ -38852,20 +39010,23 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38852
39010
  const dataCollected = !isToday2 || hourNumber < currentHour;
38853
39011
  const outputStr = dataCollected ? output.toString() : "TBD";
38854
39012
  const targetStr = hourlyTarget.toString();
39013
+ const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39014
+ doc.setDrawColor(200, 200, 200);
39015
+ doc.line(20, rowBottomY, 190, rowBottomY);
38855
39016
  doc.text(timeRange, 25, yPos);
38856
- doc.text(outputStr, 95, yPos);
38857
- doc.text(targetStr, 135, yPos);
39017
+ doc.text(outputStr, 75, yPos);
39018
+ doc.text(targetStr, 105, yPos);
38858
39019
  if (!dataCollected) {
38859
39020
  doc.setTextColor(100, 100, 100);
38860
- doc.text("-", 170, yPos);
39021
+ doc.text("-", 135, yPos);
38861
39022
  } else if (output >= hourlyTarget) {
38862
39023
  doc.setTextColor(0, 171, 69);
38863
39024
  doc.setFont("ZapfDingbats", "normal");
38864
- doc.text("4", 170, yPos);
39025
+ doc.text("4", 135, yPos);
38865
39026
  doc.setFont("helvetica", "normal");
38866
39027
  } else {
38867
39028
  doc.setTextColor(227, 67, 41);
38868
- doc.text("\xD7", 170, yPos);
39029
+ doc.text("\xD7", 135, yPos);
38869
39030
  }
38870
39031
  doc.setTextColor(0, 0, 0);
38871
39032
  yPos += rowHeight;
@@ -39671,14 +39832,16 @@ var KPICard = ({
39671
39832
  "uppercase tracking-wide sm:tracking-wider"
39672
39833
  ), children: title }),
39673
39834
  /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-0.5 sm:gap-2 flex-wrap", children: [
39674
- /* @__PURE__ */ jsx("span", { className: clsx(
39675
- "font-bold text-gray-900 dark:text-gray-50",
39676
- "text-base sm:text-xl md:text-2xl"
39677
- ), children: isLoading ? "\u2014" : formattedValue }),
39678
- suffix && !isLoading && /* @__PURE__ */ jsx("span", { className: clsx(
39679
- "font-medium text-gray-600 dark:text-gray-300",
39680
- style.compact ? "text-base" : "text-lg"
39681
- ), children: suffix }),
39835
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "h-5 sm:h-6 md:h-7 w-12 sm:w-16 md:w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
39836
+ /* @__PURE__ */ jsx("span", { className: clsx(
39837
+ "font-bold text-gray-900 dark:text-gray-50",
39838
+ "text-base sm:text-xl md:text-2xl"
39839
+ ), children: formattedValue }),
39840
+ suffix && /* @__PURE__ */ jsx("span", { className: clsx(
39841
+ "font-medium text-gray-600 dark:text-gray-300",
39842
+ style.compact ? "text-base" : "text-lg"
39843
+ ), children: suffix })
39844
+ ] }),
39682
39845
  !isLoading && trendInfo.shouldShowTrend && trendInfo.trendValue !== "neutral" && title !== "Output" && /* @__PURE__ */ jsx("span", { className: trendInfo.colorClass, children: /* @__PURE__ */ jsx(
39683
39846
  trendInfo.Icon,
39684
39847
  {
@@ -39837,6 +40000,7 @@ var KPIHeader = ({
39837
40000
  };
39838
40001
  var KPISection = memo(({
39839
40002
  kpis,
40003
+ isLoading = false,
39840
40004
  className,
39841
40005
  layout: layout2 = "row",
39842
40006
  gridColumns,
@@ -39845,7 +40009,8 @@ var KPISection = memo(({
39845
40009
  compactCards = false,
39846
40010
  useSrcLayout = false
39847
40011
  }) => {
39848
- const outputDifference = kpis.outputProgress.current - kpis.outputProgress.idealOutput;
40012
+ const showSkeleton = isLoading || !kpis;
40013
+ const outputDifference = kpis ? kpis.outputProgress.current - kpis.outputProgress.idealOutput : 0;
39849
40014
  const outputIsOnTarget = outputDifference >= 0;
39850
40015
  if (useSrcLayout) {
39851
40016
  return /* @__PURE__ */ jsxs(
@@ -39862,27 +40027,30 @@ var KPISection = memo(({
39862
40027
  KPICard,
39863
40028
  {
39864
40029
  title: "Underperforming",
39865
- value: "2/3",
39866
- change: 0
40030
+ value: showSkeleton ? "" : "2/3",
40031
+ change: 0,
40032
+ isLoading: showSkeleton
39867
40033
  }
39868
40034
  ) }),
39869
40035
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
39870
40036
  KPICard,
39871
40037
  {
39872
40038
  title: "Efficiency",
39873
- value: kpis.efficiency.value,
39874
- change: kpis.efficiency.change,
39875
- suffix: "%"
40039
+ value: showSkeleton ? "" : kpis.efficiency.value,
40040
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40041
+ suffix: "%",
40042
+ isLoading: showSkeleton
39876
40043
  }
39877
40044
  ) }),
39878
40045
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
39879
40046
  KPICard,
39880
40047
  {
39881
40048
  title: "Output Progress",
39882
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39883
- change: kpis.outputProgress.change,
40049
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40050
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
39884
40051
  outputDifference,
39885
- showOutputDetails: true
40052
+ showOutputDetails: !showSkeleton,
40053
+ isLoading: showSkeleton
39886
40054
  }
39887
40055
  ) })
39888
40056
  ]
@@ -39893,34 +40061,30 @@ var KPISection = memo(({
39893
40061
  {
39894
40062
  key: "underperforming",
39895
40063
  title: "Underperforming",
39896
- value: `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
39897
- change: kpis.underperformingWorkers.change
40064
+ value: showSkeleton ? "" : `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
40065
+ change: showSkeleton ? 0 : kpis.underperformingWorkers.change,
40066
+ suffix: void 0,
40067
+ status: void 0
39898
40068
  },
39899
40069
  {
39900
40070
  key: "efficiency",
39901
40071
  title: "Efficiency",
39902
- value: kpis.efficiency.value,
39903
- change: kpis.efficiency.change,
39904
- suffix: "%"
40072
+ value: showSkeleton ? "" : kpis.efficiency.value,
40073
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40074
+ suffix: "%",
40075
+ status: void 0
39905
40076
  },
39906
40077
  {
39907
40078
  key: "outputProgress",
39908
40079
  title: "Output Progress",
39909
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39910
- change: kpis.outputProgress.change,
39911
- status: {
40080
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40081
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
40082
+ suffix: void 0,
40083
+ status: showSkeleton ? void 0 : {
39912
40084
  tooltipText: outputIsOnTarget ? "On Target" : "Off Target",
39913
40085
  positive: outputIsOnTarget
39914
40086
  }
39915
40087
  }
39916
- // Only include these additional KPIs if not using the src layout
39917
- // ...(kpis.qualityCompliance ? [{
39918
- // key: 'qualityCompliance',
39919
- // title: 'Quality Compliance',
39920
- // value: kpis.qualityCompliance.value,
39921
- // change: kpis.qualityCompliance.change,
39922
- // suffix: '%',
39923
- // }] : []),
39924
40088
  ];
39925
40089
  return /* @__PURE__ */ jsx(
39926
40090
  KPIGrid,
@@ -39937,7 +40101,8 @@ var KPISection = memo(({
39937
40101
  change: kpi.change,
39938
40102
  suffix: kpi.suffix,
39939
40103
  status: kpi.status,
39940
- style: { variant: cardVariant, compact: compactCards }
40104
+ style: { variant: cardVariant, compact: compactCards },
40105
+ isLoading: showSkeleton
39941
40106
  },
39942
40107
  kpi.key
39943
40108
  ))
@@ -39946,6 +40111,9 @@ var KPISection = memo(({
39946
40111
  }, (prevProps, nextProps) => {
39947
40112
  const prevKpis = prevProps.kpis;
39948
40113
  const nextKpis = nextProps.kpis;
40114
+ if (prevProps.isLoading !== nextProps.isLoading) return false;
40115
+ if (!prevKpis && !nextKpis) return true;
40116
+ if (!prevKpis || !nextKpis) return false;
39949
40117
  if (prevKpis === nextKpis) return true;
39950
40118
  if (Math.abs(prevKpis.efficiency.value - nextKpis.efficiency.value) >= 0.5) return false;
39951
40119
  if (prevKpis.underperformingWorkers.current !== nextKpis.underperformingWorkers.current || prevKpis.underperformingWorkers.total !== nextKpis.underperformingWorkers.total) return false;
@@ -45698,7 +45866,8 @@ var TeamUsagePdfGenerator = ({
45698
45866
  usageData,
45699
45867
  daysInRange = 1,
45700
45868
  title = "Team Usage Report",
45701
- className
45869
+ className,
45870
+ iconOnly = false
45702
45871
  }) => {
45703
45872
  const [isGenerating, setIsGenerating] = useState(false);
45704
45873
  const generatePDF = async () => {
@@ -45878,10 +46047,11 @@ var TeamUsagePdfGenerator = ({
45878
46047
  {
45879
46048
  onClick: generatePDF,
45880
46049
  disabled: isGenerating || !usageData,
45881
- className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
46050
+ className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 ${iconOnly ? "p-2" : "px-4 py-2"} text-sm font-medium text-gray-700 hover:bg-gray-200 active:bg-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
46051
+ "aria-label": iconOnly ? isGenerating ? "Generating PDF..." : "Export PDF" : void 0,
45882
46052
  children: [
45883
- /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" }),
45884
- isGenerating ? "Generating..." : "Export PDF"
46053
+ /* @__PURE__ */ jsx(Download, { className: iconOnly ? "h-5 w-5" : "h-4 w-4" }),
46054
+ !iconOnly && (isGenerating ? "Generating..." : "Export PDF")
45885
46055
  ]
45886
46056
  }
45887
46057
  );
@@ -48650,10 +48820,19 @@ function HomeView({
48650
48820
  const [errorMessage, setErrorMessage] = useState(null);
48651
48821
  const [displayNamesInitialized, setDisplayNamesInitialized] = useState(false);
48652
48822
  const [hasInitialDataLoaded, setHasInitialDataLoaded] = useState(false);
48823
+ const KPI_CACHE_KEY = "optifye_kpi_cache";
48653
48824
  const dashboardConfig = useDashboardConfig();
48654
48825
  const entityConfig = useEntityConfig();
48655
48826
  const supabaseClient = useSupabaseClient();
48656
48827
  const { user } = useAuth();
48828
+ const { lines: dbLines } = useLines();
48829
+ const mergedLineNames = useMemo(() => {
48830
+ const merged = { ...lineNames };
48831
+ dbLines.forEach((line) => {
48832
+ merged[line.id] = line.line_name;
48833
+ });
48834
+ return merged;
48835
+ }, [lineNames, dbLines]);
48657
48836
  const isSupervisor = user?.role_level === "supervisor";
48658
48837
  const hasMultipleLines = allLineIds.length > 1;
48659
48838
  const availableLineIds = useMemo(() => {
@@ -48760,6 +48939,42 @@ function HomeView({
48760
48939
  }
48761
48940
  return buildKPIsFromLineMetricsRow(row);
48762
48941
  }, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
48942
+ const [cachedKpis, setCachedKpis] = useState(() => {
48943
+ if (typeof window === "undefined") return null;
48944
+ try {
48945
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
48946
+ if (cached) {
48947
+ const parsed = JSON.parse(cached);
48948
+ return parsed[selectedLineId] || parsed[factoryViewId] || null;
48949
+ }
48950
+ } catch (e) {
48951
+ }
48952
+ return null;
48953
+ });
48954
+ useEffect(() => {
48955
+ if (kpis && typeof window !== "undefined") {
48956
+ try {
48957
+ const existing = localStorage.getItem(KPI_CACHE_KEY);
48958
+ const cache = existing ? JSON.parse(existing) : {};
48959
+ cache[selectedLineId] = kpis;
48960
+ localStorage.setItem(KPI_CACHE_KEY, JSON.stringify(cache));
48961
+ setCachedKpis(kpis);
48962
+ } catch (e) {
48963
+ }
48964
+ }
48965
+ }, [kpis, selectedLineId, KPI_CACHE_KEY]);
48966
+ useEffect(() => {
48967
+ if (typeof window === "undefined") return;
48968
+ try {
48969
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
48970
+ if (cached) {
48971
+ const parsed = JSON.parse(cached);
48972
+ setCachedKpis(parsed[selectedLineId] || null);
48973
+ }
48974
+ } catch (e) {
48975
+ }
48976
+ }, [selectedLineId, KPI_CACHE_KEY]);
48977
+ const displayKpis = kpis || cachedKpis;
48763
48978
  const {
48764
48979
  activeBreaks: allActiveBreaks,
48765
48980
  isLoading: breaksLoading,
@@ -49061,16 +49276,16 @@ function HomeView({
49061
49276
  // Use stable string representation instead of spreading array
49062
49277
  JSON.stringify(workspaceMetrics.map((w) => `${w.workspace_uuid}-${Math.round(w.efficiency)}-${w.trend}`))
49063
49278
  ]);
49064
- const memoizedKPIs = useMemo(() => kpis, [
49279
+ const memoizedKPIs = useMemo(() => displayKpis, [
49065
49280
  // Only update reference when values change by at least 1%
49066
- kpis?.efficiency?.value ? Math.round(kpis.efficiency.value) : null,
49067
- kpis?.underperformingWorkers?.current,
49068
- kpis?.underperformingWorkers?.total,
49069
- kpis?.outputProgress?.current,
49070
- kpis?.outputProgress?.target,
49071
- kpis?.avgCycleTime?.value ? Math.round(kpis.avgCycleTime.value * 10) / 10 : null,
49281
+ displayKpis?.efficiency?.value ? Math.round(displayKpis.efficiency.value) : null,
49282
+ displayKpis?.underperformingWorkers?.current,
49283
+ displayKpis?.underperformingWorkers?.total,
49284
+ displayKpis?.outputProgress?.current,
49285
+ displayKpis?.outputProgress?.target,
49286
+ displayKpis?.avgCycleTime?.value ? Math.round(displayKpis.avgCycleTime.value * 10) / 10 : null,
49072
49287
  // Round to 1 decimal
49073
- kpis?.qualityCompliance?.value ? Math.round(kpis.qualityCompliance.value) : null
49288
+ displayKpis?.qualityCompliance?.value ? Math.round(displayKpis.qualityCompliance.value) : null
49074
49289
  ]);
49075
49290
  useEffect(() => {
49076
49291
  setIsHydrated(true);
@@ -49088,7 +49303,7 @@ function HomeView({
49088
49303
  trackCoreEvent("Home Line Filter Changed", {
49089
49304
  previous_line_id: selectedLineId,
49090
49305
  new_line_id: value,
49091
- line_name: lineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49306
+ line_name: mergedLineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49092
49307
  });
49093
49308
  try {
49094
49309
  sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
@@ -49122,9 +49337,9 @@ function HomeView({
49122
49337
  }
49123
49338
  return /* @__PURE__ */ jsxs(Select, { onValueChange: handleLineChange, defaultValue: selectedLineId, children: [
49124
49339
  /* @__PURE__ */ 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__ */ jsx(SelectValue, { placeholder: "Select a line" }) }),
49125
- /* @__PURE__ */ 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__ */ jsx(SelectItem, { value: id3, children: lineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49340
+ /* @__PURE__ */ 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__ */ jsx(SelectItem, { value: id3, children: mergedLineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49126
49341
  ] });
49127
- }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
49342
+ }, [availableLineIds, handleLineChange, selectedLineId, mergedLineNames, factoryViewId, allLineIds.length]);
49128
49343
  const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
49129
49344
  const isDataLoading = metricsLoading;
49130
49345
  if (isInitialLoading) {
@@ -49153,7 +49368,7 @@ function HomeView({
49153
49368
  lineTitle,
49154
49369
  lineId: selectedLineId === factoryViewId ? allLineIds[0] : selectedLineId,
49155
49370
  className: "w-full",
49156
- headerControls: memoizedKPIs ? /* @__PURE__ */ jsx(KPISection2, { kpis: memoizedKPIs, className: "w-full sm:w-auto" }) : null
49371
+ headerControls: /* @__PURE__ */ jsx(KPISection2, { kpis: memoizedKPIs, isLoading: !memoizedKPIs, className: "w-full sm:w-auto" })
49157
49372
  }
49158
49373
  ) }) }),
49159
49374
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative", children: [
@@ -56442,35 +56657,62 @@ var TeamManagementView = ({
56442
56657
  ) }) });
56443
56658
  }
56444
56659
  return /* @__PURE__ */ jsxs("div", { className: cn("min-h-screen bg-slate-50", className), children: [
56445
- /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
56446
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56447
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56448
- /* @__PURE__ */ jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56449
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56450
- ] }),
56451
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
56452
- canViewUsageStats && /* @__PURE__ */ jsx(
56453
- TeamUsagePdfGenerator,
56454
- {
56455
- users,
56456
- usageData,
56457
- daysInRange: usageDateRange.daysElapsed,
56458
- title: "Team Usage Report"
56459
- }
56460
- ),
56461
- canAddUsers && /* @__PURE__ */ jsxs(
56462
- "button",
56463
- {
56464
- onClick: () => setIsAddUserDialogOpen(true),
56465
- className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56466
- children: [
56467
- /* @__PURE__ */ jsx(UserPlus, { className: "w-4 h-4" }),
56468
- /* @__PURE__ */ jsx("span", { children: "Add User" })
56469
- ]
56470
- }
56471
- )
56660
+ /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxs("div", { className: "px-3 sm:px-6 lg:px-8 py-3 sm:py-4", children: [
56661
+ /* @__PURE__ */ jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
56662
+ /* @__PURE__ */ jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }),
56663
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900 truncate px-2", children: pageTitle }) }),
56664
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
56665
+ canViewUsageStats && /* @__PURE__ */ jsx(
56666
+ TeamUsagePdfGenerator,
56667
+ {
56668
+ users,
56669
+ usageData,
56670
+ daysInRange: usageDateRange.daysElapsed,
56671
+ title: "Team Usage Report",
56672
+ iconOnly: true
56673
+ }
56674
+ ),
56675
+ canAddUsers && /* @__PURE__ */ jsx(
56676
+ "button",
56677
+ {
56678
+ onClick: () => setIsAddUserDialogOpen(true),
56679
+ className: "p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 active:bg-blue-800 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56680
+ "aria-label": "Add User",
56681
+ children: /* @__PURE__ */ jsx(UserPlus, { className: "w-5 h-5" })
56682
+ }
56683
+ )
56684
+ ] })
56685
+ ] }) }),
56686
+ /* @__PURE__ */ jsxs("div", { className: "hidden sm:flex items-center justify-between", children: [
56687
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56688
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56689
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56690
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56691
+ ] }),
56692
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
56693
+ canViewUsageStats && /* @__PURE__ */ jsx(
56694
+ TeamUsagePdfGenerator,
56695
+ {
56696
+ users,
56697
+ usageData,
56698
+ daysInRange: usageDateRange.daysElapsed,
56699
+ title: "Team Usage Report"
56700
+ }
56701
+ ),
56702
+ canAddUsers && /* @__PURE__ */ jsxs(
56703
+ "button",
56704
+ {
56705
+ onClick: () => setIsAddUserDialogOpen(true),
56706
+ className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
56707
+ children: [
56708
+ /* @__PURE__ */ jsx(UserPlus, { className: "w-4 h-4" }),
56709
+ /* @__PURE__ */ jsx("span", { children: "Add User" })
56710
+ ]
56711
+ }
56712
+ )
56713
+ ] })
56472
56714
  ] })
56473
- ] }) }) }),
56715
+ ] }) }),
56474
56716
  /* @__PURE__ */ jsx("main", { className: "px-4 sm:px-6 lg:px-8 py-6", children: /* @__PURE__ */ jsx(
56475
56717
  UserManagementTable,
56476
56718
  {