@optifye/dashboard-core 6.12.1 → 6.12.3

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
@@ -2599,19 +2599,206 @@ function getSentry() {
2599
2599
  return null;
2600
2600
  }
2601
2601
  }
2602
+ var SENTRY_HANDLED_EVENT_WINDOW_MS = 10 * 60 * 1e3;
2603
+ var SENTRY_HANDLED_EVENT_SESSION_LIMIT = 20;
2604
+ var SENTRY_QUOTA_STORAGE_KEY = "optifye:sentry-handled-quota:v1";
2605
+ var sentryFingerprintSentAt = /* @__PURE__ */ new Map();
2606
+ var handledSentryEventCount = 0;
2607
+ var UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi;
2608
+ var LONG_HEX_PATTERN = /\b[0-9a-f]{24,}\b/gi;
2609
+ var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
2610
+ var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
2611
+ var URI_PATTERN = /\b[a-z][a-z0-9+.-]{1,31}:\/\/[^\s)]+/gi;
2612
+ var MEDIA_PATH_PATTERN = /\b[^\s)]+\.m3u8(?:\?[^\s)]*)?/gi;
2613
+ var SENSITIVE_ASSIGNMENT_PATTERN = /\b([A-Za-z0-9_.-]*(?:authorization|cookie|password|secret|token|session|email|api[_-]?key)[A-Za-z0-9_.-]*)\b\s*[:=]\s*["']?[^"'\s,;)}\]]+/gi;
2614
+ var SENSITIVE_KEY_PATTERN = /(authorization|cookie|password|secret|token|session|email|api[_-]?key|url|stream|playlist|media)/i;
2615
+ function resetSentryQuotaForTests() {
2616
+ sentryFingerprintSentAt.clear();
2617
+ handledSentryEventCount = 0;
2618
+ const storage = getBrowserSessionStorage();
2619
+ storage?.removeItem(SENTRY_QUOTA_STORAGE_KEY);
2620
+ }
2621
+ var sanitizeString = (value) => {
2622
+ return value.replace(BEARER_PATTERN, "Bearer [redacted]").replace(SENSITIVE_ASSIGNMENT_PATTERN, (_match, key) => `${key}=[redacted]`).replace(URI_PATTERN, "[url]").replace(MEDIA_PATH_PATTERN, "[media]").replace(EMAIL_PATTERN, "[email]").replace(UUID_PATTERN, ":uuid").replace(LONG_HEX_PATTERN, ":id").replace(/\?.*$/, "").replace(/\s+/g, " ").trim();
2623
+ };
2624
+ var sanitizeRoute = (value) => {
2625
+ if (typeof value !== "string" || value.trim().length === 0) return void 0;
2626
+ try {
2627
+ const parsed = new URL(value, "https://optifye.local");
2628
+ return sanitizeString(parsed.pathname);
2629
+ } catch {
2630
+ return sanitizeString(value.split("?")[0] || value);
2631
+ }
2632
+ };
2633
+ var sanitizeValue = (value, depth = 0) => {
2634
+ if (depth > 3) return "[truncated]";
2635
+ if (typeof value === "string") return sanitizeString(value);
2636
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) return value;
2637
+ if (Array.isArray(value)) {
2638
+ return value.slice(0, 20).map((item) => sanitizeValue(item, depth + 1));
2639
+ }
2640
+ if (typeof value === "object") {
2641
+ const sanitized = {};
2642
+ Object.entries(value).forEach(([key, item]) => {
2643
+ sanitized[key] = SENSITIVE_KEY_PATTERN.test(key) ? "[redacted]" : sanitizeValue(item, depth + 1);
2644
+ });
2645
+ return sanitized;
2646
+ }
2647
+ return String(value);
2648
+ };
2649
+ var sanitizeStack = (stack) => {
2650
+ return stack.split("\n").map((line) => sanitizeString(line)).join("\n");
2651
+ };
2652
+ var sanitizeExtras = (extras) => {
2653
+ if (!extras) return void 0;
2654
+ return sanitizeValue(extras);
2655
+ };
2656
+ var getBrowserSessionStorage = () => {
2657
+ if (typeof window === "undefined") return null;
2658
+ try {
2659
+ return window.sessionStorage;
2660
+ } catch {
2661
+ return null;
2662
+ }
2663
+ };
2664
+ var loadSentryQuotaFromStorage = (now4) => {
2665
+ const storage = getBrowserSessionStorage();
2666
+ if (!storage) return;
2667
+ const rawState = storage.getItem(SENTRY_QUOTA_STORAGE_KEY);
2668
+ if (!rawState) return;
2669
+ try {
2670
+ const parsed = JSON.parse(rawState);
2671
+ const persistedCount = Number(parsed.handledEventCount);
2672
+ handledSentryEventCount = Number.isFinite(persistedCount) && persistedCount > 0 ? Math.floor(persistedCount) : 0;
2673
+ sentryFingerprintSentAt.clear();
2674
+ if (parsed.fingerprintSentAt && typeof parsed.fingerprintSentAt === "object") {
2675
+ Object.entries(parsed.fingerprintSentAt).forEach(([fingerprint, sentAt]) => {
2676
+ const sentAtMs = Number(sentAt);
2677
+ if (Number.isFinite(sentAtMs) && now4 - sentAtMs < SENTRY_HANDLED_EVENT_WINDOW_MS) {
2678
+ sentryFingerprintSentAt.set(fingerprint, sentAtMs);
2679
+ }
2680
+ });
2681
+ }
2682
+ } catch {
2683
+ storage.removeItem(SENTRY_QUOTA_STORAGE_KEY);
2684
+ }
2685
+ };
2686
+ var persistSentryQuotaToStorage = () => {
2687
+ const storage = getBrowserSessionStorage();
2688
+ if (!storage) return;
2689
+ try {
2690
+ storage.setItem(
2691
+ SENTRY_QUOTA_STORAGE_KEY,
2692
+ JSON.stringify({
2693
+ handledEventCount: handledSentryEventCount,
2694
+ fingerprintSentAt: Object.fromEntries(sentryFingerprintSentAt)
2695
+ })
2696
+ );
2697
+ } catch {
2698
+ }
2699
+ };
2700
+ var normalizeMessage = (error) => {
2701
+ const message = error instanceof Error ? error.message : String(error ?? "unknown");
2702
+ return sanitizeString(message.toLowerCase()).slice(0, 180) || "unknown";
2703
+ };
2704
+ var getErrorName = (error) => {
2705
+ if (error && typeof error === "object" && "name" in error) {
2706
+ const name = error.name;
2707
+ if (typeof name === "string" && name.trim()) return sanitizeString(name);
2708
+ }
2709
+ return error instanceof Error ? error.constructor.name : typeof error;
2710
+ };
2711
+ var getStatusFromError = (error) => {
2712
+ const message = error instanceof Error ? error.message : String(error ?? "");
2713
+ const statusMatch = message.match(/\((\d{3})\)/) || message.match(/http\s+(\d{3})/i) || message.match(/status:\s*(\d{3})/i);
2714
+ if (!statusMatch) return null;
2715
+ const status = Number.parseInt(statusMatch[1], 10);
2716
+ return Number.isFinite(status) ? status : null;
2717
+ };
2718
+ var hasCaptureOptionShape = (value) => {
2719
+ return "surface" in value || "route" in value || "status" in value || "severity" in value || "quotaKey" in value || "extras" in value;
2720
+ };
2721
+ var normalizeCaptureOptions = (options) => {
2722
+ if (!options) return {};
2723
+ const optionRecord = options;
2724
+ if (!hasCaptureOptionShape(optionRecord)) {
2725
+ return { extras: optionRecord };
2726
+ }
2727
+ const {
2728
+ surface,
2729
+ route,
2730
+ status,
2731
+ severity,
2732
+ quotaKey,
2733
+ extras,
2734
+ ...rest
2735
+ } = options;
2736
+ return {
2737
+ surface: typeof surface === "string" ? surface : void 0,
2738
+ route: typeof route === "string" ? route : void 0,
2739
+ status: typeof status === "number" ? status : null,
2740
+ severity: severity === "info" || severity === "warning" || severity === "error" ? severity : void 0,
2741
+ quotaKey: typeof quotaKey === "string" ? quotaKey : void 0,
2742
+ extras: {
2743
+ ...rest,
2744
+ ...extras && typeof extras === "object" && !Array.isArray(extras) ? extras : {}
2745
+ }
2746
+ };
2747
+ };
2748
+ var buildSentryFingerprint = (error, options) => {
2749
+ if (options.quotaKey) return sanitizeString(options.quotaKey);
2750
+ const surface = sanitizeString(options.surface || "frontend");
2751
+ const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url) || "unknown-route";
2752
+ const status = options.status ?? getStatusFromError(error) ?? "unknown-status";
2753
+ return [
2754
+ surface,
2755
+ route,
2756
+ String(status),
2757
+ getErrorName(error),
2758
+ normalizeMessage(error)
2759
+ ].join("|");
2760
+ };
2761
+ var shouldSendHandledSentryEvent = (fingerprint) => {
2762
+ const now4 = Date.now();
2763
+ loadSentryQuotaFromStorage(now4);
2764
+ const lastSentAt = sentryFingerprintSentAt.get(fingerprint);
2765
+ if (lastSentAt !== void 0 && now4 - lastSentAt < SENTRY_HANDLED_EVENT_WINDOW_MS) {
2766
+ return false;
2767
+ }
2768
+ if (handledSentryEventCount >= SENTRY_HANDLED_EVENT_SESSION_LIMIT) {
2769
+ return false;
2770
+ }
2771
+ sentryFingerprintSentAt.set(fingerprint, now4);
2772
+ handledSentryEventCount += 1;
2773
+ persistSentryQuotaToStorage();
2774
+ return true;
2775
+ };
2776
+ var createSanitizedSentryException = (error) => {
2777
+ if (error instanceof Error) {
2778
+ const sanitizedError = new Error(sanitizeString(error.message) || getErrorName(error) || "Error");
2779
+ sanitizedError.name = getErrorName(error) || "Error";
2780
+ if (typeof error.stack === "string") {
2781
+ sanitizedError.stack = sanitizeStack(error.stack);
2782
+ }
2783
+ return sanitizedError;
2784
+ }
2785
+ if (typeof error === "string") {
2786
+ return sanitizeString(error) || "unknown";
2787
+ }
2788
+ return sanitizeValue(error);
2789
+ };
2602
2790
  function isIgnorableFrontendError(error) {
2603
2791
  const name = error && typeof error === "object" ? error.name || "" : "";
2604
2792
  const message = error instanceof Error ? error.message : String(error ?? "");
2605
2793
  const lowerMessage = message.toLowerCase();
2606
- return name === "AbortError" || lowerMessage.includes("the operation was aborted") || lowerMessage.includes("signal is aborted") || lowerMessage.includes("resizeobserver loop");
2794
+ return name === "AbortError" || name === "NotAllowedError" || lowerMessage.includes("the operation was aborted") || lowerMessage.includes("signal is aborted") || lowerMessage.includes("resizeobserver loop") || lowerMessage.includes("play() failed") || lowerMessage.includes("play request was interrupted") || lowerMessage.includes("autoplay") || lowerMessage.includes("not allowed by the user agent") || lowerMessage.includes("recoverable hls") || lowerMessage.includes("non-fatal hls") || lowerMessage.includes("fragloaderror") || lowerMessage.includes("frag load error") || lowerMessage.includes("fragloadtimeout") || lowerMessage.includes("bufferstallederror") || lowerMessage.includes("buffer stalled") || lowerMessage.includes("media error recovered") || lowerMessage.includes("recovermediaerror");
2607
2795
  }
