@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.js CHANGED
@@ -28998,6 +28998,33 @@ var VideoControls = ({
28998
28998
  }
28999
28999
  );
29000
29000
  };
29001
+
29002
+ // src/lib/utils/r2Detection.ts
29003
+ function isR2WorkerUrl(url, r2WorkerDomain) {
29004
+ if (!url || !r2WorkerDomain) return false;
29005
+ try {
29006
+ const workerDomain = new URL(r2WorkerDomain).hostname;
29007
+ return url.includes(workerDomain);
29008
+ } catch {
29009
+ return url.includes(r2WorkerDomain.replace(/https?:\/\//, ""));
29010
+ }
29011
+ }
29012
+
29013
+ // src/lib/services/hlsAuthService.ts
29014
+ async function getAuthTokenForHls(supabase) {
29015
+ try {
29016
+ const { data: { session } } = await supabase.auth.getSession();
29017
+ if (!session?.access_token) {
29018
+ console.warn("[HLS Auth] No active session, R2 streaming may fail");
29019
+ return null;
29020
+ }
29021
+ console.log("[HLS Auth] Retrieved token for HLS.js requests");
29022
+ return session.access_token;
29023
+ } catch (error) {
29024
+ console.error("[HLS Auth] Error getting auth token:", error);
29025
+ return null;
29026
+ }
29027
+ }
29001
29028
  var ERROR_MAPPING = {
29002
29029
  "networkError": {
29003
29030
  code: 2,
@@ -29120,6 +29147,7 @@ var HlsVideoPlayer = React24.forwardRef(({
29120
29147
  onSeeked,
29121
29148
  onClick
29122
29149
  }, ref) => {
29150
+ const supabase = useSupabase();
29123
29151
  const videoContainerRef = React24.useRef(null);
29124
29152
  const videoRef = React24.useRef(null);
29125
29153
  const hlsRef = React24.useRef(null);
@@ -29247,14 +29275,49 @@ var HlsVideoPlayer = React24.forwardRef(({
29247
29275
  dispose: () => dispose()
29248
29276
  };
29249
29277
  }, [dispose]);
29250
- const initializePlayer = React24.useCallback(() => {
29278
+ const initializePlayer = React24.useCallback(async () => {
29251
29279
  if (!videoRef.current || !src) return;
29252
29280
  const video = videoRef.current;
29253
29281
  const player = playerLikeObject();
29282
+ let authToken = null;
29283
+ try {
29284
+ authToken = await getAuthTokenForHls(supabase);
29285
+ if (authToken) {
29286
+ console.log("[HLS Auth] Retrieved token for R2 streaming");
29287
+ } else {
29288
+ console.warn("[HLS Auth] No active session - R2 streaming may fail");
29289
+ }
29290
+ } catch (error) {
29291
+ console.error("[HLS Auth] Error retrieving token:", error);
29292
+ }
29293
+ const r2WorkerDomain = "https://r2-stream-proxy.optifye-r2.workers.dev";
29254
29294
  const mergedHlsConfig = {
29255
29295
  ...BASE_HLS_CONFIG,
29256
29296
  ...stableHlsConfigRef.current || {},
29257
- ...stableOptionsRef.current || {}
29297
+ ...stableOptionsRef.current || {},
29298
+ // Modern HLS.js uses Fetch API - override fetch to add Authorization header
29299
+ fetchSetup: function(context, initParams) {
29300
+ const url = context.url;
29301
+ console.log("[HLS fetchSetup] Request URL:", url);
29302
+ console.log("[HLS fetchSetup] R2 Worker domain:", r2WorkerDomain);
29303
+ console.log("[HLS fetchSetup] Token available:", !!authToken);
29304
+ const isR2 = isR2WorkerUrl(url, r2WorkerDomain);
29305
+ console.log("[HLS fetchSetup] Is R2 Worker URL:", isR2);
29306
+ if (isR2) {
29307
+ if (authToken) {
29308
+ initParams.headers = {
29309
+ ...initParams.headers,
29310
+ "Authorization": `Bearer ${authToken}`
29311
+ };
29312
+ console.log("[HLS Auth] \u2705 Injected JWT for R2 request:", url);
29313
+ } else {
29314
+ console.warn("[HLS Auth] \u26A0\uFE0F No token available for R2 request:", url);
29315
+ }
29316
+ } else {
29317
+ console.log("[HLS Auth] CloudFront URL - no auth needed:", url);
29318
+ }
29319
+ return new Request(url, initParams);
29320
+ }
29258
29321
  };
29259
29322
  cleanupBlobUrl();
29260
29323
  const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
@@ -29293,9 +29356,20 @@ var HlsVideoPlayer = React24.forwardRef(({
29293
29356
  let errorInfo;
29294
29357
  switch (data.type) {
29295
29358
  case Hls3.ErrorTypes.NETWORK_ERROR:
29296
- errorInfo = ERROR_MAPPING.networkError;
29297
- console.log("[HlsVideoPlayer] Attempting to recover from network error");
29298
- hls.startLoad();
29359
+ if (data.response?.code === 401) {
29360
+ errorInfo = {
29361
+ code: 401,
29362
+ type: "recoverable",
29363
+ message: "Authentication expired. Please refresh the page.",
29364
+ canRetry: true,
29365
+ details: "JWT_EXPIRED"
29366
+ };
29367
+ console.error("[HLS Auth] 401 Unauthorized - token may be expired");
29368
+ } else {
29369
+ errorInfo = ERROR_MAPPING.networkError;
29370
+ console.log("[HlsVideoPlayer] Attempting to recover from network error");
29371
+ hls.startLoad();
29372
+ }
29299
29373
  break;
29300
29374
  case Hls3.ErrorTypes.MEDIA_ERROR:
29301
29375
  errorInfo = ERROR_MAPPING.mediaError;
@@ -29340,8 +29414,19 @@ var HlsVideoPlayer = React24.forwardRef(({
29340
29414
  let errorInfo;
29341
29415
  switch (data.type) {
29342
29416
  case Hls3.ErrorTypes.NETWORK_ERROR:
29343
- errorInfo = ERROR_MAPPING.networkError;
29344
- hls.startLoad();
29417
+ if (data.response?.code === 401) {
29418
+ errorInfo = {
29419
+ code: 401,
29420
+ type: "recoverable",
29421
+ message: "Authentication expired. Please refresh the page.",
29422
+ canRetry: true,
29423
+ details: "JWT_EXPIRED"
29424
+ };
29425
+ console.error("[HLS Auth] 401 Unauthorized - token may be expired");
29426
+ } else {
29427
+ errorInfo = ERROR_MAPPING.networkError;
29428
+ hls.startLoad();
29429
+ }
29345
29430
  break;
29346
29431
  case Hls3.ErrorTypes.MEDIA_ERROR:
29347
29432
  errorInfo = ERROR_MAPPING.mediaError;
@@ -29492,7 +29577,10 @@ var HlsVideoPlayer = React24.forwardRef(({
29492
29577
  configVersion
29493
29578
  ]);
29494
29579
  React24.useEffect(() => {
29495
- const cleanup = initializePlayer();
29580
+ let cleanup;
29581
+ initializePlayer().then((cleanupFn) => {
29582
+ cleanup = cleanupFn;
29583
+ });
29496
29584
  return () => {
29497
29585
  cleanup?.();
29498
29586
  if (hlsRef.current) {
@@ -31699,6 +31787,30 @@ var FileManagerFilters = ({
31699
31787
  if (node.type === "category" || node.type === "percentile-category") {
31700
31788
  toggleExpanded(node.id);
31701
31789
  onFilterChange(node.id);
31790
+ if (node.id === "fast-cycles") {
31791
+ trackCoreEvent("Fast Clips Clicked", {
31792
+ workspaceId,
31793
+ date,
31794
+ shift,
31795
+ count: node.count || 0,
31796
+ percentile: filterState.percentile
31797
+ });
31798
+ } else if (node.id === "slow-cycles") {
31799
+ trackCoreEvent("Slow Clips Clicked", {
31800
+ workspaceId,
31801
+ date,
31802
+ shift,
31803
+ count: node.count || 0,
31804
+ percentile: filterState.percentile
31805
+ });
31806
+ } else if (node.id === "cycle_completion") {
31807
+ trackCoreEvent("Cycle Completions Clicked", {
31808
+ workspaceId,
31809
+ date,
31810
+ shift,
31811
+ count: node.count || 0
31812
+ });
31813
+ }
31702
31814
  if (node.id !== "idle_time" && idleLabelFilter) {
31703
31815
  setIdleLabelFilter(null);
31704
31816
  }
@@ -37213,11 +37325,6 @@ var LinePdfGenerator = ({
37213
37325
  doc.setLineWidth(0.8);
37214
37326
  doc.line(20, 118, 190, 118);
37215
37327
  const hourlyOverviewStartY = 123;
37216
- doc.setFontSize(18);
37217
- doc.setFont("helvetica", "bold");
37218
- doc.setTextColor(40, 40, 40);
37219
- doc.text("Hourly Output Overview", 20, 133);
37220
- doc.setTextColor(0, 0, 0);
37221
37328
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
37222
37329
  const [hours, minutes] = startTimeStr.split(":");
37223
37330
  const startHour = parseInt(hours);
@@ -37408,23 +37515,43 @@ var LinePdfGenerator = ({
37408
37515
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
37409
37516
  });
37410
37517
  }
37411
- const tableHeaderY = 143;
37412
- const tableStartY = 151;
37518
+ const tableHeaderY = 146;
37519
+ const tableStartY = 153;
37413
37520
  const rowSpacing = 8;
37414
37521
  const bottomPadding = 8;
37415
37522
  const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
37416
37523
  const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
37417
37524
  doc.setFillColor(245, 245, 245);
37418
37525
  doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
37526
+ doc.setFontSize(18);
37527
+ doc.setFont("helvetica", "bold");
37528
+ doc.setTextColor(40, 40, 40);
37529
+ doc.text("Hourly Performance", 20, 133);
37530
+ doc.setTextColor(0, 0, 0);
37531
+ const gridTopY = 139;
37532
+ const headerBottomY = 148;
37533
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
37534
+ const totalRows = hourlyTimeRanges.length;
37535
+ const gridBottomY = headerBottomY + totalRows * rowSpacing;
37536
+ const tableHeight = gridBottomY - gridTopY;
37537
+ doc.setFillColor(255, 255, 255);
37538
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "F");
37539
+ doc.setDrawColor(230, 230, 230);
37540
+ doc.setLineWidth(0.2);
37541
+ doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "S");
37542
+ doc.setDrawColor(200, 200, 200);
37543
+ doc.setLineWidth(0.3);
37544
+ doc.line(20, headerBottomY, 190, headerBottomY);
37545
+ colBoundaries.slice(1, -1).forEach((x) => {
37546
+ doc.line(x, gridTopY, x, gridBottomY);
37547
+ });
37419
37548
  doc.setFontSize(11);
37420
37549
  doc.setFont("helvetica", "bold");
37421
37550
  doc.text("Time Range", 25, tableHeaderY);
37422
- doc.text("Output", 80, tableHeaderY);
37423
- doc.text("Target", 125, tableHeaderY);
37424
- doc.text("Status", 170, tableHeaderY);
37425
- doc.setLineWidth(0.3);
37426
- doc.setDrawColor(200, 200, 200);
37427
- doc.line(20, 146, 190, 146);
37551
+ doc.text("Output", 75, tableHeaderY);
37552
+ doc.text("Target", 105, tableHeaderY);
37553
+ doc.text("Status", 135, tableHeaderY);
37554
+ doc.text("Remarks", 160, tableHeaderY);
37428
37555
  doc.setFont("helvetica", "normal");
37429
37556
  let yPos = tableStartY;
37430
37557
  const lineDateForTable = new Date(lineInfo.date);
@@ -37446,20 +37573,25 @@ var LinePdfGenerator = ({
37446
37573
  const dataCollected = !isTodayForTable || hourNumber < currentHour;
37447
37574
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
37448
37575
  const targetStr = targetOutputPerHour.toString();
37576
+ if (index < totalRows - 1) {
37577
+ const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
37578
+ doc.setDrawColor(200, 200, 200);
37579
+ doc.line(20, rowBottomY, 190, rowBottomY);
37580
+ }
37449
37581
  doc.text(timeRange, 25, yPos);
37450
- doc.text(outputStr, 80, yPos);
37451
- doc.text(targetStr, 125, yPos);
37582
+ doc.text(outputStr, 75, yPos);
37583
+ doc.text(targetStr, 105, yPos);
37452
37584
  if (!dataCollected) {
37453
37585
  doc.setTextColor(100, 100, 100);
37454
- doc.text("-", 170, yPos);
37586
+ doc.text("-", 135, yPos);
37455
37587
  } else if (actualOutput >= targetOutputPerHour) {
37456
37588
  doc.setTextColor(0, 171, 69);
37457
37589
  doc.setFont("ZapfDingbats", "normal");
37458
- doc.text("4", 170, yPos);
37590
+ doc.text("4", 135, yPos);
37459
37591
  doc.setFont("helvetica", "normal");
37460
37592
  } else {
37461
37593
  doc.setTextColor(227, 67, 41);
37462
- doc.text("\xD7", 170, yPos);
37594
+ doc.text("\xD7", 135, yPos);
37463
37595
  }
37464
37596
  doc.setTextColor(0, 0, 0);
37465
37597
  yPos += rowSpacing;
@@ -37467,7 +37599,7 @@ var LinePdfGenerator = ({
37467
37599
  doc.addPage();
37468
37600
  yPos = addHeaderPage2();
37469
37601
  const workspaceSectionStartY = yPos;
37470
- const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
37602
+ const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 3);
37471
37603
  const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
37472
37604
  const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
37473
37605
  doc.setFillColor(245, 245, 245);
@@ -37475,28 +37607,45 @@ var LinePdfGenerator = ({
37475
37607
  doc.setFontSize(18);
37476
37608
  doc.setFont("helvetica", "bold");
37477
37609
  doc.setTextColor(40, 40, 40);
37478
- doc.text("Poorest Performing Workspaces", 20, yPos);
37610
+ doc.text("Poorest Performing Workspaces", 20, yPos + 10);
37479
37611
  doc.setTextColor(0, 0, 0);
37480
- yPos += 10;
37612
+ yPos += 20;
37613
+ const wsGridTopY = yPos - 5;
37614
+ const wsHeaderBottomY = wsGridTopY + 8;
37615
+ const wsColBoundaries = [20, 80, 140, 190];
37616
+ const wsGridBottomY = wsHeaderBottomY + wsRowCount * 8;
37617
+ const wsTableTotalHeight = wsGridBottomY - wsGridTopY;
37618
+ doc.setFillColor(255, 255, 255);
37619
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "F");
37620
+ doc.setDrawColor(230, 230, 230);
37621
+ doc.setLineWidth(0.2);
37622
+ doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "S");
37623
+ doc.setDrawColor(200, 200, 200);
37624
+ doc.setLineWidth(0.3);
37625
+ doc.line(20, wsHeaderBottomY, 190, wsHeaderBottomY);
37626
+ wsColBoundaries.slice(1, -1).forEach((x) => {
37627
+ doc.line(x, wsGridTopY, x, wsGridBottomY);
37628
+ });
37481
37629
  doc.setFontSize(11);
37482
37630
  doc.setFont("helvetica", "bold");
37483
- yPos += 5;
37631
+ yPos = wsGridTopY + 5.5;
37484
37632
  doc.text("Workspace", 25, yPos);
37485
37633
  doc.text("Current/Target", 85, yPos);
37486
37634
  doc.text("Efficiency", 145, yPos);
37487
- yPos += 3;
37488
- doc.setLineWidth(0.3);
37489
- doc.setDrawColor(200, 200, 200);
37490
- doc.line(20, yPos, 190, yPos);
37491
37635
  doc.setFont("helvetica", "normal");
37492
- yPos += 7;
37636
+ yPos = wsHeaderBottomY + 5.5;
37493
37637
  if (sortedWorkspaces.length === 0) {
37494
37638
  doc.text("No workspace data available", 25, yPos);
37495
- yPos += 10;
37639
+ yPos += 8;
37496
37640
  } else {
37497
37641
  sortedWorkspaces.forEach((ws, index) => {
37498
37642
  const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
37499
37643
  const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
37644
+ if (index < wsRowCount - 1) {
37645
+ const rowBottomY = wsHeaderBottomY + (index + 1) * 8;
37646
+ doc.setDrawColor(200, 200, 200);
37647
+ doc.line(20, rowBottomY, 190, rowBottomY);
37648
+ }
37500
37649
  doc.text(truncatedName, 25, yPos);
37501
37650
  doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
37502
37651
  doc.text(`${(ws.efficiency || 0).toFixed(1)}%`, 145, yPos);
@@ -38839,18 +38988,27 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38839
38988
  const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38840
38989
  doc.text("Hourly Performance", 20, hourlyTitleY);
38841
38990
  doc.setTextColor(0, 0, 0);
38842
- doc.setFontSize(headerFontSize);
38843
- doc.setFont("helvetica", "bold");
38844
38991
  const baseHeaderY = hasIdleTimeReason ? 208 : 198;
38845
38992
  const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
38846
- doc.text("Time Range", 25, headerY);
38847
- doc.text("Output", 95, headerY);
38848
- doc.text("Target", 135, headerY);
38849
- doc.text("Status", 170, headerY);
38850
- doc.setLineWidth(0.3);
38993
+ const gridTopY = headerY - 5;
38994
+ const headerBottomY = gridTopY + 8;
38995
+ const colBoundaries = [20, 70, 100, 130, 155, 190];
38996
+ const totalRows = hourlyData.length;
38997
+ const gridBottomY = headerBottomY + totalRows * rowHeight;
38851
38998
  doc.setDrawColor(200, 200, 200);
38852
- const separatorY = headerY + 3;
38853
- doc.line(20, separatorY, 190, separatorY);
38999
+ doc.setLineWidth(0.3);
39000
+ doc.line(20, gridTopY, 190, gridTopY);
39001
+ doc.line(20, headerBottomY, 190, headerBottomY);
39002
+ colBoundaries.forEach((x) => {
39003
+ doc.line(x, gridTopY, x, gridBottomY);
39004
+ });
39005
+ doc.setFontSize(headerFontSize);
39006
+ doc.setFont("helvetica", "bold");
39007
+ doc.text("Time Range", 25, headerY);
39008
+ doc.text("Output", 75, headerY);
39009
+ doc.text("Target", 105, headerY);
39010
+ doc.text("Status", 135, headerY);
39011
+ doc.text("Remarks", 160, headerY);
38854
39012
  doc.setFontSize(contentFontSize);
38855
39013
  doc.setFont("helvetica", "normal");
38856
39014
  let yPos = tableStartY;
@@ -38881,20 +39039,23 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38881
39039
  const dataCollected = !isToday2 || hourNumber < currentHour;
38882
39040
  const outputStr = dataCollected ? output.toString() : "TBD";
38883
39041
  const targetStr = hourlyTarget.toString();
39042
+ const rowBottomY = headerBottomY + (index + 1) * rowHeight;
39043
+ doc.setDrawColor(200, 200, 200);
39044
+ doc.line(20, rowBottomY, 190, rowBottomY);
38884
39045
  doc.text(timeRange, 25, yPos);
38885
- doc.text(outputStr, 95, yPos);
38886
- doc.text(targetStr, 135, yPos);
39046
+ doc.text(outputStr, 75, yPos);
39047
+ doc.text(targetStr, 105, yPos);
38887
39048
  if (!dataCollected) {
38888
39049
  doc.setTextColor(100, 100, 100);
38889
- doc.text("-", 170, yPos);
39050
+ doc.text("-", 135, yPos);
38890
39051
  } else if (output >= hourlyTarget) {
38891
39052
  doc.setTextColor(0, 171, 69);
38892
39053
  doc.setFont("ZapfDingbats", "normal");
38893
- doc.text("4", 170, yPos);
39054
+ doc.text("4", 135, yPos);
38894
39055
  doc.setFont("helvetica", "normal");
38895
39056
  } else {
38896
39057
  doc.setTextColor(227, 67, 41);
38897
- doc.text("\xD7", 170, yPos);
39058
+ doc.text("\xD7", 135, yPos);
38898
39059
  }
38899
39060
  doc.setTextColor(0, 0, 0);
38900
39061
  yPos += rowHeight;
@@ -39700,14 +39861,16 @@ var KPICard = ({
39700
39861
  "uppercase tracking-wide sm:tracking-wider"
39701
39862
  ), children: title }),
39702
39863
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-0.5 sm:gap-2 flex-wrap", children: [
39703
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39704
- "font-bold text-gray-900 dark:text-gray-50",
39705
- "text-base sm:text-xl md:text-2xl"
39706
- ), children: isLoading ? "\u2014" : formattedValue }),
39707
- suffix && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39708
- "font-medium text-gray-600 dark:text-gray-300",
39709
- style.compact ? "text-base" : "text-lg"
39710
- ), children: suffix }),
39864
+ isLoading ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39865
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39866
+ "font-bold text-gray-900 dark:text-gray-50",
39867
+ "text-base sm:text-xl md:text-2xl"
39868
+ ), children: formattedValue }),
39869
+ suffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
39870
+ "font-medium text-gray-600 dark:text-gray-300",
39871
+ style.compact ? "text-base" : "text-lg"
39872
+ ), children: suffix })
39873
+ ] }),
39711
39874
  !isLoading && trendInfo.shouldShowTrend && trendInfo.trendValue !== "neutral" && title !== "Output" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: trendInfo.colorClass, children: /* @__PURE__ */ jsxRuntime.jsx(
39712
39875
  trendInfo.Icon,
39713
39876
  {
@@ -39866,6 +40029,7 @@ var KPIHeader = ({
39866
40029
  };
39867
40030
  var KPISection = React24.memo(({
39868
40031
  kpis,
40032
+ isLoading = false,
39869
40033
  className,
39870
40034
  layout: layout2 = "row",
39871
40035
  gridColumns,
@@ -39874,7 +40038,8 @@ var KPISection = React24.memo(({
39874
40038
  compactCards = false,
39875
40039
  useSrcLayout = false
39876
40040
  }) => {
39877
- const outputDifference = kpis.outputProgress.current - kpis.outputProgress.idealOutput;
40041
+ const showSkeleton = isLoading || !kpis;
40042
+ const outputDifference = kpis ? kpis.outputProgress.current - kpis.outputProgress.idealOutput : 0;
39878
40043
  const outputIsOnTarget = outputDifference >= 0;
39879
40044
  if (useSrcLayout) {
39880
40045
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -39891,27 +40056,30 @@ var KPISection = React24.memo(({
39891
40056
  KPICard,
39892
40057
  {
39893
40058
  title: "Underperforming",
39894
- value: "2/3",
39895
- change: 0
40059
+ value: showSkeleton ? "" : "2/3",
40060
+ change: 0,
40061
+ isLoading: showSkeleton
39896
40062
  }
39897
40063
  ) }),
39898
40064
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
39899
40065
  KPICard,
39900
40066
  {
39901
40067
  title: "Efficiency",
39902
- value: kpis.efficiency.value,
39903
- change: kpis.efficiency.change,
39904
- suffix: "%"
40068
+ value: showSkeleton ? "" : kpis.efficiency.value,
40069
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40070
+ suffix: "%",
40071
+ isLoading: showSkeleton
39905
40072
  }
39906
40073
  ) }),
39907
40074
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
39908
40075
  KPICard,
39909
40076
  {
39910
40077
  title: "Output Progress",
39911
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39912
- change: kpis.outputProgress.change,
40078
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40079
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
39913
40080
  outputDifference,
39914
- showOutputDetails: true
40081
+ showOutputDetails: !showSkeleton,
40082
+ isLoading: showSkeleton
39915
40083
  }
39916
40084
  ) })
39917
40085
  ]
@@ -39922,34 +40090,30 @@ var KPISection = React24.memo(({
39922
40090
  {
39923
40091
  key: "underperforming",
39924
40092
  title: "Underperforming",
39925
- value: `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
39926
- change: kpis.underperformingWorkers.change
40093
+ value: showSkeleton ? "" : `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
40094
+ change: showSkeleton ? 0 : kpis.underperformingWorkers.change,
40095
+ suffix: void 0,
40096
+ status: void 0
39927
40097
  },
39928
40098
  {
39929
40099
  key: "efficiency",
39930
40100
  title: "Efficiency",
39931
- value: kpis.efficiency.value,
39932
- change: kpis.efficiency.change,
39933
- suffix: "%"
40101
+ value: showSkeleton ? "" : kpis.efficiency.value,
40102
+ change: showSkeleton ? 0 : kpis.efficiency.change,
40103
+ suffix: "%",
40104
+ status: void 0
39934
40105
  },
39935
40106
  {
39936
40107
  key: "outputProgress",
39937
40108
  title: "Output Progress",
39938
- value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
39939
- change: kpis.outputProgress.change,
39940
- status: {
40109
+ value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
40110
+ change: showSkeleton ? 0 : kpis.outputProgress.change,
40111
+ suffix: void 0,
40112
+ status: showSkeleton ? void 0 : {
39941
40113
  tooltipText: outputIsOnTarget ? "On Target" : "Off Target",
39942
40114
  positive: outputIsOnTarget
39943
40115
  }
39944
40116
  }
39945
- // Only include these additional KPIs if not using the src layout
39946
- // ...(kpis.qualityCompliance ? [{
39947
- // key: 'qualityCompliance',
39948
- // title: 'Quality Compliance',
39949
- // value: kpis.qualityCompliance.value,
39950
- // change: kpis.qualityCompliance.change,
39951
- // suffix: '%',
39952
- // }] : []),
39953
40117
  ];
39954
40118
  return /* @__PURE__ */ jsxRuntime.jsx(
39955
40119
  KPIGrid,
@@ -39966,7 +40130,8 @@ var KPISection = React24.memo(({
39966
40130
  change: kpi.change,
39967
40131
  suffix: kpi.suffix,
39968
40132
  status: kpi.status,
39969
- style: { variant: cardVariant, compact: compactCards }
40133
+ style: { variant: cardVariant, compact: compactCards },
40134
+ isLoading: showSkeleton
39970
40135
  },
39971
40136
  kpi.key
39972
40137
  ))
@@ -39975,6 +40140,9 @@ var KPISection = React24.memo(({
39975
40140
  }, (prevProps, nextProps) => {
39976
40141
  const prevKpis = prevProps.kpis;
39977
40142
  const nextKpis = nextProps.kpis;
40143
+ if (prevProps.isLoading !== nextProps.isLoading) return false;
40144
+ if (!prevKpis && !nextKpis) return true;
40145
+ if (!prevKpis || !nextKpis) return false;
39978
40146
  if (prevKpis === nextKpis) return true;
39979
40147
  if (Math.abs(prevKpis.efficiency.value - nextKpis.efficiency.value) >= 0.5) return false;
39980
40148
  if (prevKpis.underperformingWorkers.current !== nextKpis.underperformingWorkers.current || prevKpis.underperformingWorkers.total !== nextKpis.underperformingWorkers.total) return false;
@@ -45727,7 +45895,8 @@ var TeamUsagePdfGenerator = ({
45727
45895
  usageData,
45728
45896
  daysInRange = 1,
45729
45897
  title = "Team Usage Report",
45730
- className
45898
+ className,
45899
+ iconOnly = false
45731
45900
  }) => {
45732
45901
  const [isGenerating, setIsGenerating] = React24.useState(false);
45733
45902
  const generatePDF = async () => {
@@ -45907,10 +46076,11 @@ var TeamUsagePdfGenerator = ({
45907
46076
  {
45908
46077
  onClick: generatePDF,
45909
46078
  disabled: isGenerating || !usageData,
45910
- 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 || ""}`,
46079
+ 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 || ""}`,
46080
+ "aria-label": iconOnly ? isGenerating ? "Generating PDF..." : "Export PDF" : void 0,
45911
46081
  children: [
45912
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" }),
45913
- isGenerating ? "Generating..." : "Export PDF"
46082
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: iconOnly ? "h-5 w-5" : "h-4 w-4" }),
46083
+ !iconOnly && (isGenerating ? "Generating..." : "Export PDF")
45914
46084
  ]
45915
46085
  }
45916
46086
  );
@@ -48679,10 +48849,19 @@ function HomeView({
48679
48849
  const [errorMessage, setErrorMessage] = React24.useState(null);
48680
48850
  const [displayNamesInitialized, setDisplayNamesInitialized] = React24.useState(false);
48681
48851
  const [hasInitialDataLoaded, setHasInitialDataLoaded] = React24.useState(false);
48852
+ const KPI_CACHE_KEY = "optifye_kpi_cache";
48682
48853
  const dashboardConfig = useDashboardConfig();
48683
48854
  const entityConfig = useEntityConfig();
48684
48855
  const supabaseClient = useSupabaseClient();
48685
48856
  const { user } = useAuth();
48857
+ const { lines: dbLines } = useLines();
48858
+ const mergedLineNames = React24.useMemo(() => {
48859
+ const merged = { ...lineNames };
48860
+ dbLines.forEach((line) => {
48861
+ merged[line.id] = line.line_name;
48862
+ });
48863
+ return merged;
48864
+ }, [lineNames, dbLines]);
48686
48865
  const isSupervisor = user?.role_level === "supervisor";
48687
48866
  const hasMultipleLines = allLineIds.length > 1;
48688
48867
  const availableLineIds = React24.useMemo(() => {
@@ -48789,6 +48968,42 @@ function HomeView({
48789
48968
  }
48790
48969
  return buildKPIsFromLineMetricsRow(row);
48791
48970
  }, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
48971
+ const [cachedKpis, setCachedKpis] = React24.useState(() => {
48972
+ if (typeof window === "undefined") return null;
48973
+ try {
48974
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
48975
+ if (cached) {
48976
+ const parsed = JSON.parse(cached);
48977
+ return parsed[selectedLineId] || parsed[factoryViewId] || null;
48978
+ }
48979
+ } catch (e) {
48980
+ }
48981
+ return null;
48982
+ });
48983
+ React24.useEffect(() => {
48984
+ if (kpis && typeof window !== "undefined") {
48985
+ try {
48986
+ const existing = localStorage.getItem(KPI_CACHE_KEY);
48987
+ const cache = existing ? JSON.parse(existing) : {};
48988
+ cache[selectedLineId] = kpis;
48989
+ localStorage.setItem(KPI_CACHE_KEY, JSON.stringify(cache));
48990
+ setCachedKpis(kpis);
48991
+ } catch (e) {
48992
+ }
48993
+ }
48994
+ }, [kpis, selectedLineId, KPI_CACHE_KEY]);
48995
+ React24.useEffect(() => {
48996
+ if (typeof window === "undefined") return;
48997
+ try {
48998
+ const cached = localStorage.getItem(KPI_CACHE_KEY);
48999
+ if (cached) {
49000
+ const parsed = JSON.parse(cached);
49001
+ setCachedKpis(parsed[selectedLineId] || null);
49002
+ }
49003
+ } catch (e) {
49004
+ }
49005
+ }, [selectedLineId, KPI_CACHE_KEY]);
49006
+ const displayKpis = kpis || cachedKpis;
48792
49007
  const {
48793
49008
  activeBreaks: allActiveBreaks,
48794
49009
  isLoading: breaksLoading,
@@ -49090,16 +49305,16 @@ function HomeView({
49090
49305
  // Use stable string representation instead of spreading array
49091
49306
  JSON.stringify(workspaceMetrics.map((w) => `${w.workspace_uuid}-${Math.round(w.efficiency)}-${w.trend}`))
49092
49307
  ]);
49093
- const memoizedKPIs = React24.useMemo(() => kpis, [
49308
+ const memoizedKPIs = React24.useMemo(() => displayKpis, [
49094
49309
  // Only update reference when values change by at least 1%
49095
- kpis?.efficiency?.value ? Math.round(kpis.efficiency.value) : null,
49096
- kpis?.underperformingWorkers?.current,
49097
- kpis?.underperformingWorkers?.total,
49098
- kpis?.outputProgress?.current,
49099
- kpis?.outputProgress?.target,
49100
- kpis?.avgCycleTime?.value ? Math.round(kpis.avgCycleTime.value * 10) / 10 : null,
49310
+ displayKpis?.efficiency?.value ? Math.round(displayKpis.efficiency.value) : null,
49311
+ displayKpis?.underperformingWorkers?.current,
49312
+ displayKpis?.underperformingWorkers?.total,
49313
+ displayKpis?.outputProgress?.current,
49314
+ displayKpis?.outputProgress?.target,
49315
+ displayKpis?.avgCycleTime?.value ? Math.round(displayKpis.avgCycleTime.value * 10) / 10 : null,
49101
49316
  // Round to 1 decimal
49102
- kpis?.qualityCompliance?.value ? Math.round(kpis.qualityCompliance.value) : null
49317
+ displayKpis?.qualityCompliance?.value ? Math.round(displayKpis.qualityCompliance.value) : null
49103
49318
  ]);
49104
49319
  React24.useEffect(() => {
49105
49320
  setIsHydrated(true);
@@ -49117,7 +49332,7 @@ function HomeView({
49117
49332
  trackCoreEvent("Home Line Filter Changed", {
49118
49333
  previous_line_id: selectedLineId,
49119
49334
  new_line_id: value,
49120
- line_name: lineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49335
+ line_name: mergedLineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
49121
49336
  });
49122
49337
  try {
49123
49338
  sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
@@ -49151,9 +49366,9 @@ function HomeView({
49151
49366
  }
49152
49367
  return /* @__PURE__ */ jsxRuntime.jsxs(Select, { onValueChange: handleLineChange, defaultValue: selectedLineId, children: [
49153
49368
  /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[200px] bg-white border border-gray-200 shadow-sm rounded-md h-9 sm:h-9 text-xs sm:text-sm px-2 sm:px-3", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Select a line" }) }),
49154
- /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: lineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49369
+ /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: mergedLineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
49155
49370
  ] });
49156
- }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
49371
+ }, [availableLineIds, handleLineChange, selectedLineId, mergedLineNames, factoryViewId, allLineIds.length]);
49157
49372
  const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
49158
49373
  const isDataLoading = metricsLoading;
49159
49374
  if (isInitialLoading) {
@@ -49182,7 +49397,7 @@ function HomeView({
49182
49397
  lineTitle,
49183
49398
  lineId: selectedLineId === factoryViewId ? allLineIds[0] : selectedLineId,
49184
49399
  className: "w-full",
49185
- headerControls: memoizedKPIs ? /* @__PURE__ */ jsxRuntime.jsx(KPISection2, { kpis: memoizedKPIs, className: "w-full sm:w-auto" }) : null
49400
+ headerControls: /* @__PURE__ */ jsxRuntime.jsx(KPISection2, { kpis: memoizedKPIs, isLoading: !memoizedKPIs, className: "w-full sm:w-auto" })
49186
49401
  }
49187
49402
  ) }) }),
49188
49403
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative", children: [
@@ -56471,35 +56686,62 @@ var TeamManagementView = ({
56471
56686
  ) }) });
56472
56687
  }
56473
56688
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("min-h-screen bg-slate-50", className), children: [
56474
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
56475
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56476
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56477
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56478
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56479
- ] }),
56480
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
56481
- canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
56482
- TeamUsagePdfGenerator,
56483
- {
56484
- users,
56485
- usageData,
56486
- daysInRange: usageDateRange.daysElapsed,
56487
- title: "Team Usage Report"
56488
- }
56489
- ),
56490
- canAddUsers && /* @__PURE__ */ jsxRuntime.jsxs(
56491
- "button",
56492
- {
56493
- onClick: () => setIsAddUserDialogOpen(true),
56494
- 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",
56495
- children: [
56496
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-4 h-4" }),
56497
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add User" })
56498
- ]
56499
- }
56500
- )
56689
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-6 lg:px-8 py-3 sm:py-4", children: [
56690
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
56691
+ /* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }),
56692
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900 truncate px-2", children: pageTitle }) }),
56693
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
56694
+ canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
56695
+ TeamUsagePdfGenerator,
56696
+ {
56697
+ users,
56698
+ usageData,
56699
+ daysInRange: usageDateRange.daysElapsed,
56700
+ title: "Team Usage Report",
56701
+ iconOnly: true
56702
+ }
56703
+ ),
56704
+ canAddUsers && /* @__PURE__ */ jsxRuntime.jsx(
56705
+ "button",
56706
+ {
56707
+ onClick: () => setIsAddUserDialogOpen(true),
56708
+ 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",
56709
+ "aria-label": "Add User",
56710
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-5 h-5" })
56711
+ }
56712
+ )
56713
+ ] })
56714
+ ] }) }),
56715
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden sm:flex items-center justify-between", children: [
56716
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
56717
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
56718
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
56719
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
56720
+ ] }),
56721
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
56722
+ canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
56723
+ TeamUsagePdfGenerator,
56724
+ {
56725
+ users,
56726
+ usageData,
56727
+ daysInRange: usageDateRange.daysElapsed,
56728
+ title: "Team Usage Report"
56729
+ }
56730
+ ),
56731
+ canAddUsers && /* @__PURE__ */ jsxRuntime.jsxs(
56732
+ "button",
56733
+ {
56734
+ onClick: () => setIsAddUserDialogOpen(true),
56735
+ 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",
56736
+ children: [
56737
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-4 h-4" }),
56738
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add User" })
56739
+ ]
56740
+ }
56741
+ )
56742
+ ] })
56501
56743
  ] })
56502
- ] }) }) }),
56744
+ ] }) }),
56503
56745
  /* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-4 sm:px-6 lg:px-8 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(
56504
56746
  UserManagementTable,
56505
56747
  {