2608
2796
  function setSentryUserContext(user) {
2609
2797
  const sentry = getSentry();
2610
2798
  if (!sentry) return;
2611
2799
  if (user) {
2612
2800
  sentry.setUser({
2613
- id: user.id,
2614
- email: user.email
2801
+ id: user.id
2615
2802
  });
2616
2803
  sentry.setTags({
2617
2804
  company_id: user.company_id || "unknown",
@@ -2662,28 +2849,60 @@ function applyScopeExtras(scope, extras) {
2662
2849
  function captureSentryMessage(message, level = "warning", extras) {
2663
2850
  const sentry = getSentry();
2664
2851
  if (!sentry || !sentry.captureMessage) return;
2852
+ const options = normalizeCaptureOptions({ ...normalizeCaptureOptions(extras), severity: level });
2853
+ const fingerprint = buildSentryFingerprint(new Error(message), options);
2854
+ if (!shouldSendHandledSentryEvent(fingerprint)) return;
2855
+ const sanitizedExtras = sanitizeExtras({
2856
+ ...options.extras,
2857
+ sentry_quota_key: fingerprint,
2858
+ sentry_capture_policy: "quota_controlled"
2859
+ });
2860
+ const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url);
2861
+ const sanitizedMessage = sanitizeString(message) || "Sentry message";
2665
2862
  if (sentry.withScope) {
2666
2863
  sentry.withScope((scope) => {
2667
2864
  scope.setLevel?.(level);
2668
- applyScopeExtras(scope, extras);
2669
- sentry.captureMessage?.(message);
2865
+ scope.setFingerprint?.([fingerprint]);
2866
+ if (options.surface) scope.setTag?.("surface", sanitizeString(options.surface));
2867
+ if (route) scope.setTag?.("route", route);
2868
+ if (options.status !== void 0 && options.status !== null) scope.setTag?.("status", options.status);
2869
+ applyScopeExtras(scope, sanitizedExtras);
2870
+ sentry.captureMessage?.(sanitizedMessage);
2670
2871
  });
2671
2872
  return;
2672
2873
  }
2673
- sentry.captureMessage(message, level);
2874
+ sentry.captureMessage(sanitizedMessage, level);
2674
2875
  }
2675
2876
  function captureSentryException(error, extras) {
2877
+ if (isIgnorableFrontendError(error)) {
2878
+ return;
2879
+ }
2676
2880
  const sentry = getSentry();
2677
2881
  if (!sentry || !sentry.captureException) return;
2882
+ const options = normalizeCaptureOptions(extras);
2883
+ const fingerprint = buildSentryFingerprint(error, options);
2884
+ if (!shouldSendHandledSentryEvent(fingerprint)) return;
2885
+ const sanitizedExtras = sanitizeExtras({
2886
+ ...options.extras,
2887
+ sentry_quota_key: fingerprint,
2888
+ sentry_capture_policy: "quota_controlled"
2889
+ });
2890
+ const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url);
2891
+ const status = options.status ?? getStatusFromError(error);
2892
+ const sanitizedError = createSanitizedSentryException(error);
2678
2893
  if (sentry.withScope) {
2679
2894
  sentry.withScope((scope) => {
2680
- scope.setLevel?.("error");
2681
- applyScopeExtras(scope, extras);
2682
- sentry.captureException?.(error);
2895
+ scope.setLevel?.(options.severity || "error");
2896
+ scope.setFingerprint?.([fingerprint]);
2897
+ if (options.surface) scope.setTag?.("surface", sanitizeString(options.surface));
2898
+ if (route) scope.setTag?.("route", route);
2899
+ if (status !== null && status !== void 0) scope.setTag?.("status", status);
2900
+ applyScopeExtras(scope, sanitizedExtras);
2901
+ sentry.captureException?.(sanitizedError);
2683
2902
  });
2684
2903
  return;
2685
2904
  }
2686
- sentry.captureException(error);
2905
+ sentry.captureException(sanitizedError);
2687
2906
  }
2688
2907
  function captureHandledFrontendException(error, extras) {
2689
2908
  if (isIgnorableFrontendError(error)) {
@@ -2691,6 +2910,23 @@ function captureHandledFrontendException(error, extras) {
2691
2910
  }
2692
2911
  captureSentryException(error, extras);
2693
2912
  }
2913
+ function addSentryBreadcrumb(message, options = {}) {
2914
+ const sentry = getSentry();
2915
+ if (!sentry?.addBreadcrumb) return;
2916
+ const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url);
2917
+ const data = sanitizeExtras({
2918
+ surface: options.surface,
2919
+ route,
2920
+ status: options.status,
2921
+ ...options.extras
2922
+ });
2923
+ sentry.addBreadcrumb({
2924
+ category: options.category || options.surface || "frontend",
2925
+ message: sanitizeString(message),
2926
+ level: options.severity || "info",
2927
+ data
2928
+ });
2929
+ }
2694
2930
 
2695
2931
  // src/lib/services/backendClient.ts
2696
2932
  var ACCESS_TOKEN_REFRESH_BUFFER_MS = 6e4;
@@ -2758,7 +2994,7 @@ var createAbortError = () => {
2758
2994
  return error;
2759
2995
  }
2760
2996
  };
2761
- var getStatusFromError = (error) => {
2997
+ var getStatusFromError2 = (error) => {
2762
2998
  const message = error instanceof Error ? error.message : String(error ?? "");
2763
2999
  const statusMatch = message.match(/\((\d{3})\)/) || message.match(/http\s+(\d{3})/i);
2764
3000
  if (!statusMatch) {
@@ -2774,7 +3010,7 @@ var isRetryableError = (error) => {
2774
3010
  if (isAbortError(error)) {
2775
3011
  return false;
2776
3012
  }
2777
- const status = getStatusFromError(error);
3013
+ const status = getStatusFromError2(error);
2778
3014
  if (status !== null) {
2779
3015
  return status >= 500 || status === 408 || status === 425 || status === 429;
2780
3016
  }
@@ -2798,6 +3034,7 @@ var fetchBackendJson = async (supabase, endpoint, options = {}) => {
2798
3034
  timeoutMs = DEFAULT_TIMEOUT_MS,
2799
3035
  retries = 0,
2800
3036
  retryDelayMs = DEFAULT_RETRY_DELAY_MS,
3037
+ sentry,
2801
3038
  signal: externalSignal,
2802
3039
  ...fetchOptions
2803
3040
  } = options;
@@ -2835,14 +3072,22 @@ var fetchBackendJson = async (supabase, endpoint, options = {}) => {
2835
3072
  } catch (error) {
2836
3073
  const wrappedError = externalSignal?.aborted || controller.signal.aborted && !isAbortError(error) ? createAbortError() : error;
2837
3074
  if (attempt >= retries || !isRetryableError(wrappedError)) {
2838
- captureHandledFrontendException(wrappedError, {
2839
- surface: "backend_client",
2840
- url,
2841
- method,
2842
- retry_attempt: attempt,
2843
- retries,
2844
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
2845
- });
3075
+ if (sentry && sentry.capture !== false) {
3076
+ captureHandledFrontendException(wrappedError, {
3077
+ surface: sentry.surface || "backend_client",
3078
+ route: sentry.route || endpoint,
3079
+ status: sentry.status ?? getStatusFromError2(wrappedError),
3080
+ severity: sentry.severity || "error",
3081
+ quotaKey: sentry.quotaKey,
3082
+ extras: {
3083
+ method,
3084
+ retry_attempt: attempt,
3085
+ retries,
3086
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
3087
+ ...sentry.extras
3088
+ }
3089
+ });
3090
+ }
2846
3091
  throw wrappedError;
2847
3092
  }
2848
3093
  await new Promise((resolve) => globalThis.setTimeout(resolve, retryDelayMs));
@@ -2928,6 +3173,7 @@ var ApiClient = class {
2928
3173
  retryDelay = 1e3,
2929
3174
  silentErrors = true,
2930
3175
  fallbackData = null,
3176
+ sentry,
2931
3177
  ...fetchOptions
2932
3178
  } = options;
2933
3179
  let lastError = null;
@@ -2961,24 +3207,36 @@ var ApiClient = class {
2961
3207
  await new Promise((resolve) => setTimeout(resolve, retryDelay));
2962
3208
  continue;
2963
3209
  }
2964
- if (silentErrors) {
3210
+ if (sentry && sentry.capture !== false) {
2965
3211
  captureHandledFrontendException(lastError, {
3212
+ surface: sentry.surface || "api_client",
3213
+ route: sentry.route || url,
3214
+ status: sentry.status,
3215
+ severity: sentry.severity || "error",
3216
+ quotaKey: sentry.quotaKey,
3217
+ extras: {
3218
+ retries,
3219
+ silent_errors: silentErrors,
3220
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
3221
+ ...sentry.extras
3222
+ }
3223
+ });
3224
+ } else {
3225
+ addSentryBreadcrumb("API client request failed", {
2966
3226
  surface: "api_client",
2967
- url,
2968
- retries,
2969
- silent_errors: true,
2970
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
3227
+ route: url,
3228
+ severity: silentErrors ? "info" : "warning",
3229
+ extras: {
3230
+ retries,
3231
+ silent_errors: silentErrors,
3232
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
3233
+ }
2971
3234
  });
3235
+ }
3236
+ if (silentErrors) {
2972
3237
  console.warn("[ApiClient] All retries exhausted, returning fallback data");
2973
3238
  return fallbackData;
2974
3239
  } else {
2975
- captureHandledFrontendException(lastError, {
2976
- surface: "api_client",
2977
- url,
2978
- retries,
2979
- silent_errors: false,
2980
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
2981
- });
2982
3240
  throw lastError;
2983
3241
  }
2984
3242
  }
@@ -3230,8 +3488,12 @@ var AuthService = class {
3230
3488
  timeout: 1e4,
3231
3489
  // 10 seconds
3232
3490
  retries: 1,
3233
- silentErrors: false
3491
+ silentErrors: false,
3234
3492
  // We want to know about auth errors
3493
+ sentry: {
3494
+ surface: "auth_session_request",
3495
+ route: "/api/auth/session"
3496
+ }
3235
3497
  }
3236
3498
  );
3237
3499
  console.log("[AuthService] Session loaded:", {
@@ -3301,7 +3563,11 @@ var AuthService = class {
3301
3563
  timeout: 1e4,
3302
3564
  // 10 seconds
3303
3565
  retries: 1,
3304
- silentErrors: false
3566
+ silentErrors: false,
3567
+ sentry: {
3568
+ surface: "auth_permissions_request",
3569
+ route: "/api/auth/permissions"
3570
+ }
3305
3571
  }
3306
3572
  );
3307
3573
  console.log("[AuthService] Permissions loaded for role:", data.role);
@@ -5943,12 +6209,16 @@ var workspaceService = {
5943
6209
  return displayNamesMap;
5944
6210
  } catch (error) {
5945
6211
  console.error("Error fetching workspace display names:", error);
5946
- captureSentryException(error, {
6212
+ addSentryBreadcrumb("Workspace display-name fallback failed", {
5947
6213
  surface: "workspace_display_names",
5948
6214
  route: "/api/workspaces/display-names",
5949
- company_id: companyId || null,
5950
- line_id: lineId || null,
5951
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
6215
+ severity: "warning",
6216
+ extras: {
6217
+ company_id: companyId || null,
6218
+ line_id: lineId || null,
6219
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
6220
+ error_message: error instanceof Error ? error.message : String(error)
6221
+ }
5952
6222
  });
5953
6223
  throw error;
5954
6224
  }
@@ -7552,11 +7822,28 @@ var shouldReportMixpanel = (key, isError) => {
7552
7822
  };
7553
7823
  var reportMixpanelWarning = (key, message, extras) => {
7554
7824
  if (!shouldReportMixpanel(key, false)) return;
7555
- captureSentryMessage(message, "warning", { ...baseMixpanelExtras(), ...extras });
7825
+ addSentryBreadcrumb(message, {
7826
+ surface: "mixpanel",
7827
+ severity: "warning",
7828
+ extras: {
7829
+ key,
7830
+ ...baseMixpanelExtras(),
7831
+ ...extras
7832
+ }
7833
+ });
7556
7834
  };
7557
7835
  var reportMixpanelError = (key, error, extras) => {
7558
7836
  if (!shouldReportMixpanel(key, true)) return;
7559
- captureSentryException(error, { ...baseMixpanelExtras(), ...extras });
7837
+ addSentryBreadcrumb("Mixpanel operation failed", {
7838
+ surface: "mixpanel",
7839
+ severity: "warning",
7840
+ extras: {
7841
+ key,
7842
+ error_message: error instanceof Error ? error.message : String(error ?? "unknown"),
7843
+ ...baseMixpanelExtras(),
7844
+ ...extras
7845
+ }
7846
+ });
7560
7847
  };
7561
7848
  var normalizeCoreEventName = (eventName) => {
7562
7849
  const canonicalName = MIXPANEL_EVENT_NAME_ALIASES[eventName] || eventName;
@@ -9573,11 +9860,15 @@ var LinesService = class {
9573
9860
  }));
9574
9861
  } catch (error) {
9575
9862
  console.error("Error fetching lines:", error);
9576
- captureHandledFrontendException(error, {
9863
+ addSentryBreadcrumb("Lines service request failed", {
9577
9864
  surface: "lines_service",
9578
- operation: "getLinesByCompanyId",
9579
- company_id: companyId,
9580
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
9865
+ route: "/api/lines",
9866
+ severity: "warning",
9867
+ extras: {
9868
+ operation: "getLinesByCompanyId",
9869
+ company_id: companyId,
9870
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
9871
+ }
9581
9872
  });
9582
9873
  throw new Error(`Failed to fetch lines: ${error.message}`);
9583
9874
  }
@@ -9628,10 +9919,14 @@ var LinesService = class {
9628
9919
  }));
9629
9920
  } catch (error) {
9630
9921
  console.error("Error fetching all lines:", error);
9631
- captureHandledFrontendException(error, {
9922
+ addSentryBreadcrumb("Lines service request failed", {
9632
9923
  surface: "lines_service",
9633
- operation: "getAllLines",
9634
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
9924
+ route: "/api/lines",
9925
+ severity: "warning",
9926
+ extras: {
9927
+ operation: "getAllLines",
9928
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
9929
+ }
9635
9930
  });
9636
9931
  throw new Error(`Failed to fetch lines: ${error.message}`);
9637
9932
  }
@@ -9691,11 +9986,15 @@ var LinesService = class {
9691
9986
  };
9692
9987
  } catch (error) {
9693
9988
  console.error("Error fetching line:", error);
9694
- captureHandledFrontendException(error, {
9989
+ addSentryBreadcrumb("Lines service request failed", {
9695
9990
  surface: "lines_service",
9696
- operation: "getLineById",
9697
- line_id: lineId,
9698
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
9991
+ route: "/api/lines/:id",
9992
+ severity: "warning",
9993
+ extras: {
9994
+ operation: "getLineById",
9995
+ line_id: lineId,
9996
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
9997
+ }
9699
9998
  });
9700
9999
  throw new Error(`Failed to fetch line: ${error.message}`);
9701
10000
  }
@@ -13004,7 +13303,8 @@ var toWorkspaceDetailedMetrics = ({
13004
13303
  const targetOutput = coerceNumber(data.target_output ?? data.total_day_output, 0);
13005
13304
  const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
13006
13305
  const outputDifference = totalActions - idealOutput;
13007
- const hourlyTargetOutput = Array.isArray(data.hourly_target_output) ? data.hourly_target_output.map((value) => value === null || value === void 0 ? null : coerceNumber(value, 0)) : null;
13306
+ const hasHourlyTargetOutputField = Object.prototype.hasOwnProperty.call(data, "hourly_target_output");
13307
+ const hourlyTargetOutput = Array.isArray(data.hourly_target_output) ? data.hourly_target_output.map((value) => value === null || value === void 0 ? null : coerceNumber(value, 0)) : hasHourlyTargetOutputField ? null : void 0;
13008
13308
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
13009
13309
  const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
13010
13310
  const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
@@ -13169,6 +13469,17 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
13169
13469
  workspaceMetricsStore.setDetailed(transformedData);
13170
13470
  } catch (err) {
13171
13471
  console.error("Error fetching workspace metrics:", err);
13472
+ captureHandledFrontendException(err, {
13473
+ surface: "workspace_detail_metrics",
13474
+ route: "/api/dashboard/workspace/:workspaceId/metrics",
13475
+ extras: {
13476
+ workspace_id: workspaceId,
13477
+ company_id: companyId,
13478
+ date,
13479
+ shift_id: shiftId,
13480
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
13481
+ }
13482
+ });
13172
13483
  setError({ message: err.message, code: err.code });
13173
13484
  } finally {
13174
13485
  isFetchingRef.current = false;
@@ -14309,6 +14620,7 @@ var transformMonitorWorkspaceMetrics = ({
14309
14620
  recent_flow_window_minutes: item.recent_flow_window_minutes ?? null,
14310
14621
  recent_flow_effective_end_at: item.recent_flow_effective_end_at ?? null,
14311
14622
  recent_flow_computed_at: item.recent_flow_computed_at ?? null,
14623
+ recent_flow_forced_zero_after_shift: item.recent_flow_forced_zero_after_shift ?? null,
14312
14624
  scheduled_break_active: item.scheduled_break_active ?? false,
14313
14625
  incoming_wip_current: item.incoming_wip_current ?? null,
14314
14626
  incoming_wip_effective_at: item.incoming_wip_effective_at ?? null,
@@ -21435,20 +21747,26 @@ var apiUtils = {
21435
21747
  const token = sessionResponse.data.session?.access_token;
21436
21748
  if (!token) {
21437
21749
  console.error("API Util: No authentication token available.");
21438
- captureHandledFrontendException(new Error("Authentication required."), {
21750
+ addSentryBreadcrumb("API utility request skipped without auth token", {
21439
21751
  surface: "api_utils",
21440
- endpoint: relativeEndpoint,
21441
- reason: "missing_auth_token"
21752
+ route: relativeEndpoint,
21753
+ severity: "warning",
21754
+ extras: {
21755
+ reason: "missing_auth_token"
21756
+ }
21442
21757
  });
21443
21758
  throw new Error("Authentication required.");
21444
21759
  }
21445
21760
  const baseUrl = config.apiBaseUrl;
21446
21761
  if (!baseUrl) {
21447
21762
  console.error("API Util: apiBaseUrl is not configured.");
21448
- captureHandledFrontendException(new Error("API base URL is not configured."), {
21763
+ addSentryBreadcrumb("API utility request skipped without base URL", {
21449
21764
  surface: "api_utils",
21450
- endpoint: relativeEndpoint,
21451
- reason: "missing_api_base_url"
21765
+ route: relativeEndpoint,
21766
+ severity: "warning",
21767
+ extras: {
21768
+ reason: "missing_api_base_url"
21769
+ }
21452
21770
  });
21453
21771
  throw new Error("API base URL is not configured.");
21454
21772
  }
@@ -21478,11 +21796,14 @@ var apiUtils = {
21478
21796
  return await response.json();
21479
21797
  } catch (error) {
21480
21798
  console.error(`Network or fetch error calling ${endpoint}:`, error);
21481
- captureHandledFrontendException(error, {
21799
+ addSentryBreadcrumb("API utility request failed", {
21482
21800
  surface: "api_utils",
21483
- endpoint,
21484
- method: options.method || "GET",
21485
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
21801
+ route: endpoint,
21802
+ severity: "warning",
21803
+ extras: {
21804
+ method: options.method || "GET",
21805
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
21806
+ }
21486
21807
  });
21487
21808
  if (error instanceof Error) {
21488
21809
  throw error;
@@ -35185,6 +35506,261 @@ var Button = React144.forwardRef(
35185
35506
  }
35186
35507
  );
35187
35508
  Button.displayName = "Button";
35509
+
35510
+ // src/lib/utils/hourlyTargets.ts
35511
+ var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
35512
+ var MINUTES_PER_DAY = 24 * 60;
35513
+ var parseTimeToMinutes2 = (timeString) => {
35514
+ const normalized = stripSeconds2(timeString || "");
35515
+ if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
35516
+ const [hours, minutes] = normalized.split(":").map(Number);
35517
+ return hours * 60 + minutes;
35518
+ };
35519
+ var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
35520
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35521
+ if (!Number.isFinite(shiftStartMinutes)) return [];
35522
+ const normalizedBreaks = [];
35523
+ for (const entry of breaks) {
35524
+ const startRaw = parseTimeToMinutes2(entry.startTime);
35525
+ const endRaw = parseTimeToMinutes2(entry.endTime);
35526
+ if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
35527
+ let start = startRaw;
35528
+ let end = endRaw;
35529
+ if (end <= start) {
35530
+ end += 24 * 60;
35531
+ }
35532
+ if (start < shiftStartMinutes) {
35533
+ start += 24 * 60;
35534
+ end += 24 * 60;
35535
+ }
35536
+ const label = entry.remarks?.trim() || "Break";
35537
+ normalizedBreaks.push({ start, end, label });
35538
+ }
35539
+ return normalizedBreaks;
35540
+ };
35541
+ var roundTarget = (value, mode) => {
35542
+ if (!Number.isFinite(value)) return 0;
35543
+ switch (mode) {
35544
+ case "floor":
35545
+ return Math.floor(value);
35546
+ case "ceil":
35547
+ return Math.ceil(value);
35548
+ case "round":
35549
+ default:
35550
+ return Math.round(value);
35551
+ }
35552
+ };
35553
+ var formatDateKey = (date) => {
35554
+ const year = date.getUTCFullYear();
35555
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
35556
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
35557
+ return `${year}-${month}-${day}`;
35558
+ };
35559
+ var shiftDateKey = (dateKey, deltaDays) => {
35560
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
35561
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
35562
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
35563
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
35564
+ const date = new Date(Date.UTC(year, month - 1, day));
35565
+ date.setUTCDate(date.getUTCDate() + deltaDays);
35566
+ return formatDateKey(date);
35567
+ };
35568
+ var getZonedNowSnapshot = (timeZone, now4) => {
35569
+ const formatter = new Intl.DateTimeFormat("en-US", {
35570
+ timeZone,
35571
+ year: "numeric",
35572
+ month: "2-digit",
35573
+ day: "2-digit",
35574
+ hour: "2-digit",
35575
+ minute: "2-digit",
35576
+ hourCycle: "h23"
35577
+ });
35578
+ const parts = formatter.formatToParts(now4).reduce((acc, part) => {
35579
+ if (part.type !== "literal") {
35580
+ acc[part.type] = part.value;
35581
+ }
35582
+ return acc;
35583
+ }, {});
35584
+ const year = Number(parts.year);
35585
+ const month = Number(parts.month);
35586
+ const day = Number(parts.day);
35587
+ const hour = Number(parts.hour);
35588
+ const minute = Number(parts.minute);
35589
+ return {
35590
+ dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
35591
+ minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
35592
+ };
35593
+ };
35594
+ var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
35595
+ var buildHourlyIntervals = ({
35596
+ shiftStart,
35597
+ shiftEnd,
35598
+ bucketMinutes = 60,
35599
+ fallbackHours = 11
35600
+ }) => {
35601
+ const startMinutes = parseTimeToMinutes2(shiftStart);
35602
+ if (!Number.isFinite(startMinutes)) return [];
35603
+ const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
35604
+ let totalMinutes;
35605
+ const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35606
+ if (!Number.isFinite(endRaw)) {
35607
+ totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
35608
+ } else {
35609
+ let endMinutes = endRaw;
35610
+ if (endMinutes <= startMinutes) {
35611
+ endMinutes += 24 * 60;
35612
+ }
35613
+ totalMinutes = endMinutes - startMinutes;
35614
+ }
35615
+ if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
35616
+ const count = Math.ceil(totalMinutes / bucket);
35617
+ const shiftEndMinutes = startMinutes + totalMinutes;
35618
+ const intervals = [];
35619
+ for (let i = 0; i < count; i += 1) {
35620
+ const start = startMinutes + i * bucket;
35621
+ const end = Math.min(start + bucket, shiftEndMinutes);
35622
+ const minutes = Math.max(0, end - start);
35623
+ if (minutes <= 0) continue;
35624
+ intervals.push({ start, end, minutes });
35625
+ }
35626
+ return intervals;
35627
+ };
35628
+ var computeBreakMinutesByInterval = ({
35629
+ intervals,
35630
+ shiftStart,
35631
+ breaks
35632
+ }) => {
35633
+ if (!intervals.length || !breaks.length) return intervals.map(() => 0);
35634
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35635
+ return intervals.map((interval) => {
35636
+ if (!normalizedBreaks.length) return 0;
35637
+ let total = 0;
35638
+ for (const brk of normalizedBreaks) {
35639
+ const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
35640
+ total += overlap;
35641
+ if (total >= interval.minutes) return interval.minutes;
35642
+ }
35643
+ return Math.min(interval.minutes, total);
35644
+ });
35645
+ };
35646
+ var computeBreakRemarksByInterval = ({
35647
+ intervals,
35648
+ shiftStart,
35649
+ breaks
35650
+ }) => {
35651
+ if (!intervals.length || !breaks.length) return intervals.map(() => "");
35652
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35653
+ return intervals.map((interval) => {
35654
+ const labels = normalizedBreaks.filter((brk) => Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start)) > 0).map((brk) => brk.label).filter((label, index, values) => label && values.indexOf(label) === index);
35655
+ return labels.join(", ");
35656
+ });
35657
+ };
35658
+ var computeEffectiveTargets = ({
35659
+ intervals,
35660
+ breakMinutes,
35661
+ pphThreshold,
35662
+ rounding = "round"
35663
+ }) => {
35664
+ return intervals.map((interval, idx) => {
35665
+ const intervalMinutes = Number(interval?.minutes) || 0;
35666
+ const breakMins = Number(breakMinutes?.[idx]) || 0;
35667
+ const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
35668
+ if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
35669
+ if (plannedWorkMinutes <= 0) return 0;
35670
+ return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
35671
+ });
35672
+ };
35673
+ var buildHourlyTargetPlan = ({
35674
+ shiftStart,
35675
+ shiftEnd,
35676
+ breaks = [],
35677
+ pphThreshold,
35678
+ bucketMinutes = 60,
35679
+ fallbackHours = 11,
35680
+ rounding = "round"
35681
+ }) => {
35682
+ const intervals = buildHourlyIntervals({
35683
+ shiftStart,
35684
+ shiftEnd,
35685
+ bucketMinutes,
35686
+ fallbackHours
35687
+ });
35688
+ const breakMinutes = computeBreakMinutesByInterval({
35689
+ intervals,
35690
+ shiftStart,
35691
+ breaks
35692
+ });
35693
+ const breakRemarks = computeBreakRemarksByInterval({
35694
+ intervals,
35695
+ shiftStart,
35696
+ breaks
35697
+ });
35698
+ const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
35699
+ const targets = computeEffectiveTargets({
35700
+ intervals,
35701
+ breakMinutes,
35702
+ pphThreshold,
35703
+ rounding
35704
+ });
35705
+ return {
35706
+ intervals,
35707
+ breakMinutes,
35708
+ breakRemarks,
35709
+ productiveMinutes,
35710
+ targets
35711
+ };
35712
+ };
35713
+ var isHourlyIntervalComplete = ({
35714
+ reportDate,
35715
+ shiftStart,
35716
+ shiftEnd,
35717
+ interval,
35718
+ timeZone = "Asia/Kolkata",
35719
+ now: now4 = /* @__PURE__ */ new Date()
35720
+ }) => {
35721
+ if (!reportDate) return true;
35722
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35723
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35724
+ const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35725
+ const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
35726
+ if (reportDate === snapshot.dateKey) {
35727
+ return interval.end <= snapshot.minutesOfDay;
35728
+ }
35729
+ if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35730
+ return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
35731
+ }
35732
+ return reportDate < snapshot.dateKey;
35733
+ };
35734
+ var isShiftInProgressForReportDate = ({
35735
+ reportDate,
35736
+ shiftStart,
35737
+ shiftEnd,
35738
+ timeZone = "Asia/Kolkata",
35739
+ now: now4 = /* @__PURE__ */ new Date()
35740
+ }) => {
35741
+ if (!reportDate || !shiftStart || !shiftEnd) return false;
35742
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35743
+ const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
35744
+ if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
35745
+ return false;
35746
+ }
35747
+ let shiftEndMinutes = shiftEndMinutesRaw;
35748
+ const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
35749
+ if (wrapsMidnight) {
35750
+ shiftEndMinutes += MINUTES_PER_DAY;
35751
+ }
35752
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35753
+ let currentMinutes = null;
35754
+ if (reportDate === snapshot.dateKey) {
35755
+ currentMinutes = snapshot.minutesOfDay;
35756
+ } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35757
+ currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
35758
+ }
35759
+ if (currentMinutes === null) {
35760
+ return false;
35761
+ }
35762
+ return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
35763
+ };
35188
35764
  var padTime = (value) => value.toString().padStart(2, "0");
35189
35765
  var parseTime = (timeValue) => {
35190
35766
  if (!timeValue) return null;
@@ -35566,6 +36142,7 @@ var HourlyOutputChartComponent = ({
35566
36142
  hourlyTargetOutput,
35567
36143
  shiftStart,
35568
36144
  shiftEnd,
36145
+ shiftBreaks = [],
35569
36146
  showIdleTime = false,
35570
36147
  idleTimeHourly,
35571
36148
  shiftDate,
@@ -35803,13 +36380,41 @@ var HourlyOutputChartComponent = ({
35803
36380
  end: index === skuTimelineSegments.length - 1 ? Math.max(segment.start, targetLineEndOffset) : segment.end
35804
36381
  })).filter((segment) => segment.end > segment.start);
35805
36382
  }, [skuTimelineSegments, targetLineEndOffset]);
35806
- const hasHourlyTargetOutputProp = React144__default.useMemo(
36383
+ const hasExplicitHourlyTargetOutputProp = React144__default.useMemo(
35807
36384
  () => hourlyTargetOutput !== void 0,
35808
36385
  [hourlyTargetOutput]
35809
36386
  );
36387
+ const fallbackHourlyTargetOutput = React144__default.useMemo(() => {
36388
+ if (hasExplicitHourlyTargetOutputProp) return void 0;
36389
+ if (skuTimelineSegments.length > 0) return void 0;
36390
+ const plan = buildHourlyTargetPlan({
36391
+ shiftStart,
36392
+ shiftEnd,
36393
+ breaks: shiftBreaks,
36394
+ pphThreshold,
36395
+ rounding: "floor"
36396
+ });
36397
+ if (!plan.targets.length) return void 0;
36398
+ return plan.targets.map((value) => Number.isFinite(value) ? value : null);
36399
+ }, [
36400
+ hasExplicitHourlyTargetOutputProp,
36401
+ skuTimelineSegments.length,
36402
+ shiftStart,
36403
+ shiftEnd,
36404
+ shiftBreaks,
36405
+ pphThreshold
36406
+ ]);
36407
+ const effectiveHourlyTargetOutput = React144__default.useMemo(
36408
+ () => hasExplicitHourlyTargetOutputProp ? hourlyTargetOutput : fallbackHourlyTargetOutput,
36409
+ [hasExplicitHourlyTargetOutputProp, hourlyTargetOutput, fallbackHourlyTargetOutput]
36410
+ );
36411
+ const hasHourlyTargetOutputProp = React144__default.useMemo(
36412
+ () => effectiveHourlyTargetOutput !== void 0,
36413
+ [effectiveHourlyTargetOutput]
36414
+ );
35810
36415
  const hasExplicitHourlyTargets = React144__default.useMemo(
35811
- () => Array.isArray(hourlyTargetOutput) && hourlyTargetOutput.some((value) => value !== null && value !== void 0),
35812
- [hourlyTargetOutput]
36416
+ () => Array.isArray(effectiveHourlyTargetOutput) && effectiveHourlyTargetOutput.some((value) => value !== null && value !== void 0),
36417
+ [effectiveHourlyTargetOutput]
35813
36418
  );
35814
36419
  const hourlyTargetSegments = React144__default.useMemo(() => {
35815
36420
  if (!hasExplicitHourlyTargets) return [];
@@ -35823,7 +36428,7 @@ var HourlyOutputChartComponent = ({
35823
36428
  runValue = null;
35824
36429
  };
35825
36430
  for (let i = 0; i < SHIFT_DURATION; i += 1) {
35826
- const rawValue = Array.isArray(hourlyTargetOutput) ? hourlyTargetOutput[i] : null;
36431
+ const rawValue = Array.isArray(effectiveHourlyTargetOutput) ? effectiveHourlyTargetOutput[i] : null;
35827
36432
  const value = rawValue === null || rawValue === void 0 ? null : Number(rawValue);
35828
36433
  if (value === null || !Number.isFinite(value)) {
35829
36434
  flush(i);
@@ -35842,7 +36447,7 @@ var HourlyOutputChartComponent = ({
35842
36447
  }
35843
36448
  flush(SHIFT_DURATION);
35844
36449
  return segments.filter((segment) => segment.end > segment.start);
35845
- }, [SHIFT_DURATION, hasExplicitHourlyTargets, hourlyTargetOutput]);
36450
+ }, [SHIFT_DURATION, hasExplicitHourlyTargets, effectiveHourlyTargetOutput]);
35846
36451
  const activeSkuHourIndices = React144__default.useMemo(() => {
35847
36452
  const indices = /* @__PURE__ */ new Set();
35848
36453
  const targets = Array(SHIFT_DURATION).fill(pphThreshold);
@@ -35880,7 +36485,7 @@ var HourlyOutputChartComponent = ({
35880
36485
  const { indices, targets, hasTimeline } = activeSkuHourIndices;
35881
36486
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
35882
36487
  const idleSlot = idleSlots[i];
35883
- const explicitTarget = hasHourlyTargetOutputProp ? hourlyTargetOutput?.[i] ?? null : void 0;
36488
+ const explicitTarget = hasHourlyTargetOutputProp ? effectiveHourlyTargetOutput?.[i] ?? null : void 0;
35884
36489
  const currentTarget = hasHourlyTargetOutputProp ? explicitTarget : targets[i] || pphThreshold;
35885
36490
  const comparisonTarget = currentTarget === null || currentTarget === void 0 ? targets[i] || pphThreshold : currentTarget;
35886
36491
  return {
@@ -35899,7 +36504,7 @@ var HourlyOutputChartComponent = ({
35899
36504
  isDimmed: hasTimeline && !!activeSkuId && !indices.has(i)
35900
36505
  };
35901
36506
  });
35902
- }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, hourlyTargetOutput, hasHourlyTargetOutputProp]);
36507
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, effectiveHourlyTargetOutput, hasHourlyTargetOutputProp]);
35903
36508
  const renderSkuTimelineRail = React144__default.useCallback((props) => {
35904
36509
  if (!skuTimelineSegments.length || SHIFT_DURATION <= 0) return null;
35905
36510
  const offset = props?.offset;
@@ -36558,6 +37163,16 @@ var HourlyOutputChart = React144__default.memo(
36558
37163
  if (!prevProps.data.every((val, idx) => val === nextProps.data[idx])) {
36559
37164
  return false;
36560
37165
  }
37166
+ const prevHasHourlyTargetOutputProp = prevProps.hourlyTargetOutput !== void 0;
37167
+ const nextHasHourlyTargetOutputProp = nextProps.hourlyTargetOutput !== void 0;
37168
+ if (prevHasHourlyTargetOutputProp !== nextHasHourlyTargetOutputProp) {
37169
+ return false;
37170
+ }
37171
+ if (prevProps.hourlyTargetOutput === null || nextProps.hourlyTargetOutput === null) {
37172
+ if (prevProps.hourlyTargetOutput !== nextProps.hourlyTargetOutput) {
37173
+ return false;
37174
+ }
37175
+ }
36561
37176
  const prevHourlyTargets = prevProps.hourlyTargetOutput || [];
36562
37177
  const nextHourlyTargets = nextProps.hourlyTargetOutput || [];
36563
37178
  if (prevHourlyTargets.length !== nextHourlyTargets.length) {
@@ -36568,6 +37183,18 @@ var HourlyOutputChart = React144__default.memo(
36568
37183
  return false;
36569
37184
  }
36570
37185
  }
37186
+ const prevShiftBreaks = prevProps.shiftBreaks || [];
37187
+ const nextShiftBreaks = nextProps.shiftBreaks || [];
37188
+ if (prevShiftBreaks.length !== nextShiftBreaks.length) {
37189
+ return false;
37190
+ }
37191
+ for (let i = 0; i < prevShiftBreaks.length; i += 1) {
37192
+ const prevBreak = prevShiftBreaks[i] || {};
37193
+ const nextBreak = nextShiftBreaks[i] || {};
37194
+ if (prevBreak.startTime !== nextBreak.startTime || prevBreak.endTime !== nextBreak.endTime || prevBreak.duration !== nextBreak.duration || prevBreak.remarks !== nextBreak.remarks) {
37195
+ return false;
37196
+ }
37197
+ }
36571
37198
  const prevIdle = prevProps.idleTimeHourly || {};
36572
37199
  const nextIdle = nextProps.idleTimeHourly || {};
36573
37200
  const prevKeys = Object.keys(prevIdle);
@@ -36660,6 +37287,9 @@ var isLowWipGreenOverride = (workspace, legend = DEFAULT_EFFICIENCY_LEGEND) => {
36660
37287
  if (workspace.scheduled_break_active === true) {
36661
37288
  return false;
36662
37289
  }
37290
+ if (workspace.recent_flow_forced_zero_after_shift === true) {
37291
+ return false;
37292
+ }
36663
37293
  if (!hasVideoGridRecentFlow(workspace) || !isVideoGridWipGated(workspace)) {
36664
37294
  return false;
36665
37295
  }
@@ -36905,7 +37535,7 @@ var VideoCard = React144__default.memo(({
36905
37535
  }
36906
37536
  );
36907
37537
  }, (prevProps, nextProps) => {
36908
- if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
37538
+ if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
36909
37539
  return false;
36910
37540
  }
36911
37541
  if (prevProps.workspace.workspace_uuid !== nextProps.workspace.workspace_uuid || prevProps.workspace.workspace_name !== nextProps.workspace.workspace_name || prevProps.workspace.line_id !== nextProps.workspace.line_id) {
@@ -37224,6 +37854,15 @@ var VideoGridView = React144__default.memo(({
37224
37854
  }
37225
37855
  console.error(`[VideoGridView] Stream failed for workspace: ${workspaceId}`);
37226
37856
  setFailedStreams((prev) => new Set(prev).add(workspaceId));
37857
+ captureHandledFrontendException(new Error("Video stream failed after recovery attempts"), {
37858
+ surface: "video_grid_stream",
37859
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
37860
+ extras: {
37861
+ workspace_id: workspaceId,
37862
+ stream_source: isR2Stream ? "r2" : "media_config",
37863
+ has_fallback: hasFallback
37864
+ }
37865
+ });
37227
37866
  trackCoreEvent("Video Stream Error", {
37228
37867
  workspace_id: workspaceId,
37229
37868
  view_type: "video_grid",
@@ -41891,11 +42530,14 @@ var SilentErrorBoundary = class extends React144__default.Component {
41891
42530
  componentStack: errorInfo.componentStack,
41892
42531
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
41893
42532
  });
41894
- captureSentryException(error, {
42533
+ captureHandledFrontendException(error, {
41895
42534
  surface: "react_error_boundary",
41896
- component_stack: errorInfo.componentStack,
41897
- error_count: this.state.errorCount + 1,
41898
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
42535
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
42536
+ severity: "error",
42537
+ extras: {
42538
+ component_stack: errorInfo.componentStack,
42539
+ error_count: this.state.errorCount + 1
42540
+ }
41899
42541
  });
41900
42542
  this.setState((prev) => ({
41901
42543
  errorCount: prev.errorCount + 1,
@@ -44432,6 +45074,11 @@ var OVERLAY_ICON_COLOR_BY_PALETTE = {
44432
45074
  cyan: "text-cyan-300",
44433
45075
  slate: "text-slate-200"
44434
45076
  };
45077
+ var buildPlaybackCaptureError = (recoverable) => {
45078
+ return new Error(
45079
+ recoverable ? "Clip playback failed after retry exhaustion" : "Clip playback failed with non-recoverable media error"
45080
+ );
45081
+ };
44435
45082
  var BottlenecksContent = ({
44436
45083
  workspaceId,
44437
45084
  workspaceName,
@@ -44886,6 +45533,16 @@ var BottlenecksContent = ({
44886
45533
  }
44887
45534
  } catch (err) {
44888
45535
  console.error("Error loading first video for category:", err);
45536
+ captureHandledFrontendException(err, {
45537
+ surface: "clips_initial_load",
45538
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
45539
+ extras: {
45540
+ workspace_id: workspaceId,
45541
+ category: targetCategory,
45542
+ date: effectiveDateString,
45543
+ shift_id: effectiveShiftId
45544
+ }
45545
+ });
44889
45546
  if (isMountedRef.current) {
44890
45547
  setError({
44891
45548
  type: "fatal",
@@ -45423,6 +46080,15 @@ var BottlenecksContent = ({
45423
46080
  console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
45424
46081
  } catch (error2) {
45425
46082
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
46083
+ captureHandledFrontendException(error2, {
46084
+ surface: "clips_selected_load",
46085
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
46086
+ extras: {
46087
+ workspace_id: workspaceId,
46088
+ clip_id: clipId,
46089
+ category: categoryId
46090
+ }
46091
+ });
45426
46092
  if (isMountedRef.current) {
45427
46093
  setError({
45428
46094
  type: "fatal",
@@ -46022,6 +46688,18 @@ var BottlenecksContent = ({
46022
46688
  errorCode,
46023
46689
  errorMessage
46024
46690
  });
46691
+ captureHandledFrontendException(buildPlaybackCaptureError(false), {
46692
+ surface: "clips_video_playback",
46693
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
46694
+ extras: {
46695
+ workspace_id: workspaceId,
46696
+ category: activeFilterRef.current,
46697
+ video_id: currentVideo?.id,
46698
+ error_code: errorCode,
46699
+ player_error_message: errorMessage,
46700
+ recoverable: false
46701
+ }
46702
+ });
46025
46703
  return;
46026
46704
  }
46027
46705
  if (videoRetryCountRef.current < 3 && currentVideo) {
@@ -46065,6 +46743,19 @@ var BottlenecksContent = ({
46065
46743
  errorMessage,
46066
46744
  attempts: 3
46067
46745
  });
46746
+ captureHandledFrontendException(buildPlaybackCaptureError(true), {
46747
+ surface: "clips_video_playback",
46748
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
46749
+ extras: {
46750
+ workspace_id: workspaceId,
46751
+ category: activeFilterRef.current,
46752
+ video_id: currentVideo?.id,
46753
+ error_code: errorCode,
46754
+ player_error_message: errorMessage,
46755
+ recoverable: true,
46756
+ attempts: 3
46757
+ }
46758
+ });
46068
46759
  }
46069
46760
  }, [currentVideo, workspaceId, clearLoadingState, clearRetryTimeout, restartCurrentClipPlayback]);
46070
46761
  useEffect(() => {
@@ -48396,6 +49087,14 @@ var LinePdfExportButton = ({
48396
49087
  pdf.save(`${fileName}.pdf`);
48397
49088
  } catch (error) {
48398
49089
  console.error("PDF Export Error:", error);
49090
+ captureHandledFrontendException(error, {
49091
+ surface: "line_pdf_export",
49092
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
49093
+ extras: {
49094
+ file_name: fileName,
49095
+ target_type: typeof targetElement === "string" ? "selector" : "element"
49096
+ }
49097
+ });
48399
49098
  alert("An error occurred while exporting to PDF. Please try again.");
48400
49099
  } finally {
48401
49100
  setIsExporting(false);
@@ -50218,6 +50917,16 @@ var LineMonthlyPdfGenerator = ({
50218
50917
  doc.save(fileName);
50219
50918
  } catch (error) {
50220
50919
  console.error("Line Monthly PDF generation failed:", error);
50920
+ captureHandledFrontendException(error, {
50921
+ surface: "line_monthly_pdf_generation",
50922
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
50923
+ extras: {
50924
+ line_name: lineName || null,
50925
+ selected_month: selectedMonth,
50926
+ selected_year: selectedYear,
50927
+ selected_shift_id: selectedShiftId
50928
+ }
50929
+ });
50221
50930
  } finally {
50222
50931
  setIsGenerating(false);
50223
50932
  }
@@ -50282,261 +50991,6 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
50282
50991
  }
50283
50992
  );
50284
50993
  };
50285
-
50286
- // src/lib/utils/hourlyTargets.ts
50287
- var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
50288
- var MINUTES_PER_DAY = 24 * 60;
50289
- var parseTimeToMinutes2 = (timeString) => {
50290
- const normalized = stripSeconds2(timeString || "");
50291
- if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
50292
- const [hours, minutes] = normalized.split(":").map(Number);
50293
- return hours * 60 + minutes;
50294
- };
50295
- var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
50296
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50297
- if (!Number.isFinite(shiftStartMinutes)) return [];
50298
- const normalizedBreaks = [];
50299
- for (const entry of breaks) {
50300
- const startRaw = parseTimeToMinutes2(entry.startTime);
50301
- const endRaw = parseTimeToMinutes2(entry.endTime);
50302
- if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
50303
- let start = startRaw;
50304
- let end = endRaw;
50305
- if (end <= start) {
50306
- end += 24 * 60;
50307
- }
50308
- if (start < shiftStartMinutes) {
50309
- start += 24 * 60;
50310
- end += 24 * 60;
50311
- }
50312
- const label = entry.remarks?.trim() || "Break";
50313
- normalizedBreaks.push({ start, end, label });
50314
- }
50315
- return normalizedBreaks;
50316
- };
50317
- var roundTarget = (value, mode) => {
50318
- if (!Number.isFinite(value)) return 0;
50319
- switch (mode) {
50320
- case "floor":
50321
- return Math.floor(value);
50322
- case "ceil":
50323
- return Math.ceil(value);
50324
- case "round":
50325
- default:
50326
- return Math.round(value);
50327
- }
50328
- };
50329
- var formatDateKey = (date) => {
50330
- const year = date.getUTCFullYear();
50331
- const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
50332
- const day = `${date.getUTCDate()}`.padStart(2, "0");
50333
- return `${year}-${month}-${day}`;
50334
- };
50335
- var shiftDateKey = (dateKey, deltaDays) => {
50336
- const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50337
- const year = Number.isFinite(yearPart) ? yearPart : 1970;
50338
- const month = Number.isFinite(monthPart) ? monthPart : 1;
50339
- const day = Number.isFinite(dayPart) ? dayPart : 1;
50340
- const date = new Date(Date.UTC(year, month - 1, day));
50341
- date.setUTCDate(date.getUTCDate() + deltaDays);
50342
- return formatDateKey(date);
50343
- };
50344
- var getZonedNowSnapshot = (timeZone, now4) => {
50345
- const formatter = new Intl.DateTimeFormat("en-US", {
50346
- timeZone,
50347
- year: "numeric",
50348
- month: "2-digit",
50349
- day: "2-digit",
50350
- hour: "2-digit",
50351
- minute: "2-digit",
50352
- hourCycle: "h23"
50353
- });
50354
- const parts = formatter.formatToParts(now4).reduce((acc, part) => {
50355
- if (part.type !== "literal") {
50356
- acc[part.type] = part.value;
50357
- }
50358
- return acc;
50359
- }, {});
50360
- const year = Number(parts.year);
50361
- const month = Number(parts.month);
50362
- const day = Number(parts.day);
50363
- const hour = Number(parts.hour);
50364
- const minute = Number(parts.minute);
50365
- return {
50366
- dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
50367
- minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
50368
- };
50369
- };
50370
- var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
50371
- var buildHourlyIntervals = ({
50372
- shiftStart,
50373
- shiftEnd,
50374
- bucketMinutes = 60,
50375
- fallbackHours = 11
50376
- }) => {
50377
- const startMinutes = parseTimeToMinutes2(shiftStart);
50378
- if (!Number.isFinite(startMinutes)) return [];
50379
- const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
50380
- let totalMinutes;
50381
- const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50382
- if (!Number.isFinite(endRaw)) {
50383
- totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
50384
- } else {
50385
- let endMinutes = endRaw;
50386
- if (endMinutes <= startMinutes) {
50387
- endMinutes += 24 * 60;
50388
- }
50389
- totalMinutes = endMinutes - startMinutes;
50390
- }
50391
- if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
50392
- const count = Math.ceil(totalMinutes / bucket);
50393
- const shiftEndMinutes = startMinutes + totalMinutes;
50394
- const intervals = [];
50395
- for (let i = 0; i < count; i += 1) {
50396
- const start = startMinutes + i * bucket;
50397
- const end = Math.min(start + bucket, shiftEndMinutes);
50398
- const minutes = Math.max(0, end - start);
50399
- if (minutes <= 0) continue;
50400
- intervals.push({ start, end, minutes });
50401
- }
50402
- return intervals;
50403
- };
50404
- var computeBreakMinutesByInterval = ({
50405
- intervals,
50406
- shiftStart,
50407
- breaks
50408
- }) => {
50409
- if (!intervals.length || !breaks.length) return intervals.map(() => 0);
50410
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50411
- return intervals.map((interval) => {
50412
- if (!normalizedBreaks.length) return 0;
50413
- let total = 0;
50414
- for (const brk of normalizedBreaks) {
50415
- const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
50416
- total += overlap;
50417
- if (total >= interval.minutes) return interval.minutes;
50418
- }
50419
- return Math.min(interval.minutes, total);
50420
- });
50421
- };
50422
- var computeBreakRemarksByInterval = ({
50423
- intervals,
50424
- shiftStart,
50425
- breaks
50426
- }) => {
50427
- if (!intervals.length || !breaks.length) return intervals.map(() => "");
50428
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50429
- return intervals.map((interval) => {
50430
- const labels = normalizedBreaks.filter((brk) => Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start)) > 0).map((brk) => brk.label).filter((label, index, values) => label && values.indexOf(label) === index);
50431
- return labels.join(", ");
50432
- });
50433
- };
50434
- var computeEffectiveTargets = ({
50435
- intervals,
50436
- breakMinutes,
50437
- pphThreshold,
50438
- rounding = "round"
50439
- }) => {
50440
- return intervals.map((interval, idx) => {
50441
- const intervalMinutes = Number(interval?.minutes) || 0;
50442
- const breakMins = Number(breakMinutes?.[idx]) || 0;
50443
- const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
50444
- if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
50445
- if (plannedWorkMinutes <= 0) return 0;
50446
- return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
50447
- });
50448
- };
50449
- var buildHourlyTargetPlan = ({
50450
- shiftStart,
50451
- shiftEnd,
50452
- breaks = [],
50453
- pphThreshold,
50454
- bucketMinutes = 60,
50455
- fallbackHours = 11,
50456
- rounding = "round"
50457
- }) => {
50458
- const intervals = buildHourlyIntervals({
50459
- shiftStart,
50460
- shiftEnd,
50461
- bucketMinutes,
50462
- fallbackHours
50463
- });
50464
- const breakMinutes = computeBreakMinutesByInterval({
50465
- intervals,
50466
- shiftStart,
50467
- breaks
50468
- });
50469
- const breakRemarks = computeBreakRemarksByInterval({
50470
- intervals,
50471
- shiftStart,
50472
- breaks
50473
- });
50474
- const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
50475
- const targets = computeEffectiveTargets({
50476
- intervals,
50477
- breakMinutes,
50478
- pphThreshold,
50479
- rounding
50480
- });
50481
- return {
50482
- intervals,
50483
- breakMinutes,
50484
- breakRemarks,
50485
- productiveMinutes,
50486
- targets
50487
- };
50488
- };
50489
- var isHourlyIntervalComplete = ({
50490
- reportDate,
50491
- shiftStart,
50492
- shiftEnd,
50493
- interval,
50494
- timeZone = "Asia/Kolkata",
50495
- now: now4 = /* @__PURE__ */ new Date()
50496
- }) => {
50497
- if (!reportDate) return true;
50498
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50499
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50500
- const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50501
- const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
50502
- if (reportDate === snapshot.dateKey) {
50503
- return interval.end <= snapshot.minutesOfDay;
50504
- }
50505
- if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50506
- return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
50507
- }
50508
- return reportDate < snapshot.dateKey;
50509
- };
50510
- var isShiftInProgressForReportDate = ({
50511
- reportDate,
50512
- shiftStart,
50513
- shiftEnd,
50514
- timeZone = "Asia/Kolkata",
50515
- now: now4 = /* @__PURE__ */ new Date()
50516
- }) => {
50517
- if (!reportDate || !shiftStart || !shiftEnd) return false;
50518
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50519
- const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
50520
- if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
50521
- return false;
50522
- }
50523
- let shiftEndMinutes = shiftEndMinutesRaw;
50524
- const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
50525
- if (wrapsMidnight) {
50526
- shiftEndMinutes += MINUTES_PER_DAY;
50527
- }
50528
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50529
- let currentMinutes = null;
50530
- if (reportDate === snapshot.dateKey) {
50531
- currentMinutes = snapshot.minutesOfDay;
50532
- } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50533
- currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
50534
- }
50535
- if (currentMinutes === null) {
50536
- return false;
50537
- }
50538
- return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
50539
- };
50540
50994
  var formatOperationalDateKey = (dateKey, options) => {
50541
50995
  const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50542
50996
  const year = Number.isFinite(yearPart) ? yearPart : 1970;
@@ -51255,6 +51709,15 @@ var LinePdfGenerator = ({
51255
51709
  doc.save(fileName);
51256
51710
  } catch (error) {
51257
51711
  console.error("PDF generation failed:", error);
51712
+ captureHandledFrontendException(error, {
51713
+ surface: "line_pdf_generation",
51714
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
51715
+ extras: {
51716
+ line_id: lineInfo.line_id,
51717
+ date: lineInfo.date,
51718
+ shift_id: lineInfo.shift_id
51719
+ }
51720
+ });
51258
51721
  } finally {
51259
51722
  setIsGenerating(false);
51260
51723
  }
@@ -51327,6 +51790,14 @@ var WorkspacePdfExportButton = ({
51327
51790
  pdf.save(`${fileName}.pdf`);
51328
51791
  } catch (error) {
51329
51792
  console.error("PDF Export Error:", error);
51793
+ captureHandledFrontendException(error, {
51794
+ surface: "workspace_pdf_export",
51795
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
51796
+ extras: {
51797
+ file_name: fileName,
51798
+ target_type: typeof targetElement === "string" ? "selector" : "element"
51799
+ }
51800
+ });
51330
51801
  alert("An error occurred while exporting to PDF. Please try again.");
51331
51802
  } finally {
51332
51803
  setIsExporting(false);
@@ -53176,6 +53647,16 @@ var WorkspacePdfGenerator = ({
53176
53647
  doc.save(fileName);
53177
53648
  } catch (error) {
53178
53649
  console.error("PDF generation failed:", error);
53650
+ captureHandledFrontendException(error, {
53651
+ surface: "workspace_pdf_generation",
53652
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
53653
+ extras: {
53654
+ workspace_id: workspace.workspace_id,
53655
+ line_id: workspace.line_id,
53656
+ date: workspace.date,
53657
+ shift_id: workspace.shift_id
53658
+ }
53659
+ });
53179
53660
  } finally {
53180
53661
  setIsGenerating(false);
53181
53662
  }
@@ -53583,6 +54064,18 @@ var WorkspaceMonthlyPdfGenerator = ({
53583
54064
  doc.save(fileName);
53584
54065
  } catch (error) {
53585
54066
  console.error("Monthly PDF generation failed:", error);
54067
+ captureHandledFrontendException(error, {
54068
+ surface: "workspace_monthly_pdf_generation",
54069
+ route: typeof window !== "undefined" ? window.location.pathname : "unknown",
54070
+ extras: {
54071
+ workspace_id: workspaceId,
54072
+ workspace_name: workspaceName,
54073
+ line_name: lineName || null,
54074
+ selected_month: selectedMonth,
54075
+ selected_year: selectedYear,
54076
+ selected_shift_id: selectedShiftId
54077
+ }
54078
+ });
53586
54079
  } finally {
53587
54080
  setIsGenerating(false);
53588
54081
  }
@@ -62855,6 +63348,16 @@ var useLiveMonitorBootstrap = ({
62855
63348
  if (requestId !== activeRequestIdRef.current) {
62856
63349
  return;
62857
63350
  }
63351
+ captureHandledFrontendException(fetchError, {
63352
+ surface: "live_monitor_bootstrap",
63353
+ route: "/api/dashboard/monitor-bootstrap",
63354
+ extras: {
63355
+ company_id: resolvedCompanyId,
63356
+ line_ids: normalizedLineIds,
63357
+ force_refresh: force,
63358
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
63359
+ }
63360
+ });
62858
63361
  setError(fetchError instanceof Error ? fetchError : new Error("Failed to load live monitor bootstrap"));
62859
63362
  } finally {
62860
63363
  if (requestId === activeRequestIdRef.current) {
@@ -63167,11 +63670,14 @@ var NotificationService = class {
63167
63670
  }
63168
63671
  return response;
63169
63672
  } catch (error) {
63170
- captureHandledFrontendException(error, {
63673
+ addSentryBreadcrumb("Notification service request failed", {
63171
63674
  surface: "notification_service",
63172
- url,
63173
- method: options.method || "GET",
63174
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
63675
+ route: url,
63676
+ severity: "warning",
63677
+ extras: {
63678
+ method: options.method || "GET",
63679
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
63680
+ }
63175
63681
  });
63176
63682
  throw error;
63177
63683
  }
@@ -64679,7 +65185,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
64679
65185
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
64680
65186
  output_array: metrics2.output_array || [],
64681
65187
  output_hourly: metrics2.output_hourly,
64682
- hourly_target_output: metrics2.hourly_target_output ?? null,
65188
+ hourly_target_output: metrics2.hourly_target_output,
64683
65189
  line_threshold: metrics2.line_threshold ?? 0,
64684
65190
  threshold_pph: metrics2.threshold_pph ?? 0,
64685
65191
  shift_start: metrics2.shift_start || "06:00",
@@ -64766,7 +65272,7 @@ var transformLineMetrics = (lineId, detailResponse, queryDate, queryShiftId) =>
64766
65272
  underperforming_workspace_names: [],
64767
65273
  underperforming_workspace_uuids: [],
64768
65274
  output_array: [],
64769
- hourly_target_output: null,
65275
+ hourly_target_output: void 0,
64770
65276
  line_threshold: 0,
64771
65277
  threshold_pph: 0,
64772
65278
  shift_start: "06:00",
@@ -65805,6 +66311,7 @@ var BottomSection = memo$1(({
65805
66311
  hourlyOutputData,
65806
66312
  hourlyThreshold,
65807
66313
  hourlyTargetOutput,
66314
+ shiftBreaks,
65808
66315
  idleTimeHourly,
65809
66316
  timezone,
65810
66317
  urlDate,
@@ -65980,6 +66487,7 @@ var BottomSection = memo$1(({
65980
66487
  hourlyTargetOutput,
65981
66488
  shiftStart: lineInfo.metrics.shift_start || "06:00",
65982
66489
  shiftEnd: lineInfo.metrics.shift_end,
66490
+ shiftBreaks,
65983
66491
  idleTimeHourly,
65984
66492
  shiftDate: lineInfo.date,
65985
66493
  timezone,
@@ -66003,6 +66511,9 @@ var BottomSection = memo$1(({
66003
66511
  if (prevProps.lineInfo.monitoring_mode !== nextProps.lineInfo.monitoring_mode) return false;
66004
66512
  if (prevProps.skuAware !== nextProps.skuAware) return false;
66005
66513
  if (prevProps.activeSkuId !== nextProps.activeSkuId) return false;
66514
+ if (JSON.stringify(prevProps.shiftBreaks || []) !== JSON.stringify(nextProps.shiftBreaks || [])) {
66515
+ return false;
66516
+ }
66006
66517
  if (JSON.stringify(prevProps.hourlyTargetOutput || []) !== JSON.stringify(nextProps.hourlyTargetOutput || [])) {
66007
66518
  return false;
66008
66519
  }
@@ -66604,7 +67115,7 @@ var KPIDetailView = ({
66604
67115
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
66605
67116
  output_array: metrics2.output_array || [],
66606
67117
  output_hourly: metrics2.output_hourly,
66607
- hourly_target_output: metrics2.hourly_target_output ?? null,
67118
+ hourly_target_output: metrics2.hourly_target_output,
66608
67119
  line_threshold: metrics2.line_threshold ?? 0,
66609
67120
  threshold_pph: metrics2.threshold_pph ?? 0,
66610
67121
  shift_start: metrics2.shift_start || "06:00",
@@ -67568,7 +68079,8 @@ var KPIDetailView = ({
67568
68079
  workspaceDisplayNames,
67569
68080
  hourlyOutputData,
67570
68081
  hourlyThreshold,
67571
- hourlyTargetOutput: chartMetrics?.hourly_target_output ?? null,
68082
+ hourlyTargetOutput: chartMetrics?.hourly_target_output,
68083
+ shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
67572
68084
  idleTimeHourly: chartMetrics?.idle_time_hourly,
67573
68085
  timezone: lineTimezone,
67574
68086
  urlDate,
@@ -75332,6 +75844,7 @@ var WorkspaceDetailView = ({
75332
75844
  hourlyTargetOutput: workspace.hourly_target_output,
75333
75845
  shiftStart: workspace.shift_start || "06:00",
75334
75846
  shiftEnd: workspace.shift_end,
75847
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75335
75848
  showIdleTime: showChartIdleTime,
75336
75849
  idleTimeHourly: workspace.idle_time_hourly,
75337
75850
  idleTimeClips,
@@ -75479,6 +75992,7 @@ var WorkspaceDetailView = ({
75479
75992
  hourlyTargetOutput: workspace.hourly_target_output,
75480
75993
  shiftStart: workspace.shift_start || "06:00",
75481
75994
  shiftEnd: workspace.shift_end,
75995
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75482
75996
  showIdleTime: showChartIdleTime,
75483
75997
  idleTimeHourly: workspace.idle_time_hourly,
75484
75998
  idleTimeClips,
@@ -77318,11 +77832,14 @@ var TicketService = class {
77318
77832
  }
77319
77833
  return response;
77320
77834
  } catch (error) {
77321
- captureHandledFrontendException(error, {
77835
+ addSentryBreadcrumb("Ticket service request failed", {
77322
77836
  surface: "ticket_service",
77323
- url,
77324
- method: options.method || "GET",
77325
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
77837
+ route: url,
77838
+ severity: "warning",
77839
+ extras: {
77840
+ method: options.method || "GET",
77841
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
77842
+ }
77326
77843
  });
77327
77844
  throw error;
77328
77845
  }
@@ -83102,10 +83619,33 @@ var useOperationsOverviewRefresh = ({
83102
83619
  if (controller.signal.aborted || requestIdsRef.current[section] !== requestId || isAbortError2(error)) {
83103
83620
  return;
83104
83621
  }
83622
+ const sentryContext = {
83623
+ surface: "operations_overview_refresh",
83624
+ route: `/api/dashboard/operations-overview/${section}`,
83625
+ extras: {
83626
+ section,
83627
+ reason,
83628
+ company_id: companyId,
83629
+ line_ids: lineIds,
83630
+ start_date: startKey,
83631
+ end_date: endKey,
83632
+ trend_mode: trendMode,
83633
+ comparison_strategy: comparisonStrategy || null
83634
+ }
83635
+ };
83636
+ if (section === "improvements") {
83637
+ addSentryBreadcrumb("Operations overview optional improvements refresh failed", {
83638
+ ...sentryContext,
83639
+ severity: "warning",
83640
+ category: "operations_overview"
83641
+ });
83642
+ } else {
83643
+ captureHandledFrontendException(error, sentryContext);
83644
+ }
83105
83645
  onError(error instanceof Error ? error.message : `Failed to refresh ${section}`);
83106
83646
  }
83107
83647
  },
83108
- [companyId, enabled, lineIds.length, supabase]
83648
+ [companyId, comparisonStrategy, enabled, endKey, lineIds, startKey, supabase, trendMode]
83109
83649
  );
83110
83650
  const refreshSnapshot = React144__default.useCallback(
83111
83651
  async (reason) => {
@@ -84548,4 +85088,4 @@ var streamProxyConfig = {
84548
85088
  }
84549
85089
  };
84550
85090
 
84551
- export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, aggregateKPIsFromLineMetricsRows, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildLineSkuBreakdown, buildShiftGroupsKey, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
85091
+ export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SENTRY_HANDLED_EVENT_SESSION_LIMIT, SENTRY_HANDLED_EVENT_WINDOW_MS, SENTRY_QUOTA_STORAGE_KEY, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, addSentryBreadcrumb, aggregateKPIsFromLineMetricsRows, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildLineSkuBreakdown, buildShiftGroupsKey, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSentryQuotaForTests, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };