@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/README.md +5 -1
- package/dist/index.d.mts +41 -4
- package/dist/index.d.ts +41 -4
- package/dist/index.js +887 -342
- package/dist/index.mjs +883 -343
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2628,19 +2628,206 @@ function getSentry() {
|
|
|
2628
2628
|
return null;
|
|
2629
2629
|
}
|
|
2630
2630
|
}
|
|
2631
|
+
var SENTRY_HANDLED_EVENT_WINDOW_MS = 10 * 60 * 1e3;
|
|
2632
|
+
var SENTRY_HANDLED_EVENT_SESSION_LIMIT = 20;
|
|
2633
|
+
var SENTRY_QUOTA_STORAGE_KEY = "optifye:sentry-handled-quota:v1";
|
|
2634
|
+
var sentryFingerprintSentAt = /* @__PURE__ */ new Map();
|
|
2635
|
+
var handledSentryEventCount = 0;
|
|
2636
|
+
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;
|
|
2637
|
+
var LONG_HEX_PATTERN = /\b[0-9a-f]{24,}\b/gi;
|
|
2638
|
+
var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
2639
|
+
var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
2640
|
+
var URI_PATTERN = /\b[a-z][a-z0-9+.-]{1,31}:\/\/[^\s)]+/gi;
|
|
2641
|
+
var MEDIA_PATH_PATTERN = /\b[^\s)]+\.m3u8(?:\?[^\s)]*)?/gi;
|
|
2642
|
+
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;
|
|
2643
|
+
var SENSITIVE_KEY_PATTERN = /(authorization|cookie|password|secret|token|session|email|api[_-]?key|url|stream|playlist|media)/i;
|
|
2644
|
+
function resetSentryQuotaForTests() {
|
|
2645
|
+
sentryFingerprintSentAt.clear();
|
|
2646
|
+
handledSentryEventCount = 0;
|
|
2647
|
+
const storage = getBrowserSessionStorage();
|
|
2648
|
+
storage?.removeItem(SENTRY_QUOTA_STORAGE_KEY);
|
|
2649
|
+
}
|
|
2650
|
+
var sanitizeString = (value) => {
|
|
2651
|
+
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();
|
|
2652
|
+
};
|
|
2653
|
+
var sanitizeRoute = (value) => {
|
|
2654
|
+
if (typeof value !== "string" || value.trim().length === 0) return void 0;
|
|
2655
|
+
try {
|
|
2656
|
+
const parsed = new URL(value, "https://optifye.local");
|
|
2657
|
+
return sanitizeString(parsed.pathname);
|
|
2658
|
+
} catch {
|
|
2659
|
+
return sanitizeString(value.split("?")[0] || value);
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
var sanitizeValue = (value, depth = 0) => {
|
|
2663
|
+
if (depth > 3) return "[truncated]";
|
|
2664
|
+
if (typeof value === "string") return sanitizeString(value);
|
|
2665
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) return value;
|
|
2666
|
+
if (Array.isArray(value)) {
|
|
2667
|
+
return value.slice(0, 20).map((item) => sanitizeValue(item, depth + 1));
|
|
2668
|
+
}
|
|
2669
|
+
if (typeof value === "object") {
|
|
2670
|
+
const sanitized = {};
|
|
2671
|
+
Object.entries(value).forEach(([key, item]) => {
|
|
2672
|
+
sanitized[key] = SENSITIVE_KEY_PATTERN.test(key) ? "[redacted]" : sanitizeValue(item, depth + 1);
|
|
2673
|
+
});
|
|
2674
|
+
return sanitized;
|
|
2675
|
+
}
|
|
2676
|
+
return String(value);
|
|
2677
|
+
};
|
|
2678
|
+
var sanitizeStack = (stack) => {
|
|
2679
|
+
return stack.split("\n").map((line) => sanitizeString(line)).join("\n");
|
|
2680
|
+
};
|
|
2681
|
+
var sanitizeExtras = (extras) => {
|
|
2682
|
+
if (!extras) return void 0;
|
|
2683
|
+
return sanitizeValue(extras);
|
|
2684
|
+
};
|
|
2685
|
+
var getBrowserSessionStorage = () => {
|
|
2686
|
+
if (typeof window === "undefined") return null;
|
|
2687
|
+
try {
|
|
2688
|
+
return window.sessionStorage;
|
|
2689
|
+
} catch {
|
|
2690
|
+
return null;
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
var loadSentryQuotaFromStorage = (now4) => {
|
|
2694
|
+
const storage = getBrowserSessionStorage();
|
|
2695
|
+
if (!storage) return;
|
|
2696
|
+
const rawState = storage.getItem(SENTRY_QUOTA_STORAGE_KEY);
|
|
2697
|
+
if (!rawState) return;
|
|
2698
|
+
try {
|
|
2699
|
+
const parsed = JSON.parse(rawState);
|
|
2700
|
+
const persistedCount = Number(parsed.handledEventCount);
|
|
2701
|
+
handledSentryEventCount = Number.isFinite(persistedCount) && persistedCount > 0 ? Math.floor(persistedCount) : 0;
|
|
2702
|
+
sentryFingerprintSentAt.clear();
|
|
2703
|
+
if (parsed.fingerprintSentAt && typeof parsed.fingerprintSentAt === "object") {
|
|
2704
|
+
Object.entries(parsed.fingerprintSentAt).forEach(([fingerprint, sentAt]) => {
|
|
2705
|
+
const sentAtMs = Number(sentAt);
|
|
2706
|
+
if (Number.isFinite(sentAtMs) && now4 - sentAtMs < SENTRY_HANDLED_EVENT_WINDOW_MS) {
|
|
2707
|
+
sentryFingerprintSentAt.set(fingerprint, sentAtMs);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
} catch {
|
|
2712
|
+
storage.removeItem(SENTRY_QUOTA_STORAGE_KEY);
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
var persistSentryQuotaToStorage = () => {
|
|
2716
|
+
const storage = getBrowserSessionStorage();
|
|
2717
|
+
if (!storage) return;
|
|
2718
|
+
try {
|
|
2719
|
+
storage.setItem(
|
|
2720
|
+
SENTRY_QUOTA_STORAGE_KEY,
|
|
2721
|
+
JSON.stringify({
|
|
2722
|
+
handledEventCount: handledSentryEventCount,
|
|
2723
|
+
fingerprintSentAt: Object.fromEntries(sentryFingerprintSentAt)
|
|
2724
|
+
})
|
|
2725
|
+
);
|
|
2726
|
+
} catch {
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
var normalizeMessage = (error) => {
|
|
2730
|
+
const message = error instanceof Error ? error.message : String(error ?? "unknown");
|
|
2731
|
+
return sanitizeString(message.toLowerCase()).slice(0, 180) || "unknown";
|
|
2732
|
+
};
|
|
2733
|
+
var getErrorName = (error) => {
|
|
2734
|
+
if (error && typeof error === "object" && "name" in error) {
|
|
2735
|
+
const name = error.name;
|
|
2736
|
+
if (typeof name === "string" && name.trim()) return sanitizeString(name);
|
|
2737
|
+
}
|
|
2738
|
+
return error instanceof Error ? error.constructor.name : typeof error;
|
|
2739
|
+
};
|
|
2740
|
+
var getStatusFromError = (error) => {
|
|
2741
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2742
|
+
const statusMatch = message.match(/\((\d{3})\)/) || message.match(/http\s+(\d{3})/i) || message.match(/status:\s*(\d{3})/i);
|
|
2743
|
+
if (!statusMatch) return null;
|
|
2744
|
+
const status = Number.parseInt(statusMatch[1], 10);
|
|
2745
|
+
return Number.isFinite(status) ? status : null;
|
|
2746
|
+
};
|
|
2747
|
+
var hasCaptureOptionShape = (value) => {
|
|
2748
|
+
return "surface" in value || "route" in value || "status" in value || "severity" in value || "quotaKey" in value || "extras" in value;
|
|
2749
|
+
};
|
|
2750
|
+
var normalizeCaptureOptions = (options) => {
|
|
2751
|
+
if (!options) return {};
|
|
2752
|
+
const optionRecord = options;
|
|
2753
|
+
if (!hasCaptureOptionShape(optionRecord)) {
|
|
2754
|
+
return { extras: optionRecord };
|
|
2755
|
+
}
|
|
2756
|
+
const {
|
|
2757
|
+
surface,
|
|
2758
|
+
route,
|
|
2759
|
+
status,
|
|
2760
|
+
severity,
|
|
2761
|
+
quotaKey,
|
|
2762
|
+
extras,
|
|
2763
|
+
...rest
|
|
2764
|
+
} = options;
|
|
2765
|
+
return {
|
|
2766
|
+
surface: typeof surface === "string" ? surface : void 0,
|
|
2767
|
+
route: typeof route === "string" ? route : void 0,
|
|
2768
|
+
status: typeof status === "number" ? status : null,
|
|
2769
|
+
severity: severity === "info" || severity === "warning" || severity === "error" ? severity : void 0,
|
|
2770
|
+
quotaKey: typeof quotaKey === "string" ? quotaKey : void 0,
|
|
2771
|
+
extras: {
|
|
2772
|
+
...rest,
|
|
2773
|
+
...extras && typeof extras === "object" && !Array.isArray(extras) ? extras : {}
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
};
|
|
2777
|
+
var buildSentryFingerprint = (error, options) => {
|
|
2778
|
+
if (options.quotaKey) return sanitizeString(options.quotaKey);
|
|
2779
|
+
const surface = sanitizeString(options.surface || "frontend");
|
|
2780
|
+
const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url) || "unknown-route";
|
|
2781
|
+
const status = options.status ?? getStatusFromError(error) ?? "unknown-status";
|
|
2782
|
+
return [
|
|
2783
|
+
surface,
|
|
2784
|
+
route,
|
|
2785
|
+
String(status),
|
|
2786
|
+
getErrorName(error),
|
|
2787
|
+
normalizeMessage(error)
|
|
2788
|
+
].join("|");
|
|
2789
|
+
};
|
|
2790
|
+
var shouldSendHandledSentryEvent = (fingerprint) => {
|
|
2791
|
+
const now4 = Date.now();
|
|
2792
|
+
loadSentryQuotaFromStorage(now4);
|
|
2793
|
+
const lastSentAt = sentryFingerprintSentAt.get(fingerprint);
|
|
2794
|
+
if (lastSentAt !== void 0 && now4 - lastSentAt < SENTRY_HANDLED_EVENT_WINDOW_MS) {
|
|
2795
|
+
return false;
|
|
2796
|
+
}
|
|
2797
|
+
if (handledSentryEventCount >= SENTRY_HANDLED_EVENT_SESSION_LIMIT) {
|
|
2798
|
+
return false;
|
|
2799
|
+
}
|
|
2800
|
+
sentryFingerprintSentAt.set(fingerprint, now4);
|
|
2801
|
+
handledSentryEventCount += 1;
|
|
2802
|
+
persistSentryQuotaToStorage();
|
|
2803
|
+
return true;
|
|
2804
|
+
};
|
|
2805
|
+
var createSanitizedSentryException = (error) => {
|
|
2806
|
+
if (error instanceof Error) {
|
|
2807
|
+
const sanitizedError = new Error(sanitizeString(error.message) || getErrorName(error) || "Error");
|
|
2808
|
+
sanitizedError.name = getErrorName(error) || "Error";
|
|
2809
|
+
if (typeof error.stack === "string") {
|
|
2810
|
+
sanitizedError.stack = sanitizeStack(error.stack);
|
|
2811
|
+
}
|
|
2812
|
+
return sanitizedError;
|
|
2813
|
+
}
|
|
2814
|
+
if (typeof error === "string") {
|
|
2815
|
+
return sanitizeString(error) || "unknown";
|
|
2816
|
+
}
|
|
2817
|
+
return sanitizeValue(error);
|
|
2818
|
+
};
|
|
2631
2819
|
function isIgnorableFrontendError(error) {
|
|
2632
2820
|
const name = error && typeof error === "object" ? error.name || "" : "";
|
|
2633
2821
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2634
2822
|
const lowerMessage = message.toLowerCase();
|
|
2635
|
-
return name === "AbortError" || lowerMessage.includes("the operation was aborted") || lowerMessage.includes("signal is aborted") || lowerMessage.includes("resizeobserver loop");
|
|
2823
|
+
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");
|
|
2636
2824
|
}
|
|
2637
2825
|
function setSentryUserContext(user) {
|
|
2638
2826
|
const sentry = getSentry();
|
|
2639
2827
|
if (!sentry) return;
|
|
2640
2828
|
if (user) {
|
|
2641
2829
|
sentry.setUser({
|
|
2642
|
-
id: user.id
|
|
2643
|
-
email: user.email
|
|
2830
|
+
id: user.id
|
|
2644
2831
|
});
|
|
2645
2832
|
sentry.setTags({
|
|
2646
2833
|
company_id: user.company_id || "unknown",
|
|
@@ -2691,28 +2878,60 @@ function applyScopeExtras(scope, extras) {
|
|
|
2691
2878
|
function captureSentryMessage(message, level = "warning", extras) {
|
|
2692
2879
|
const sentry = getSentry();
|
|
2693
2880
|
if (!sentry || !sentry.captureMessage) return;
|
|
2881
|
+
const options = normalizeCaptureOptions({ ...normalizeCaptureOptions(extras), severity: level });
|
|
2882
|
+
const fingerprint = buildSentryFingerprint(new Error(message), options);
|
|
2883
|
+
if (!shouldSendHandledSentryEvent(fingerprint)) return;
|
|
2884
|
+
const sanitizedExtras = sanitizeExtras({
|
|
2885
|
+
...options.extras,
|
|
2886
|
+
sentry_quota_key: fingerprint,
|
|
2887
|
+
sentry_capture_policy: "quota_controlled"
|
|
2888
|
+
});
|
|
2889
|
+
const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url);
|
|
2890
|
+
const sanitizedMessage = sanitizeString(message) || "Sentry message";
|
|
2694
2891
|
if (sentry.withScope) {
|
|
2695
2892
|
sentry.withScope((scope) => {
|
|
2696
2893
|
scope.setLevel?.(level);
|
|
2697
|
-
|
|
2698
|
-
|
|
2894
|
+
scope.setFingerprint?.([fingerprint]);
|
|
2895
|
+
if (options.surface) scope.setTag?.("surface", sanitizeString(options.surface));
|
|
2896
|
+
if (route) scope.setTag?.("route", route);
|
|
2897
|
+
if (options.status !== void 0 && options.status !== null) scope.setTag?.("status", options.status);
|
|
2898
|
+
applyScopeExtras(scope, sanitizedExtras);
|
|
2899
|
+
sentry.captureMessage?.(sanitizedMessage);
|
|
2699
2900
|
});
|
|
2700
2901
|
return;
|
|
2701
2902
|
}
|
|
2702
|
-
sentry.captureMessage(
|
|
2903
|
+
sentry.captureMessage(sanitizedMessage, level);
|
|
2703
2904
|
}
|
|
2704
2905
|
function captureSentryException(error, extras) {
|
|
2906
|
+
if (isIgnorableFrontendError(error)) {
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2705
2909
|
const sentry = getSentry();
|
|
2706
2910
|
if (!sentry || !sentry.captureException) return;
|
|
2911
|
+
const options = normalizeCaptureOptions(extras);
|
|
2912
|
+
const fingerprint = buildSentryFingerprint(error, options);
|
|
2913
|
+
if (!shouldSendHandledSentryEvent(fingerprint)) return;
|
|
2914
|
+
const sanitizedExtras = sanitizeExtras({
|
|
2915
|
+
...options.extras,
|
|
2916
|
+
sentry_quota_key: fingerprint,
|
|
2917
|
+
sentry_capture_policy: "quota_controlled"
|
|
2918
|
+
});
|
|
2919
|
+
const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url);
|
|
2920
|
+
const status = options.status ?? getStatusFromError(error);
|
|
2921
|
+
const sanitizedError = createSanitizedSentryException(error);
|
|
2707
2922
|
if (sentry.withScope) {
|
|
2708
2923
|
sentry.withScope((scope) => {
|
|
2709
|
-
scope.setLevel?.("error");
|
|
2710
|
-
|
|
2711
|
-
|
|
2924
|
+
scope.setLevel?.(options.severity || "error");
|
|
2925
|
+
scope.setFingerprint?.([fingerprint]);
|
|
2926
|
+
if (options.surface) scope.setTag?.("surface", sanitizeString(options.surface));
|
|
2927
|
+
if (route) scope.setTag?.("route", route);
|
|
2928
|
+
if (status !== null && status !== void 0) scope.setTag?.("status", status);
|
|
2929
|
+
applyScopeExtras(scope, sanitizedExtras);
|
|
2930
|
+
sentry.captureException?.(sanitizedError);
|
|
2712
2931
|
});
|
|
2713
2932
|
return;
|
|
2714
2933
|
}
|
|
2715
|
-
sentry.captureException(
|
|
2934
|
+
sentry.captureException(sanitizedError);
|
|
2716
2935
|
}
|
|
2717
2936
|
function captureHandledFrontendException(error, extras) {
|
|
2718
2937
|
if (isIgnorableFrontendError(error)) {
|
|
@@ -2720,6 +2939,23 @@ function captureHandledFrontendException(error, extras) {
|
|
|
2720
2939
|
}
|
|
2721
2940
|
captureSentryException(error, extras);
|
|
2722
2941
|
}
|
|
2942
|
+
function addSentryBreadcrumb(message, options = {}) {
|
|
2943
|
+
const sentry = getSentry();
|
|
2944
|
+
if (!sentry?.addBreadcrumb) return;
|
|
2945
|
+
const route = sanitizeRoute(options.route || options.extras?.route || options.extras?.endpoint || options.extras?.url);
|
|
2946
|
+
const data = sanitizeExtras({
|
|
2947
|
+
surface: options.surface,
|
|
2948
|
+
route,
|
|
2949
|
+
status: options.status,
|
|
2950
|
+
...options.extras
|
|
2951
|
+
});
|
|
2952
|
+
sentry.addBreadcrumb({
|
|
2953
|
+
category: options.category || options.surface || "frontend",
|
|
2954
|
+
message: sanitizeString(message),
|
|
2955
|
+
level: options.severity || "info",
|
|
2956
|
+
data
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2723
2959
|
|
|
2724
2960
|
// src/lib/services/backendClient.ts
|
|
2725
2961
|
var ACCESS_TOKEN_REFRESH_BUFFER_MS = 6e4;
|
|
@@ -2787,7 +3023,7 @@ var createAbortError = () => {
|
|
|
2787
3023
|
return error;
|
|
2788
3024
|
}
|
|
2789
3025
|
};
|
|
2790
|
-
var
|
|
3026
|
+
var getStatusFromError2 = (error) => {
|
|
2791
3027
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2792
3028
|
const statusMatch = message.match(/\((\d{3})\)/) || message.match(/http\s+(\d{3})/i);
|
|
2793
3029
|
if (!statusMatch) {
|
|
@@ -2803,7 +3039,7 @@ var isRetryableError = (error) => {
|
|
|
2803
3039
|
if (isAbortError(error)) {
|
|
2804
3040
|
return false;
|
|
2805
3041
|
}
|
|
2806
|
-
const status =
|
|
3042
|
+
const status = getStatusFromError2(error);
|
|
2807
3043
|
if (status !== null) {
|
|
2808
3044
|
return status >= 500 || status === 408 || status === 425 || status === 429;
|
|
2809
3045
|
}
|
|
@@ -2827,6 +3063,7 @@ var fetchBackendJson = async (supabase, endpoint, options = {}) => {
|
|
|
2827
3063
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
2828
3064
|
retries = 0,
|
|
2829
3065
|
retryDelayMs = DEFAULT_RETRY_DELAY_MS,
|
|
3066
|
+
sentry,
|
|
2830
3067
|
signal: externalSignal,
|
|
2831
3068
|
...fetchOptions
|
|
2832
3069
|
} = options;
|
|
@@ -2864,14 +3101,22 @@ var fetchBackendJson = async (supabase, endpoint, options = {}) => {
|
|
|
2864
3101
|
} catch (error) {
|
|
2865
3102
|
const wrappedError = externalSignal?.aborted || controller.signal.aborted && !isAbortError(error) ? createAbortError() : error;
|
|
2866
3103
|
if (attempt >= retries || !isRetryableError(wrappedError)) {
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
3104
|
+
if (sentry && sentry.capture !== false) {
|
|
3105
|
+
captureHandledFrontendException(wrappedError, {
|
|
3106
|
+
surface: sentry.surface || "backend_client",
|
|
3107
|
+
route: sentry.route || endpoint,
|
|
3108
|
+
status: sentry.status ?? getStatusFromError2(wrappedError),
|
|
3109
|
+
severity: sentry.severity || "error",
|
|
3110
|
+
quotaKey: sentry.quotaKey,
|
|
3111
|
+
extras: {
|
|
3112
|
+
method,
|
|
3113
|
+
retry_attempt: attempt,
|
|
3114
|
+
retries,
|
|
3115
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
3116
|
+
...sentry.extras
|
|
3117
|
+
}
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
2875
3120
|
throw wrappedError;
|
|
2876
3121
|
}
|
|
2877
3122
|
await new Promise((resolve) => globalThis.setTimeout(resolve, retryDelayMs));
|
|
@@ -2957,6 +3202,7 @@ var ApiClient = class {
|
|
|
2957
3202
|
retryDelay = 1e3,
|
|
2958
3203
|
silentErrors = true,
|
|
2959
3204
|
fallbackData = null,
|
|
3205
|
+
sentry,
|
|
2960
3206
|
...fetchOptions
|
|
2961
3207
|
} = options;
|
|
2962
3208
|
let lastError = null;
|
|
@@ -2990,24 +3236,36 @@ var ApiClient = class {
|
|
|
2990
3236
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
2991
3237
|
continue;
|
|
2992
3238
|
}
|
|
2993
|
-
if (
|
|
3239
|
+
if (sentry && sentry.capture !== false) {
|
|
2994
3240
|
captureHandledFrontendException(lastError, {
|
|
3241
|
+
surface: sentry.surface || "api_client",
|
|
3242
|
+
route: sentry.route || url,
|
|
3243
|
+
status: sentry.status,
|
|
3244
|
+
severity: sentry.severity || "error",
|
|
3245
|
+
quotaKey: sentry.quotaKey,
|
|
3246
|
+
extras: {
|
|
3247
|
+
retries,
|
|
3248
|
+
silent_errors: silentErrors,
|
|
3249
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
3250
|
+
...sentry.extras
|
|
3251
|
+
}
|
|
3252
|
+
});
|
|
3253
|
+
} else {
|
|
3254
|
+
addSentryBreadcrumb("API client request failed", {
|
|
2995
3255
|
surface: "api_client",
|
|
2996
|
-
url,
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3256
|
+
route: url,
|
|
3257
|
+
severity: silentErrors ? "info" : "warning",
|
|
3258
|
+
extras: {
|
|
3259
|
+
retries,
|
|
3260
|
+
silent_errors: silentErrors,
|
|
3261
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
3262
|
+
}
|
|
3000
3263
|
});
|
|
3264
|
+
}
|
|
3265
|
+
if (silentErrors) {
|
|
3001
3266
|
console.warn("[ApiClient] All retries exhausted, returning fallback data");
|
|
3002
3267
|
return fallbackData;
|
|
3003
3268
|
} else {
|
|
3004
|
-
captureHandledFrontendException(lastError, {
|
|
3005
|
-
surface: "api_client",
|
|
3006
|
-
url,
|
|
3007
|
-
retries,
|
|
3008
|
-
silent_errors: false,
|
|
3009
|
-
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
3010
|
-
});
|
|
3011
3269
|
throw lastError;
|
|
3012
3270
|
}
|
|
3013
3271
|
}
|
|
@@ -3259,8 +3517,12 @@ var AuthService = class {
|
|
|
3259
3517
|
timeout: 1e4,
|
|
3260
3518
|
// 10 seconds
|
|
3261
3519
|
retries: 1,
|
|
3262
|
-
silentErrors: false
|
|
3520
|
+
silentErrors: false,
|
|
3263
3521
|
// We want to know about auth errors
|
|
3522
|
+
sentry: {
|
|
3523
|
+
surface: "auth_session_request",
|
|
3524
|
+
route: "/api/auth/session"
|
|
3525
|
+
}
|
|
3264
3526
|
}
|
|
3265
3527
|
);
|
|
3266
3528
|
console.log("[AuthService] Session loaded:", {
|
|
@@ -3330,7 +3592,11 @@ var AuthService = class {
|
|
|
3330
3592
|
timeout: 1e4,
|
|
3331
3593
|
// 10 seconds
|
|
3332
3594
|
retries: 1,
|
|
3333
|
-
silentErrors: false
|
|
3595
|
+
silentErrors: false,
|
|
3596
|
+
sentry: {
|
|
3597
|
+
surface: "auth_permissions_request",
|
|
3598
|
+
route: "/api/auth/permissions"
|
|
3599
|
+
}
|
|
3334
3600
|
}
|
|
3335
3601
|
);
|
|
3336
3602
|
console.log("[AuthService] Permissions loaded for role:", data.role);
|
|
@@ -5972,12 +6238,16 @@ var workspaceService = {
|
|
|
5972
6238
|
return displayNamesMap;
|
|
5973
6239
|
} catch (error) {
|
|
5974
6240
|
console.error("Error fetching workspace display names:", error);
|
|
5975
|
-
|
|
6241
|
+
addSentryBreadcrumb("Workspace display-name fallback failed", {
|
|
5976
6242
|
surface: "workspace_display_names",
|
|
5977
6243
|
route: "/api/workspaces/display-names",
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
6244
|
+
severity: "warning",
|
|
6245
|
+
extras: {
|
|
6246
|
+
company_id: companyId || null,
|
|
6247
|
+
line_id: lineId || null,
|
|
6248
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
6249
|
+
error_message: error instanceof Error ? error.message : String(error)
|
|
6250
|
+
}
|
|
5981
6251
|
});
|
|
5982
6252
|
throw error;
|
|
5983
6253
|
}
|
|
@@ -7581,11 +7851,28 @@ var shouldReportMixpanel = (key, isError) => {
|
|
|
7581
7851
|
};
|
|
7582
7852
|
var reportMixpanelWarning = (key, message, extras) => {
|
|
7583
7853
|
if (!shouldReportMixpanel(key, false)) return;
|
|
7584
|
-
|
|
7854
|
+
addSentryBreadcrumb(message, {
|
|
7855
|
+
surface: "mixpanel",
|
|
7856
|
+
severity: "warning",
|
|
7857
|
+
extras: {
|
|
7858
|
+
key,
|
|
7859
|
+
...baseMixpanelExtras(),
|
|
7860
|
+
...extras
|
|
7861
|
+
}
|
|
7862
|
+
});
|
|
7585
7863
|
};
|
|
7586
7864
|
var reportMixpanelError = (key, error, extras) => {
|
|
7587
7865
|
if (!shouldReportMixpanel(key, true)) return;
|
|
7588
|
-
|
|
7866
|
+
addSentryBreadcrumb("Mixpanel operation failed", {
|
|
7867
|
+
surface: "mixpanel",
|
|
7868
|
+
severity: "warning",
|
|
7869
|
+
extras: {
|
|
7870
|
+
key,
|
|
7871
|
+
error_message: error instanceof Error ? error.message : String(error ?? "unknown"),
|
|
7872
|
+
...baseMixpanelExtras(),
|
|
7873
|
+
...extras
|
|
7874
|
+
}
|
|
7875
|
+
});
|
|
7589
7876
|
};
|
|
7590
7877
|
var normalizeCoreEventName = (eventName) => {
|
|
7591
7878
|
const canonicalName = MIXPANEL_EVENT_NAME_ALIASES[eventName] || eventName;
|
|
@@ -9602,11 +9889,15 @@ var LinesService = class {
|
|
|
9602
9889
|
}));
|
|
9603
9890
|
} catch (error) {
|
|
9604
9891
|
console.error("Error fetching lines:", error);
|
|
9605
|
-
|
|
9892
|
+
addSentryBreadcrumb("Lines service request failed", {
|
|
9606
9893
|
surface: "lines_service",
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9894
|
+
route: "/api/lines",
|
|
9895
|
+
severity: "warning",
|
|
9896
|
+
extras: {
|
|
9897
|
+
operation: "getLinesByCompanyId",
|
|
9898
|
+
company_id: companyId,
|
|
9899
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
9900
|
+
}
|
|
9610
9901
|
});
|
|
9611
9902
|
throw new Error(`Failed to fetch lines: ${error.message}`);
|
|
9612
9903
|
}
|
|
@@ -9657,10 +9948,14 @@ var LinesService = class {
|
|
|
9657
9948
|
}));
|
|
9658
9949
|
} catch (error) {
|
|
9659
9950
|
console.error("Error fetching all lines:", error);
|
|
9660
|
-
|
|
9951
|
+
addSentryBreadcrumb("Lines service request failed", {
|
|
9661
9952
|
surface: "lines_service",
|
|
9662
|
-
|
|
9663
|
-
|
|
9953
|
+
route: "/api/lines",
|
|
9954
|
+
severity: "warning",
|
|
9955
|
+
extras: {
|
|
9956
|
+
operation: "getAllLines",
|
|
9957
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
9958
|
+
}
|
|
9664
9959
|
});
|
|
9665
9960
|
throw new Error(`Failed to fetch lines: ${error.message}`);
|
|
9666
9961
|
}
|
|
@@ -9720,11 +10015,15 @@ var LinesService = class {
|
|
|
9720
10015
|
};
|
|
9721
10016
|
} catch (error) {
|
|
9722
10017
|
console.error("Error fetching line:", error);
|
|
9723
|
-
|
|
10018
|
+
addSentryBreadcrumb("Lines service request failed", {
|
|
9724
10019
|
surface: "lines_service",
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
10020
|
+
route: "/api/lines/:id",
|
|
10021
|
+
severity: "warning",
|
|
10022
|
+
extras: {
|
|
10023
|
+
operation: "getLineById",
|
|
10024
|
+
line_id: lineId,
|
|
10025
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
10026
|
+
}
|
|
9728
10027
|
});
|
|
9729
10028
|
throw new Error(`Failed to fetch line: ${error.message}`);
|
|
9730
10029
|
}
|
|
@@ -13033,7 +13332,8 @@ var toWorkspaceDetailedMetrics = ({
|
|
|
13033
13332
|
const targetOutput = coerceNumber(data.target_output ?? data.total_day_output, 0);
|
|
13034
13333
|
const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
|
|
13035
13334
|
const outputDifference = totalActions - idealOutput;
|
|
13036
|
-
const
|
|
13335
|
+
const hasHourlyTargetOutputField = Object.prototype.hasOwnProperty.call(data, "hourly_target_output");
|
|
13336
|
+
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;
|
|
13037
13337
|
const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
|
|
13038
13338
|
const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
|
|
13039
13339
|
const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
|
|
@@ -13198,6 +13498,17 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
13198
13498
|
workspaceMetricsStore.setDetailed(transformedData);
|
|
13199
13499
|
} catch (err) {
|
|
13200
13500
|
console.error("Error fetching workspace metrics:", err);
|
|
13501
|
+
captureHandledFrontendException(err, {
|
|
13502
|
+
surface: "workspace_detail_metrics",
|
|
13503
|
+
route: "/api/dashboard/workspace/:workspaceId/metrics",
|
|
13504
|
+
extras: {
|
|
13505
|
+
workspace_id: workspaceId,
|
|
13506
|
+
company_id: companyId,
|
|
13507
|
+
date,
|
|
13508
|
+
shift_id: shiftId,
|
|
13509
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
13510
|
+
}
|
|
13511
|
+
});
|
|
13201
13512
|
setError({ message: err.message, code: err.code });
|
|
13202
13513
|
} finally {
|
|
13203
13514
|
isFetchingRef.current = false;
|
|
@@ -14338,6 +14649,7 @@ var transformMonitorWorkspaceMetrics = ({
|
|
|
14338
14649
|
recent_flow_window_minutes: item.recent_flow_window_minutes ?? null,
|
|
14339
14650
|
recent_flow_effective_end_at: item.recent_flow_effective_end_at ?? null,
|
|
14340
14651
|
recent_flow_computed_at: item.recent_flow_computed_at ?? null,
|
|
14652
|
+
recent_flow_forced_zero_after_shift: item.recent_flow_forced_zero_after_shift ?? null,
|
|
14341
14653
|
scheduled_break_active: item.scheduled_break_active ?? false,
|
|
14342
14654
|
incoming_wip_current: item.incoming_wip_current ?? null,
|
|
14343
14655
|
incoming_wip_effective_at: item.incoming_wip_effective_at ?? null,
|
|
@@ -21464,20 +21776,26 @@ var apiUtils = {
|
|
|
21464
21776
|
const token = sessionResponse.data.session?.access_token;
|
|
21465
21777
|
if (!token) {
|
|
21466
21778
|
console.error("API Util: No authentication token available.");
|
|
21467
|
-
|
|
21779
|
+
addSentryBreadcrumb("API utility request skipped without auth token", {
|
|
21468
21780
|
surface: "api_utils",
|
|
21469
|
-
|
|
21470
|
-
|
|
21781
|
+
route: relativeEndpoint,
|
|
21782
|
+
severity: "warning",
|
|
21783
|
+
extras: {
|
|
21784
|
+
reason: "missing_auth_token"
|
|
21785
|
+
}
|
|
21471
21786
|
});
|
|
21472
21787
|
throw new Error("Authentication required.");
|
|
21473
21788
|
}
|
|
21474
21789
|
const baseUrl = config.apiBaseUrl;
|
|
21475
21790
|
if (!baseUrl) {
|
|
21476
21791
|
console.error("API Util: apiBaseUrl is not configured.");
|
|
21477
|
-
|
|
21792
|
+
addSentryBreadcrumb("API utility request skipped without base URL", {
|
|
21478
21793
|
surface: "api_utils",
|
|
21479
|
-
|
|
21480
|
-
|
|
21794
|
+
route: relativeEndpoint,
|
|
21795
|
+
severity: "warning",
|
|
21796
|
+
extras: {
|
|
21797
|
+
reason: "missing_api_base_url"
|
|
21798
|
+
}
|
|
21481
21799
|
});
|
|
21482
21800
|
throw new Error("API base URL is not configured.");
|
|
21483
21801
|
}
|
|
@@ -21507,11 +21825,14 @@ var apiUtils = {
|
|
|
21507
21825
|
return await response.json();
|
|
21508
21826
|
} catch (error) {
|
|
21509
21827
|
console.error(`Network or fetch error calling ${endpoint}:`, error);
|
|
21510
|
-
|
|
21828
|
+
addSentryBreadcrumb("API utility request failed", {
|
|
21511
21829
|
surface: "api_utils",
|
|
21512
|
-
endpoint,
|
|
21513
|
-
|
|
21514
|
-
|
|
21830
|
+
route: endpoint,
|
|
21831
|
+
severity: "warning",
|
|
21832
|
+
extras: {
|
|
21833
|
+
method: options.method || "GET",
|
|
21834
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
21835
|
+
}
|
|
21515
21836
|
});
|
|
21516
21837
|
if (error instanceof Error) {
|
|
21517
21838
|
throw error;
|
|
@@ -35214,6 +35535,261 @@ var Button = React144__namespace.forwardRef(
|
|
|
35214
35535
|
}
|
|
35215
35536
|
);
|
|
35216
35537
|
Button.displayName = "Button";
|
|
35538
|
+
|
|
35539
|
+
// src/lib/utils/hourlyTargets.ts
|
|
35540
|
+
var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
|
|
35541
|
+
var MINUTES_PER_DAY = 24 * 60;
|
|
35542
|
+
var parseTimeToMinutes2 = (timeString) => {
|
|
35543
|
+
const normalized = stripSeconds2(timeString || "");
|
|
35544
|
+
if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
|
|
35545
|
+
const [hours, minutes] = normalized.split(":").map(Number);
|
|
35546
|
+
return hours * 60 + minutes;
|
|
35547
|
+
};
|
|
35548
|
+
var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
|
|
35549
|
+
const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
|
|
35550
|
+
if (!Number.isFinite(shiftStartMinutes)) return [];
|
|
35551
|
+
const normalizedBreaks = [];
|
|
35552
|
+
for (const entry of breaks) {
|
|
35553
|
+
const startRaw = parseTimeToMinutes2(entry.startTime);
|
|
35554
|
+
const endRaw = parseTimeToMinutes2(entry.endTime);
|
|
35555
|
+
if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
|
|
35556
|
+
let start = startRaw;
|
|
35557
|
+
let end = endRaw;
|
|
35558
|
+
if (end <= start) {
|
|
35559
|
+
end += 24 * 60;
|
|
35560
|
+
}
|
|
35561
|
+
if (start < shiftStartMinutes) {
|
|
35562
|
+
start += 24 * 60;
|
|
35563
|
+
end += 24 * 60;
|
|
35564
|
+
}
|
|
35565
|
+
const label = entry.remarks?.trim() || "Break";
|
|
35566
|
+
normalizedBreaks.push({ start, end, label });
|
|
35567
|
+
}
|
|
35568
|
+
return normalizedBreaks;
|
|
35569
|
+
};
|
|
35570
|
+
var roundTarget = (value, mode) => {
|
|
35571
|
+
if (!Number.isFinite(value)) return 0;
|
|
35572
|
+
switch (mode) {
|
|
35573
|
+
case "floor":
|
|
35574
|
+
return Math.floor(value);
|
|
35575
|
+
case "ceil":
|
|
35576
|
+
return Math.ceil(value);
|
|
35577
|
+
case "round":
|
|
35578
|
+
default:
|
|
35579
|
+
return Math.round(value);
|
|
35580
|
+
}
|
|
35581
|
+
};
|
|
35582
|
+
var formatDateKey = (date) => {
|
|
35583
|
+
const year = date.getUTCFullYear();
|
|
35584
|
+
const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
|
|
35585
|
+
const day = `${date.getUTCDate()}`.padStart(2, "0");
|
|
35586
|
+
return `${year}-${month}-${day}`;
|
|
35587
|
+
};
|
|
35588
|
+
var shiftDateKey = (dateKey, deltaDays) => {
|
|
35589
|
+
const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
|
|
35590
|
+
const year = Number.isFinite(yearPart) ? yearPart : 1970;
|
|
35591
|
+
const month = Number.isFinite(monthPart) ? monthPart : 1;
|
|
35592
|
+
const day = Number.isFinite(dayPart) ? dayPart : 1;
|
|
35593
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
35594
|
+
date.setUTCDate(date.getUTCDate() + deltaDays);
|
|
35595
|
+
return formatDateKey(date);
|
|
35596
|
+
};
|
|
35597
|
+
var getZonedNowSnapshot = (timeZone, now4) => {
|
|
35598
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
35599
|
+
timeZone,
|
|
35600
|
+
year: "numeric",
|
|
35601
|
+
month: "2-digit",
|
|
35602
|
+
day: "2-digit",
|
|
35603
|
+
hour: "2-digit",
|
|
35604
|
+
minute: "2-digit",
|
|
35605
|
+
hourCycle: "h23"
|
|
35606
|
+
});
|
|
35607
|
+
const parts = formatter.formatToParts(now4).reduce((acc, part) => {
|
|
35608
|
+
if (part.type !== "literal") {
|
|
35609
|
+
acc[part.type] = part.value;
|
|
35610
|
+
}
|
|
35611
|
+
return acc;
|
|
35612
|
+
}, {});
|
|
35613
|
+
const year = Number(parts.year);
|
|
35614
|
+
const month = Number(parts.month);
|
|
35615
|
+
const day = Number(parts.day);
|
|
35616
|
+
const hour = Number(parts.hour);
|
|
35617
|
+
const minute = Number(parts.minute);
|
|
35618
|
+
return {
|
|
35619
|
+
dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
|
|
35620
|
+
minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
|
|
35621
|
+
};
|
|
35622
|
+
};
|
|
35623
|
+
var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
|
|
35624
|
+
var buildHourlyIntervals = ({
|
|
35625
|
+
shiftStart,
|
|
35626
|
+
shiftEnd,
|
|
35627
|
+
bucketMinutes = 60,
|
|
35628
|
+
fallbackHours = 11
|
|
35629
|
+
}) => {
|
|
35630
|
+
const startMinutes = parseTimeToMinutes2(shiftStart);
|
|
35631
|
+
if (!Number.isFinite(startMinutes)) return [];
|
|
35632
|
+
const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
|
|
35633
|
+
let totalMinutes;
|
|
35634
|
+
const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
|
|
35635
|
+
if (!Number.isFinite(endRaw)) {
|
|
35636
|
+
totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
|
|
35637
|
+
} else {
|
|
35638
|
+
let endMinutes = endRaw;
|
|
35639
|
+
if (endMinutes <= startMinutes) {
|
|
35640
|
+
endMinutes += 24 * 60;
|
|
35641
|
+
}
|
|
35642
|
+
totalMinutes = endMinutes - startMinutes;
|
|
35643
|
+
}
|
|
35644
|
+
if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
|
|
35645
|
+
const count = Math.ceil(totalMinutes / bucket);
|
|
35646
|
+
const shiftEndMinutes = startMinutes + totalMinutes;
|
|
35647
|
+
const intervals = [];
|
|
35648
|
+
for (let i = 0; i < count; i += 1) {
|
|
35649
|
+
const start = startMinutes + i * bucket;
|
|
35650
|
+
const end = Math.min(start + bucket, shiftEndMinutes);
|
|
35651
|
+
const minutes = Math.max(0, end - start);
|
|
35652
|
+
if (minutes <= 0) continue;
|
|
35653
|
+
intervals.push({ start, end, minutes });
|
|
35654
|
+
}
|
|
35655
|
+
return intervals;
|
|
35656
|
+
};
|
|
35657
|
+
var computeBreakMinutesByInterval = ({
|
|
35658
|
+
intervals,
|
|
35659
|
+
shiftStart,
|
|
35660
|
+
breaks
|
|
35661
|
+
}) => {
|
|
35662
|
+
if (!intervals.length || !breaks.length) return intervals.map(() => 0);
|
|
35663
|
+
const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
|
|
35664
|
+
return intervals.map((interval) => {
|
|
35665
|
+
if (!normalizedBreaks.length) return 0;
|
|
35666
|
+
let total = 0;
|
|
35667
|
+
for (const brk of normalizedBreaks) {
|
|
35668
|
+
const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
|
|
35669
|
+
total += overlap;
|
|
35670
|
+
if (total >= interval.minutes) return interval.minutes;
|
|
35671
|
+
}
|
|
35672
|
+
return Math.min(interval.minutes, total);
|
|
35673
|
+
});
|
|
35674
|
+
};
|
|
35675
|
+
var computeBreakRemarksByInterval = ({
|
|
35676
|
+
intervals,
|
|
35677
|
+
shiftStart,
|
|
35678
|
+
breaks
|
|
35679
|
+
}) => {
|
|
35680
|
+
if (!intervals.length || !breaks.length) return intervals.map(() => "");
|
|
35681
|
+
const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
|
|
35682
|
+
return intervals.map((interval) => {
|
|
35683
|
+
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);
|
|
35684
|
+
return labels.join(", ");
|
|
35685
|
+
});
|
|
35686
|
+
};
|
|
35687
|
+
var computeEffectiveTargets = ({
|
|
35688
|
+
intervals,
|
|
35689
|
+
breakMinutes,
|
|
35690
|
+
pphThreshold,
|
|
35691
|
+
rounding = "round"
|
|
35692
|
+
}) => {
|
|
35693
|
+
return intervals.map((interval, idx) => {
|
|
35694
|
+
const intervalMinutes = Number(interval?.minutes) || 0;
|
|
35695
|
+
const breakMins = Number(breakMinutes?.[idx]) || 0;
|
|
35696
|
+
const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
|
|
35697
|
+
if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
|
|
35698
|
+
if (plannedWorkMinutes <= 0) return 0;
|
|
35699
|
+
return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
|
|
35700
|
+
});
|
|
35701
|
+
};
|
|
35702
|
+
var buildHourlyTargetPlan = ({
|
|
35703
|
+
shiftStart,
|
|
35704
|
+
shiftEnd,
|
|
35705
|
+
breaks = [],
|
|
35706
|
+
pphThreshold,
|
|
35707
|
+
bucketMinutes = 60,
|
|
35708
|
+
fallbackHours = 11,
|
|
35709
|
+
rounding = "round"
|
|
35710
|
+
}) => {
|
|
35711
|
+
const intervals = buildHourlyIntervals({
|
|
35712
|
+
shiftStart,
|
|
35713
|
+
shiftEnd,
|
|
35714
|
+
bucketMinutes,
|
|
35715
|
+
fallbackHours
|
|
35716
|
+
});
|
|
35717
|
+
const breakMinutes = computeBreakMinutesByInterval({
|
|
35718
|
+
intervals,
|
|
35719
|
+
shiftStart,
|
|
35720
|
+
breaks
|
|
35721
|
+
});
|
|
35722
|
+
const breakRemarks = computeBreakRemarksByInterval({
|
|
35723
|
+
intervals,
|
|
35724
|
+
shiftStart,
|
|
35725
|
+
breaks
|
|
35726
|
+
});
|
|
35727
|
+
const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
|
|
35728
|
+
const targets = computeEffectiveTargets({
|
|
35729
|
+
intervals,
|
|
35730
|
+
breakMinutes,
|
|
35731
|
+
pphThreshold,
|
|
35732
|
+
rounding
|
|
35733
|
+
});
|
|
35734
|
+
return {
|
|
35735
|
+
intervals,
|
|
35736
|
+
breakMinutes,
|
|
35737
|
+
breakRemarks,
|
|
35738
|
+
productiveMinutes,
|
|
35739
|
+
targets
|
|
35740
|
+
};
|
|
35741
|
+
};
|
|
35742
|
+
var isHourlyIntervalComplete = ({
|
|
35743
|
+
reportDate,
|
|
35744
|
+
shiftStart,
|
|
35745
|
+
shiftEnd,
|
|
35746
|
+
interval,
|
|
35747
|
+
timeZone = "Asia/Kolkata",
|
|
35748
|
+
now: now4 = /* @__PURE__ */ new Date()
|
|
35749
|
+
}) => {
|
|
35750
|
+
if (!reportDate) return true;
|
|
35751
|
+
const snapshot = getZonedNowSnapshot(timeZone, now4);
|
|
35752
|
+
const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
|
|
35753
|
+
const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
|
|
35754
|
+
const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
|
|
35755
|
+
if (reportDate === snapshot.dateKey) {
|
|
35756
|
+
return interval.end <= snapshot.minutesOfDay;
|
|
35757
|
+
}
|
|
35758
|
+
if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
|
|
35759
|
+
return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
|
|
35760
|
+
}
|
|
35761
|
+
return reportDate < snapshot.dateKey;
|
|
35762
|
+
};
|
|
35763
|
+
var isShiftInProgressForReportDate = ({
|
|
35764
|
+
reportDate,
|
|
35765
|
+
shiftStart,
|
|
35766
|
+
shiftEnd,
|
|
35767
|
+
timeZone = "Asia/Kolkata",
|
|
35768
|
+
now: now4 = /* @__PURE__ */ new Date()
|
|
35769
|
+
}) => {
|
|
35770
|
+
if (!reportDate || !shiftStart || !shiftEnd) return false;
|
|
35771
|
+
const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
|
|
35772
|
+
const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
|
|
35773
|
+
if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
|
|
35774
|
+
return false;
|
|
35775
|
+
}
|
|
35776
|
+
let shiftEndMinutes = shiftEndMinutesRaw;
|
|
35777
|
+
const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
|
|
35778
|
+
if (wrapsMidnight) {
|
|
35779
|
+
shiftEndMinutes += MINUTES_PER_DAY;
|
|
35780
|
+
}
|
|
35781
|
+
const snapshot = getZonedNowSnapshot(timeZone, now4);
|
|
35782
|
+
let currentMinutes = null;
|
|
35783
|
+
if (reportDate === snapshot.dateKey) {
|
|
35784
|
+
currentMinutes = snapshot.minutesOfDay;
|
|
35785
|
+
} else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
|
|
35786
|
+
currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
|
|
35787
|
+
}
|
|
35788
|
+
if (currentMinutes === null) {
|
|
35789
|
+
return false;
|
|
35790
|
+
}
|
|
35791
|
+
return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
|
|
35792
|
+
};
|
|
35217
35793
|
var padTime = (value) => value.toString().padStart(2, "0");
|
|
35218
35794
|
var parseTime = (timeValue) => {
|
|
35219
35795
|
if (!timeValue) return null;
|
|
@@ -35595,6 +36171,7 @@ var HourlyOutputChartComponent = ({
|
|
|
35595
36171
|
hourlyTargetOutput,
|
|
35596
36172
|
shiftStart,
|
|
35597
36173
|
shiftEnd,
|
|
36174
|
+
shiftBreaks = [],
|
|
35598
36175
|
showIdleTime = false,
|
|
35599
36176
|
idleTimeHourly,
|
|
35600
36177
|
shiftDate,
|
|
@@ -35832,13 +36409,41 @@ var HourlyOutputChartComponent = ({
|
|
|
35832
36409
|
end: index === skuTimelineSegments.length - 1 ? Math.max(segment.start, targetLineEndOffset) : segment.end
|
|
35833
36410
|
})).filter((segment) => segment.end > segment.start);
|
|
35834
36411
|
}, [skuTimelineSegments, targetLineEndOffset]);
|
|
35835
|
-
const
|
|
36412
|
+
const hasExplicitHourlyTargetOutputProp = React144__namespace.default.useMemo(
|
|
35836
36413
|
() => hourlyTargetOutput !== void 0,
|
|
35837
36414
|
[hourlyTargetOutput]
|
|
35838
36415
|
);
|
|
36416
|
+
const fallbackHourlyTargetOutput = React144__namespace.default.useMemo(() => {
|
|
36417
|
+
if (hasExplicitHourlyTargetOutputProp) return void 0;
|
|
36418
|
+
if (skuTimelineSegments.length > 0) return void 0;
|
|
36419
|
+
const plan = buildHourlyTargetPlan({
|
|
36420
|
+
shiftStart,
|
|
36421
|
+
shiftEnd,
|
|
36422
|
+
breaks: shiftBreaks,
|
|
36423
|
+
pphThreshold,
|
|
36424
|
+
rounding: "floor"
|
|
36425
|
+
});
|
|
36426
|
+
if (!plan.targets.length) return void 0;
|
|
36427
|
+
return plan.targets.map((value) => Number.isFinite(value) ? value : null);
|
|
36428
|
+
}, [
|
|
36429
|
+
hasExplicitHourlyTargetOutputProp,
|
|
36430
|
+
skuTimelineSegments.length,
|
|
36431
|
+
shiftStart,
|
|
36432
|
+
shiftEnd,
|
|
36433
|
+
shiftBreaks,
|
|
36434
|
+
pphThreshold
|
|
36435
|
+
]);
|
|
36436
|
+
const effectiveHourlyTargetOutput = React144__namespace.default.useMemo(
|
|
36437
|
+
() => hasExplicitHourlyTargetOutputProp ? hourlyTargetOutput : fallbackHourlyTargetOutput,
|
|
36438
|
+
[hasExplicitHourlyTargetOutputProp, hourlyTargetOutput, fallbackHourlyTargetOutput]
|
|
36439
|
+
);
|
|
36440
|
+
const hasHourlyTargetOutputProp = React144__namespace.default.useMemo(
|
|
36441
|
+
() => effectiveHourlyTargetOutput !== void 0,
|
|
36442
|
+
[effectiveHourlyTargetOutput]
|
|
36443
|
+
);
|
|
35839
36444
|
const hasExplicitHourlyTargets = React144__namespace.default.useMemo(
|
|
35840
|
-
() => Array.isArray(
|
|
35841
|
-
[
|
|
36445
|
+
() => Array.isArray(effectiveHourlyTargetOutput) && effectiveHourlyTargetOutput.some((value) => value !== null && value !== void 0),
|
|
36446
|
+
[effectiveHourlyTargetOutput]
|
|
35842
36447
|
);
|
|
35843
36448
|
const hourlyTargetSegments = React144__namespace.default.useMemo(() => {
|
|
35844
36449
|
if (!hasExplicitHourlyTargets) return [];
|
|
@@ -35852,7 +36457,7 @@ var HourlyOutputChartComponent = ({
|
|
|
35852
36457
|
runValue = null;
|
|
35853
36458
|
};
|
|
35854
36459
|
for (let i = 0; i < SHIFT_DURATION; i += 1) {
|
|
35855
|
-
const rawValue = Array.isArray(
|
|
36460
|
+
const rawValue = Array.isArray(effectiveHourlyTargetOutput) ? effectiveHourlyTargetOutput[i] : null;
|
|
35856
36461
|
const value = rawValue === null || rawValue === void 0 ? null : Number(rawValue);
|
|
35857
36462
|
if (value === null || !Number.isFinite(value)) {
|
|
35858
36463
|
flush(i);
|
|
@@ -35871,7 +36476,7 @@ var HourlyOutputChartComponent = ({
|
|
|
35871
36476
|
}
|
|
35872
36477
|
flush(SHIFT_DURATION);
|
|
35873
36478
|
return segments.filter((segment) => segment.end > segment.start);
|
|
35874
|
-
}, [SHIFT_DURATION, hasExplicitHourlyTargets,
|
|
36479
|
+
}, [SHIFT_DURATION, hasExplicitHourlyTargets, effectiveHourlyTargetOutput]);
|
|
35875
36480
|
const activeSkuHourIndices = React144__namespace.default.useMemo(() => {
|
|
35876
36481
|
const indices = /* @__PURE__ */ new Set();
|
|
35877
36482
|
const targets = Array(SHIFT_DURATION).fill(pphThreshold);
|
|
@@ -35909,7 +36514,7 @@ var HourlyOutputChartComponent = ({
|
|
|
35909
36514
|
const { indices, targets, hasTimeline } = activeSkuHourIndices;
|
|
35910
36515
|
return Array.from({ length: SHIFT_DURATION }, (_, i) => {
|
|
35911
36516
|
const idleSlot = idleSlots[i];
|
|
35912
|
-
const explicitTarget = hasHourlyTargetOutputProp ?
|
|
36517
|
+
const explicitTarget = hasHourlyTargetOutputProp ? effectiveHourlyTargetOutput?.[i] ?? null : void 0;
|
|
35913
36518
|
const currentTarget = hasHourlyTargetOutputProp ? explicitTarget : targets[i] || pphThreshold;
|
|
35914
36519
|
const comparisonTarget = currentTarget === null || currentTarget === void 0 ? targets[i] || pphThreshold : currentTarget;
|
|
35915
36520
|
return {
|
|
@@ -35928,7 +36533,7 @@ var HourlyOutputChartComponent = ({
|
|
|
35928
36533
|
isDimmed: hasTimeline && !!activeSkuId && !indices.has(i)
|
|
35929
36534
|
};
|
|
35930
36535
|
});
|
|
35931
|
-
}, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId,
|
|
36536
|
+
}, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, effectiveHourlyTargetOutput, hasHourlyTargetOutputProp]);
|
|
35932
36537
|
const renderSkuTimelineRail = React144__namespace.default.useCallback((props) => {
|
|
35933
36538
|
if (!skuTimelineSegments.length || SHIFT_DURATION <= 0) return null;
|
|
35934
36539
|
const offset = props?.offset;
|
|
@@ -36587,6 +37192,16 @@ var HourlyOutputChart = React144__namespace.default.memo(
|
|
|
36587
37192
|
if (!prevProps.data.every((val, idx) => val === nextProps.data[idx])) {
|
|
36588
37193
|
return false;
|
|
36589
37194
|
}
|
|
37195
|
+
const prevHasHourlyTargetOutputProp = prevProps.hourlyTargetOutput !== void 0;
|
|
37196
|
+
const nextHasHourlyTargetOutputProp = nextProps.hourlyTargetOutput !== void 0;
|
|
37197
|
+
if (prevHasHourlyTargetOutputProp !== nextHasHourlyTargetOutputProp) {
|
|
37198
|
+
return false;
|
|
37199
|
+
}
|
|
37200
|
+
if (prevProps.hourlyTargetOutput === null || nextProps.hourlyTargetOutput === null) {
|
|
37201
|
+
if (prevProps.hourlyTargetOutput !== nextProps.hourlyTargetOutput) {
|
|
37202
|
+
return false;
|
|
37203
|
+
}
|
|
37204
|
+
}
|
|
36590
37205
|
const prevHourlyTargets = prevProps.hourlyTargetOutput || [];
|
|
36591
37206
|
const nextHourlyTargets = nextProps.hourlyTargetOutput || [];
|
|
36592
37207
|
if (prevHourlyTargets.length !== nextHourlyTargets.length) {
|
|
@@ -36597,6 +37212,18 @@ var HourlyOutputChart = React144__namespace.default.memo(
|
|
|
36597
37212
|
return false;
|
|
36598
37213
|
}
|
|
36599
37214
|
}
|
|
37215
|
+
const prevShiftBreaks = prevProps.shiftBreaks || [];
|
|
37216
|
+
const nextShiftBreaks = nextProps.shiftBreaks || [];
|
|
37217
|
+
if (prevShiftBreaks.length !== nextShiftBreaks.length) {
|
|
37218
|
+
return false;
|
|
37219
|
+
}
|
|
37220
|
+
for (let i = 0; i < prevShiftBreaks.length; i += 1) {
|
|
37221
|
+
const prevBreak = prevShiftBreaks[i] || {};
|
|
37222
|
+
const nextBreak = nextShiftBreaks[i] || {};
|
|
37223
|
+
if (prevBreak.startTime !== nextBreak.startTime || prevBreak.endTime !== nextBreak.endTime || prevBreak.duration !== nextBreak.duration || prevBreak.remarks !== nextBreak.remarks) {
|
|
37224
|
+
return false;
|
|
37225
|
+
}
|
|
37226
|
+
}
|
|
36600
37227
|
const prevIdle = prevProps.idleTimeHourly || {};
|
|
36601
37228
|
const nextIdle = nextProps.idleTimeHourly || {};
|
|
36602
37229
|
const prevKeys = Object.keys(prevIdle);
|
|
@@ -36689,6 +37316,9 @@ var isLowWipGreenOverride = (workspace, legend = DEFAULT_EFFICIENCY_LEGEND) => {
|
|
|
36689
37316
|
if (workspace.scheduled_break_active === true) {
|
|
36690
37317
|
return false;
|
|
36691
37318
|
}
|
|
37319
|
+
if (workspace.recent_flow_forced_zero_after_shift === true) {
|
|
37320
|
+
return false;
|
|
37321
|
+
}
|
|
36692
37322
|
if (!hasVideoGridRecentFlow(workspace) || !isVideoGridWipGated(workspace)) {
|
|
36693
37323
|
return false;
|
|
36694
37324
|
}
|
|
@@ -36934,7 +37564,7 @@ var VideoCard = React144__namespace.default.memo(({
|
|
|
36934
37564
|
}
|
|
36935
37565
|
);
|
|
36936
37566
|
}, (prevProps, nextProps) => {
|
|
36937
|
-
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) {
|
|
37567
|
+
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) {
|
|
36938
37568
|
return false;
|
|
36939
37569
|
}
|
|
36940
37570
|
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) {
|
|
@@ -37253,6 +37883,15 @@ var VideoGridView = React144__namespace.default.memo(({
|
|
|
37253
37883
|
}
|
|
37254
37884
|
console.error(`[VideoGridView] Stream failed for workspace: ${workspaceId}`);
|
|
37255
37885
|
setFailedStreams((prev) => new Set(prev).add(workspaceId));
|
|
37886
|
+
captureHandledFrontendException(new Error("Video stream failed after recovery attempts"), {
|
|
37887
|
+
surface: "video_grid_stream",
|
|
37888
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
37889
|
+
extras: {
|
|
37890
|
+
workspace_id: workspaceId,
|
|
37891
|
+
stream_source: isR2Stream ? "r2" : "media_config",
|
|
37892
|
+
has_fallback: hasFallback
|
|
37893
|
+
}
|
|
37894
|
+
});
|
|
37256
37895
|
trackCoreEvent("Video Stream Error", {
|
|
37257
37896
|
workspace_id: workspaceId,
|
|
37258
37897
|
view_type: "video_grid",
|
|
@@ -41920,11 +42559,14 @@ var SilentErrorBoundary = class extends React144__namespace.default.Component {
|
|
|
41920
42559
|
componentStack: errorInfo.componentStack,
|
|
41921
42560
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
41922
42561
|
});
|
|
41923
|
-
|
|
42562
|
+
captureHandledFrontendException(error, {
|
|
41924
42563
|
surface: "react_error_boundary",
|
|
41925
|
-
|
|
41926
|
-
|
|
41927
|
-
|
|
42564
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
42565
|
+
severity: "error",
|
|
42566
|
+
extras: {
|
|
42567
|
+
component_stack: errorInfo.componentStack,
|
|
42568
|
+
error_count: this.state.errorCount + 1
|
|
42569
|
+
}
|
|
41928
42570
|
});
|
|
41929
42571
|
this.setState((prev) => ({
|
|
41930
42572
|
errorCount: prev.errorCount + 1,
|
|
@@ -44461,6 +45103,11 @@ var OVERLAY_ICON_COLOR_BY_PALETTE = {
|
|
|
44461
45103
|
cyan: "text-cyan-300",
|
|
44462
45104
|
slate: "text-slate-200"
|
|
44463
45105
|
};
|
|
45106
|
+
var buildPlaybackCaptureError = (recoverable) => {
|
|
45107
|
+
return new Error(
|
|
45108
|
+
recoverable ? "Clip playback failed after retry exhaustion" : "Clip playback failed with non-recoverable media error"
|
|
45109
|
+
);
|
|
45110
|
+
};
|
|
44464
45111
|
var BottlenecksContent = ({
|
|
44465
45112
|
workspaceId,
|
|
44466
45113
|
workspaceName,
|
|
@@ -44915,6 +45562,16 @@ var BottlenecksContent = ({
|
|
|
44915
45562
|
}
|
|
44916
45563
|
} catch (err) {
|
|
44917
45564
|
console.error("Error loading first video for category:", err);
|
|
45565
|
+
captureHandledFrontendException(err, {
|
|
45566
|
+
surface: "clips_initial_load",
|
|
45567
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
45568
|
+
extras: {
|
|
45569
|
+
workspace_id: workspaceId,
|
|
45570
|
+
category: targetCategory,
|
|
45571
|
+
date: effectiveDateString,
|
|
45572
|
+
shift_id: effectiveShiftId
|
|
45573
|
+
}
|
|
45574
|
+
});
|
|
44918
45575
|
if (isMountedRef.current) {
|
|
44919
45576
|
setError({
|
|
44920
45577
|
type: "fatal",
|
|
@@ -45452,6 +46109,15 @@ var BottlenecksContent = ({
|
|
|
45452
46109
|
console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
|
|
45453
46110
|
} catch (error2) {
|
|
45454
46111
|
console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
|
|
46112
|
+
captureHandledFrontendException(error2, {
|
|
46113
|
+
surface: "clips_selected_load",
|
|
46114
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
46115
|
+
extras: {
|
|
46116
|
+
workspace_id: workspaceId,
|
|
46117
|
+
clip_id: clipId,
|
|
46118
|
+
category: categoryId
|
|
46119
|
+
}
|
|
46120
|
+
});
|
|
45455
46121
|
if (isMountedRef.current) {
|
|
45456
46122
|
setError({
|
|
45457
46123
|
type: "fatal",
|
|
@@ -46051,6 +46717,18 @@ var BottlenecksContent = ({
|
|
|
46051
46717
|
errorCode,
|
|
46052
46718
|
errorMessage
|
|
46053
46719
|
});
|
|
46720
|
+
captureHandledFrontendException(buildPlaybackCaptureError(false), {
|
|
46721
|
+
surface: "clips_video_playback",
|
|
46722
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
46723
|
+
extras: {
|
|
46724
|
+
workspace_id: workspaceId,
|
|
46725
|
+
category: activeFilterRef.current,
|
|
46726
|
+
video_id: currentVideo?.id,
|
|
46727
|
+
error_code: errorCode,
|
|
46728
|
+
player_error_message: errorMessage,
|
|
46729
|
+
recoverable: false
|
|
46730
|
+
}
|
|
46731
|
+
});
|
|
46054
46732
|
return;
|
|
46055
46733
|
}
|
|
46056
46734
|
if (videoRetryCountRef.current < 3 && currentVideo) {
|
|
@@ -46094,6 +46772,19 @@ var BottlenecksContent = ({
|
|
|
46094
46772
|
errorMessage,
|
|
46095
46773
|
attempts: 3
|
|
46096
46774
|
});
|
|
46775
|
+
captureHandledFrontendException(buildPlaybackCaptureError(true), {
|
|
46776
|
+
surface: "clips_video_playback",
|
|
46777
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
46778
|
+
extras: {
|
|
46779
|
+
workspace_id: workspaceId,
|
|
46780
|
+
category: activeFilterRef.current,
|
|
46781
|
+
video_id: currentVideo?.id,
|
|
46782
|
+
error_code: errorCode,
|
|
46783
|
+
player_error_message: errorMessage,
|
|
46784
|
+
recoverable: true,
|
|
46785
|
+
attempts: 3
|
|
46786
|
+
}
|
|
46787
|
+
});
|
|
46097
46788
|
}
|
|
46098
46789
|
}, [currentVideo, workspaceId, clearLoadingState, clearRetryTimeout, restartCurrentClipPlayback]);
|
|
46099
46790
|
React144.useEffect(() => {
|
|
@@ -48425,6 +49116,14 @@ var LinePdfExportButton = ({
|
|
|
48425
49116
|
pdf.save(`${fileName}.pdf`);
|
|
48426
49117
|
} catch (error) {
|
|
48427
49118
|
console.error("PDF Export Error:", error);
|
|
49119
|
+
captureHandledFrontendException(error, {
|
|
49120
|
+
surface: "line_pdf_export",
|
|
49121
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
49122
|
+
extras: {
|
|
49123
|
+
file_name: fileName,
|
|
49124
|
+
target_type: typeof targetElement === "string" ? "selector" : "element"
|
|
49125
|
+
}
|
|
49126
|
+
});
|
|
48428
49127
|
alert("An error occurred while exporting to PDF. Please try again.");
|
|
48429
49128
|
} finally {
|
|
48430
49129
|
setIsExporting(false);
|
|
@@ -50247,6 +50946,16 @@ var LineMonthlyPdfGenerator = ({
|
|
|
50247
50946
|
doc.save(fileName);
|
|
50248
50947
|
} catch (error) {
|
|
50249
50948
|
console.error("Line Monthly PDF generation failed:", error);
|
|
50949
|
+
captureHandledFrontendException(error, {
|
|
50950
|
+
surface: "line_monthly_pdf_generation",
|
|
50951
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
50952
|
+
extras: {
|
|
50953
|
+
line_name: lineName || null,
|
|
50954
|
+
selected_month: selectedMonth,
|
|
50955
|
+
selected_year: selectedYear,
|
|
50956
|
+
selected_shift_id: selectedShiftId
|
|
50957
|
+
}
|
|
50958
|
+
});
|
|
50250
50959
|
} finally {
|
|
50251
50960
|
setIsGenerating(false);
|
|
50252
50961
|
}
|
|
@@ -50311,261 +51020,6 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
|
|
|
50311
51020
|
}
|
|
50312
51021
|
);
|
|
50313
51022
|
};
|
|
50314
|
-
|
|
50315
|
-
// src/lib/utils/hourlyTargets.ts
|
|
50316
|
-
var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
|
|
50317
|
-
var MINUTES_PER_DAY = 24 * 60;
|
|
50318
|
-
var parseTimeToMinutes2 = (timeString) => {
|
|
50319
|
-
const normalized = stripSeconds2(timeString || "");
|
|
50320
|
-
if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
|
|
50321
|
-
const [hours, minutes] = normalized.split(":").map(Number);
|
|
50322
|
-
return hours * 60 + minutes;
|
|
50323
|
-
};
|
|
50324
|
-
var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
|
|
50325
|
-
const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
|
|
50326
|
-
if (!Number.isFinite(shiftStartMinutes)) return [];
|
|
50327
|
-
const normalizedBreaks = [];
|
|
50328
|
-
for (const entry of breaks) {
|
|
50329
|
-
const startRaw = parseTimeToMinutes2(entry.startTime);
|
|
50330
|
-
const endRaw = parseTimeToMinutes2(entry.endTime);
|
|
50331
|
-
if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
|
|
50332
|
-
let start = startRaw;
|
|
50333
|
-
let end = endRaw;
|
|
50334
|
-
if (end <= start) {
|
|
50335
|
-
end += 24 * 60;
|
|
50336
|
-
}
|
|
50337
|
-
if (start < shiftStartMinutes) {
|
|
50338
|
-
start += 24 * 60;
|
|
50339
|
-
end += 24 * 60;
|
|
50340
|
-
}
|
|
50341
|
-
const label = entry.remarks?.trim() || "Break";
|
|
50342
|
-
normalizedBreaks.push({ start, end, label });
|
|
50343
|
-
}
|
|
50344
|
-
return normalizedBreaks;
|
|
50345
|
-
};
|
|
50346
|
-
var roundTarget = (value, mode) => {
|
|
50347
|
-
if (!Number.isFinite(value)) return 0;
|
|
50348
|
-
switch (mode) {
|
|
50349
|
-
case "floor":
|
|
50350
|
-
return Math.floor(value);
|
|
50351
|
-
case "ceil":
|
|
50352
|
-
return Math.ceil(value);
|
|
50353
|
-
case "round":
|
|
50354
|
-
default:
|
|
50355
|
-
return Math.round(value);
|
|
50356
|
-
}
|
|
50357
|
-
};
|
|
50358
|
-
var formatDateKey = (date) => {
|
|
50359
|
-
const year = date.getUTCFullYear();
|
|
50360
|
-
const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
|
|
50361
|
-
const day = `${date.getUTCDate()}`.padStart(2, "0");
|
|
50362
|
-
return `${year}-${month}-${day}`;
|
|
50363
|
-
};
|
|
50364
|
-
var shiftDateKey = (dateKey, deltaDays) => {
|
|
50365
|
-
const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
|
|
50366
|
-
const year = Number.isFinite(yearPart) ? yearPart : 1970;
|
|
50367
|
-
const month = Number.isFinite(monthPart) ? monthPart : 1;
|
|
50368
|
-
const day = Number.isFinite(dayPart) ? dayPart : 1;
|
|
50369
|
-
const date = new Date(Date.UTC(year, month - 1, day));
|
|
50370
|
-
date.setUTCDate(date.getUTCDate() + deltaDays);
|
|
50371
|
-
return formatDateKey(date);
|
|
50372
|
-
};
|
|
50373
|
-
var getZonedNowSnapshot = (timeZone, now4) => {
|
|
50374
|
-
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
50375
|
-
timeZone,
|
|
50376
|
-
year: "numeric",
|
|
50377
|
-
month: "2-digit",
|
|
50378
|
-
day: "2-digit",
|
|
50379
|
-
hour: "2-digit",
|
|
50380
|
-
minute: "2-digit",
|
|
50381
|
-
hourCycle: "h23"
|
|
50382
|
-
});
|
|
50383
|
-
const parts = formatter.formatToParts(now4).reduce((acc, part) => {
|
|
50384
|
-
if (part.type !== "literal") {
|
|
50385
|
-
acc[part.type] = part.value;
|
|
50386
|
-
}
|
|
50387
|
-
return acc;
|
|
50388
|
-
}, {});
|
|
50389
|
-
const year = Number(parts.year);
|
|
50390
|
-
const month = Number(parts.month);
|
|
50391
|
-
const day = Number(parts.day);
|
|
50392
|
-
const hour = Number(parts.hour);
|
|
50393
|
-
const minute = Number(parts.minute);
|
|
50394
|
-
return {
|
|
50395
|
-
dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
|
|
50396
|
-
minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
|
|
50397
|
-
};
|
|
50398
|
-
};
|
|
50399
|
-
var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
|
|
50400
|
-
var buildHourlyIntervals = ({
|
|
50401
|
-
shiftStart,
|
|
50402
|
-
shiftEnd,
|
|
50403
|
-
bucketMinutes = 60,
|
|
50404
|
-
fallbackHours = 11
|
|
50405
|
-
}) => {
|
|
50406
|
-
const startMinutes = parseTimeToMinutes2(shiftStart);
|
|
50407
|
-
if (!Number.isFinite(startMinutes)) return [];
|
|
50408
|
-
const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
|
|
50409
|
-
let totalMinutes;
|
|
50410
|
-
const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
|
|
50411
|
-
if (!Number.isFinite(endRaw)) {
|
|
50412
|
-
totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
|
|
50413
|
-
} else {
|
|
50414
|
-
let endMinutes = endRaw;
|
|
50415
|
-
if (endMinutes <= startMinutes) {
|
|
50416
|
-
endMinutes += 24 * 60;
|
|
50417
|
-
}
|
|
50418
|
-
totalMinutes = endMinutes - startMinutes;
|
|
50419
|
-
}
|
|
50420
|
-
if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
|
|
50421
|
-
const count = Math.ceil(totalMinutes / bucket);
|
|
50422
|
-
const shiftEndMinutes = startMinutes + totalMinutes;
|
|
50423
|
-
const intervals = [];
|
|
50424
|
-
for (let i = 0; i < count; i += 1) {
|
|
50425
|
-
const start = startMinutes + i * bucket;
|
|
50426
|
-
const end = Math.min(start + bucket, shiftEndMinutes);
|
|
50427
|
-
const minutes = Math.max(0, end - start);
|
|
50428
|
-
if (minutes <= 0) continue;
|
|
50429
|
-
intervals.push({ start, end, minutes });
|
|
50430
|
-
}
|
|
50431
|
-
return intervals;
|
|
50432
|
-
};
|
|
50433
|
-
var computeBreakMinutesByInterval = ({
|
|
50434
|
-
intervals,
|
|
50435
|
-
shiftStart,
|
|
50436
|
-
breaks
|
|
50437
|
-
}) => {
|
|
50438
|
-
if (!intervals.length || !breaks.length) return intervals.map(() => 0);
|
|
50439
|
-
const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
|
|
50440
|
-
return intervals.map((interval) => {
|
|
50441
|
-
if (!normalizedBreaks.length) return 0;
|
|
50442
|
-
let total = 0;
|
|
50443
|
-
for (const brk of normalizedBreaks) {
|
|
50444
|
-
const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
|
|
50445
|
-
total += overlap;
|
|
50446
|
-
if (total >= interval.minutes) return interval.minutes;
|
|
50447
|
-
}
|
|
50448
|
-
return Math.min(interval.minutes, total);
|
|
50449
|
-
});
|
|
50450
|
-
};
|
|
50451
|
-
var computeBreakRemarksByInterval = ({
|
|
50452
|
-
intervals,
|
|
50453
|
-
shiftStart,
|
|
50454
|
-
breaks
|
|
50455
|
-
}) => {
|
|
50456
|
-
if (!intervals.length || !breaks.length) return intervals.map(() => "");
|
|
50457
|
-
const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
|
|
50458
|
-
return intervals.map((interval) => {
|
|
50459
|
-
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);
|
|
50460
|
-
return labels.join(", ");
|
|
50461
|
-
});
|
|
50462
|
-
};
|
|
50463
|
-
var computeEffectiveTargets = ({
|
|
50464
|
-
intervals,
|
|
50465
|
-
breakMinutes,
|
|
50466
|
-
pphThreshold,
|
|
50467
|
-
rounding = "round"
|
|
50468
|
-
}) => {
|
|
50469
|
-
return intervals.map((interval, idx) => {
|
|
50470
|
-
const intervalMinutes = Number(interval?.minutes) || 0;
|
|
50471
|
-
const breakMins = Number(breakMinutes?.[idx]) || 0;
|
|
50472
|
-
const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
|
|
50473
|
-
if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
|
|
50474
|
-
if (plannedWorkMinutes <= 0) return 0;
|
|
50475
|
-
return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
|
|
50476
|
-
});
|
|
50477
|
-
};
|
|
50478
|
-
var buildHourlyTargetPlan = ({
|
|
50479
|
-
shiftStart,
|
|
50480
|
-
shiftEnd,
|
|
50481
|
-
breaks = [],
|
|
50482
|
-
pphThreshold,
|
|
50483
|
-
bucketMinutes = 60,
|
|
50484
|
-
fallbackHours = 11,
|
|
50485
|
-
rounding = "round"
|
|
50486
|
-
}) => {
|
|
50487
|
-
const intervals = buildHourlyIntervals({
|
|
50488
|
-
shiftStart,
|
|
50489
|
-
shiftEnd,
|
|
50490
|
-
bucketMinutes,
|
|
50491
|
-
fallbackHours
|
|
50492
|
-
});
|
|
50493
|
-
const breakMinutes = computeBreakMinutesByInterval({
|
|
50494
|
-
intervals,
|
|
50495
|
-
shiftStart,
|
|
50496
|
-
breaks
|
|
50497
|
-
});
|
|
50498
|
-
const breakRemarks = computeBreakRemarksByInterval({
|
|
50499
|
-
intervals,
|
|
50500
|
-
shiftStart,
|
|
50501
|
-
breaks
|
|
50502
|
-
});
|
|
50503
|
-
const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
|
|
50504
|
-
const targets = computeEffectiveTargets({
|
|
50505
|
-
intervals,
|
|
50506
|
-
breakMinutes,
|
|
50507
|
-
pphThreshold,
|
|
50508
|
-
rounding
|
|
50509
|
-
});
|
|
50510
|
-
return {
|
|
50511
|
-
intervals,
|
|
50512
|
-
breakMinutes,
|
|
50513
|
-
breakRemarks,
|
|
50514
|
-
productiveMinutes,
|
|
50515
|
-
targets
|
|
50516
|
-
};
|
|
50517
|
-
};
|
|
50518
|
-
var isHourlyIntervalComplete = ({
|
|
50519
|
-
reportDate,
|
|
50520
|
-
shiftStart,
|
|
50521
|
-
shiftEnd,
|
|
50522
|
-
interval,
|
|
50523
|
-
timeZone = "Asia/Kolkata",
|
|
50524
|
-
now: now4 = /* @__PURE__ */ new Date()
|
|
50525
|
-
}) => {
|
|
50526
|
-
if (!reportDate) return true;
|
|
50527
|
-
const snapshot = getZonedNowSnapshot(timeZone, now4);
|
|
50528
|
-
const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
|
|
50529
|
-
const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
|
|
50530
|
-
const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
|
|
50531
|
-
if (reportDate === snapshot.dateKey) {
|
|
50532
|
-
return interval.end <= snapshot.minutesOfDay;
|
|
50533
|
-
}
|
|
50534
|
-
if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
|
|
50535
|
-
return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
|
|
50536
|
-
}
|
|
50537
|
-
return reportDate < snapshot.dateKey;
|
|
50538
|
-
};
|
|
50539
|
-
var isShiftInProgressForReportDate = ({
|
|
50540
|
-
reportDate,
|
|
50541
|
-
shiftStart,
|
|
50542
|
-
shiftEnd,
|
|
50543
|
-
timeZone = "Asia/Kolkata",
|
|
50544
|
-
now: now4 = /* @__PURE__ */ new Date()
|
|
50545
|
-
}) => {
|
|
50546
|
-
if (!reportDate || !shiftStart || !shiftEnd) return false;
|
|
50547
|
-
const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
|
|
50548
|
-
const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
|
|
50549
|
-
if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
|
|
50550
|
-
return false;
|
|
50551
|
-
}
|
|
50552
|
-
let shiftEndMinutes = shiftEndMinutesRaw;
|
|
50553
|
-
const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
|
|
50554
|
-
if (wrapsMidnight) {
|
|
50555
|
-
shiftEndMinutes += MINUTES_PER_DAY;
|
|
50556
|
-
}
|
|
50557
|
-
const snapshot = getZonedNowSnapshot(timeZone, now4);
|
|
50558
|
-
let currentMinutes = null;
|
|
50559
|
-
if (reportDate === snapshot.dateKey) {
|
|
50560
|
-
currentMinutes = snapshot.minutesOfDay;
|
|
50561
|
-
} else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
|
|
50562
|
-
currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
|
|
50563
|
-
}
|
|
50564
|
-
if (currentMinutes === null) {
|
|
50565
|
-
return false;
|
|
50566
|
-
}
|
|
50567
|
-
return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
|
|
50568
|
-
};
|
|
50569
51023
|
var formatOperationalDateKey = (dateKey, options) => {
|
|
50570
51024
|
const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
|
|
50571
51025
|
const year = Number.isFinite(yearPart) ? yearPart : 1970;
|
|
@@ -51284,6 +51738,15 @@ var LinePdfGenerator = ({
|
|
|
51284
51738
|
doc.save(fileName);
|
|
51285
51739
|
} catch (error) {
|
|
51286
51740
|
console.error("PDF generation failed:", error);
|
|
51741
|
+
captureHandledFrontendException(error, {
|
|
51742
|
+
surface: "line_pdf_generation",
|
|
51743
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
51744
|
+
extras: {
|
|
51745
|
+
line_id: lineInfo.line_id,
|
|
51746
|
+
date: lineInfo.date,
|
|
51747
|
+
shift_id: lineInfo.shift_id
|
|
51748
|
+
}
|
|
51749
|
+
});
|
|
51287
51750
|
} finally {
|
|
51288
51751
|
setIsGenerating(false);
|
|
51289
51752
|
}
|
|
@@ -51356,6 +51819,14 @@ var WorkspacePdfExportButton = ({
|
|
|
51356
51819
|
pdf.save(`${fileName}.pdf`);
|
|
51357
51820
|
} catch (error) {
|
|
51358
51821
|
console.error("PDF Export Error:", error);
|
|
51822
|
+
captureHandledFrontendException(error, {
|
|
51823
|
+
surface: "workspace_pdf_export",
|
|
51824
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
51825
|
+
extras: {
|
|
51826
|
+
file_name: fileName,
|
|
51827
|
+
target_type: typeof targetElement === "string" ? "selector" : "element"
|
|
51828
|
+
}
|
|
51829
|
+
});
|
|
51359
51830
|
alert("An error occurred while exporting to PDF. Please try again.");
|
|
51360
51831
|
} finally {
|
|
51361
51832
|
setIsExporting(false);
|
|
@@ -53205,6 +53676,16 @@ var WorkspacePdfGenerator = ({
|
|
|
53205
53676
|
doc.save(fileName);
|
|
53206
53677
|
} catch (error) {
|
|
53207
53678
|
console.error("PDF generation failed:", error);
|
|
53679
|
+
captureHandledFrontendException(error, {
|
|
53680
|
+
surface: "workspace_pdf_generation",
|
|
53681
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
53682
|
+
extras: {
|
|
53683
|
+
workspace_id: workspace.workspace_id,
|
|
53684
|
+
line_id: workspace.line_id,
|
|
53685
|
+
date: workspace.date,
|
|
53686
|
+
shift_id: workspace.shift_id
|
|
53687
|
+
}
|
|
53688
|
+
});
|
|
53208
53689
|
} finally {
|
|
53209
53690
|
setIsGenerating(false);
|
|
53210
53691
|
}
|
|
@@ -53612,6 +54093,18 @@ var WorkspaceMonthlyPdfGenerator = ({
|
|
|
53612
54093
|
doc.save(fileName);
|
|
53613
54094
|
} catch (error) {
|
|
53614
54095
|
console.error("Monthly PDF generation failed:", error);
|
|
54096
|
+
captureHandledFrontendException(error, {
|
|
54097
|
+
surface: "workspace_monthly_pdf_generation",
|
|
54098
|
+
route: typeof window !== "undefined" ? window.location.pathname : "unknown",
|
|
54099
|
+
extras: {
|
|
54100
|
+
workspace_id: workspaceId,
|
|
54101
|
+
workspace_name: workspaceName,
|
|
54102
|
+
line_name: lineName || null,
|
|
54103
|
+
selected_month: selectedMonth,
|
|
54104
|
+
selected_year: selectedYear,
|
|
54105
|
+
selected_shift_id: selectedShiftId
|
|
54106
|
+
}
|
|
54107
|
+
});
|
|
53615
54108
|
} finally {
|
|
53616
54109
|
setIsGenerating(false);
|
|
53617
54110
|
}
|
|
@@ -62884,6 +63377,16 @@ var useLiveMonitorBootstrap = ({
|
|
|
62884
63377
|
if (requestId !== activeRequestIdRef.current) {
|
|
62885
63378
|
return;
|
|
62886
63379
|
}
|
|
63380
|
+
captureHandledFrontendException(fetchError, {
|
|
63381
|
+
surface: "live_monitor_bootstrap",
|
|
63382
|
+
route: "/api/dashboard/monitor-bootstrap",
|
|
63383
|
+
extras: {
|
|
63384
|
+
company_id: resolvedCompanyId,
|
|
63385
|
+
line_ids: normalizedLineIds,
|
|
63386
|
+
force_refresh: force,
|
|
63387
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
63388
|
+
}
|
|
63389
|
+
});
|
|
62887
63390
|
setError(fetchError instanceof Error ? fetchError : new Error("Failed to load live monitor bootstrap"));
|
|
62888
63391
|
} finally {
|
|
62889
63392
|
if (requestId === activeRequestIdRef.current) {
|
|
@@ -63196,11 +63699,14 @@ var NotificationService = class {
|
|
|
63196
63699
|
}
|
|
63197
63700
|
return response;
|
|
63198
63701
|
} catch (error) {
|
|
63199
|
-
|
|
63702
|
+
addSentryBreadcrumb("Notification service request failed", {
|
|
63200
63703
|
surface: "notification_service",
|
|
63201
|
-
url,
|
|
63202
|
-
|
|
63203
|
-
|
|
63704
|
+
route: url,
|
|
63705
|
+
severity: "warning",
|
|
63706
|
+
extras: {
|
|
63707
|
+
method: options.method || "GET",
|
|
63708
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
63709
|
+
}
|
|
63204
63710
|
});
|
|
63205
63711
|
throw error;
|
|
63206
63712
|
}
|
|
@@ -64708,7 +65214,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
|
|
|
64708
65214
|
underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
|
|
64709
65215
|
output_array: metrics2.output_array || [],
|
|
64710
65216
|
output_hourly: metrics2.output_hourly,
|
|
64711
|
-
hourly_target_output: metrics2.hourly_target_output
|
|
65217
|
+
hourly_target_output: metrics2.hourly_target_output,
|
|
64712
65218
|
line_threshold: metrics2.line_threshold ?? 0,
|
|
64713
65219
|
threshold_pph: metrics2.threshold_pph ?? 0,
|
|
64714
65220
|
shift_start: metrics2.shift_start || "06:00",
|
|
@@ -64795,7 +65301,7 @@ var transformLineMetrics = (lineId, detailResponse, queryDate, queryShiftId) =>
|
|
|
64795
65301
|
underperforming_workspace_names: [],
|
|
64796
65302
|
underperforming_workspace_uuids: [],
|
|
64797
65303
|
output_array: [],
|
|
64798
|
-
hourly_target_output:
|
|
65304
|
+
hourly_target_output: void 0,
|
|
64799
65305
|
line_threshold: 0,
|
|
64800
65306
|
threshold_pph: 0,
|
|
64801
65307
|
shift_start: "06:00",
|
|
@@ -65834,6 +66340,7 @@ var BottomSection = React144.memo(({
|
|
|
65834
66340
|
hourlyOutputData,
|
|
65835
66341
|
hourlyThreshold,
|
|
65836
66342
|
hourlyTargetOutput,
|
|
66343
|
+
shiftBreaks,
|
|
65837
66344
|
idleTimeHourly,
|
|
65838
66345
|
timezone,
|
|
65839
66346
|
urlDate,
|
|
@@ -66009,6 +66516,7 @@ var BottomSection = React144.memo(({
|
|
|
66009
66516
|
hourlyTargetOutput,
|
|
66010
66517
|
shiftStart: lineInfo.metrics.shift_start || "06:00",
|
|
66011
66518
|
shiftEnd: lineInfo.metrics.shift_end,
|
|
66519
|
+
shiftBreaks,
|
|
66012
66520
|
idleTimeHourly,
|
|
66013
66521
|
shiftDate: lineInfo.date,
|
|
66014
66522
|
timezone,
|
|
@@ -66032,6 +66540,9 @@ var BottomSection = React144.memo(({
|
|
|
66032
66540
|
if (prevProps.lineInfo.monitoring_mode !== nextProps.lineInfo.monitoring_mode) return false;
|
|
66033
66541
|
if (prevProps.skuAware !== nextProps.skuAware) return false;
|
|
66034
66542
|
if (prevProps.activeSkuId !== nextProps.activeSkuId) return false;
|
|
66543
|
+
if (JSON.stringify(prevProps.shiftBreaks || []) !== JSON.stringify(nextProps.shiftBreaks || [])) {
|
|
66544
|
+
return false;
|
|
66545
|
+
}
|
|
66035
66546
|
if (JSON.stringify(prevProps.hourlyTargetOutput || []) !== JSON.stringify(nextProps.hourlyTargetOutput || [])) {
|
|
66036
66547
|
return false;
|
|
66037
66548
|
}
|
|
@@ -66633,7 +67144,7 @@ var KPIDetailView = ({
|
|
|
66633
67144
|
underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
|
|
66634
67145
|
output_array: metrics2.output_array || [],
|
|
66635
67146
|
output_hourly: metrics2.output_hourly,
|
|
66636
|
-
hourly_target_output: metrics2.hourly_target_output
|
|
67147
|
+
hourly_target_output: metrics2.hourly_target_output,
|
|
66637
67148
|
line_threshold: metrics2.line_threshold ?? 0,
|
|
66638
67149
|
threshold_pph: metrics2.threshold_pph ?? 0,
|
|
66639
67150
|
shift_start: metrics2.shift_start || "06:00",
|
|
@@ -67597,7 +68108,8 @@ var KPIDetailView = ({
|
|
|
67597
68108
|
workspaceDisplayNames,
|
|
67598
68109
|
hourlyOutputData,
|
|
67599
68110
|
hourlyThreshold,
|
|
67600
|
-
hourlyTargetOutput: chartMetrics?.hourly_target_output
|
|
68111
|
+
hourlyTargetOutput: chartMetrics?.hourly_target_output,
|
|
68112
|
+
shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
|
|
67601
68113
|
idleTimeHourly: chartMetrics?.idle_time_hourly,
|
|
67602
68114
|
timezone: lineTimezone,
|
|
67603
68115
|
urlDate,
|
|
@@ -75361,6 +75873,7 @@ var WorkspaceDetailView = ({
|
|
|
75361
75873
|
hourlyTargetOutput: workspace.hourly_target_output,
|
|
75362
75874
|
shiftStart: workspace.shift_start || "06:00",
|
|
75363
75875
|
shiftEnd: workspace.shift_end,
|
|
75876
|
+
shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
|
|
75364
75877
|
showIdleTime: showChartIdleTime,
|
|
75365
75878
|
idleTimeHourly: workspace.idle_time_hourly,
|
|
75366
75879
|
idleTimeClips,
|
|
@@ -75508,6 +76021,7 @@ var WorkspaceDetailView = ({
|
|
|
75508
76021
|
hourlyTargetOutput: workspace.hourly_target_output,
|
|
75509
76022
|
shiftStart: workspace.shift_start || "06:00",
|
|
75510
76023
|
shiftEnd: workspace.shift_end,
|
|
76024
|
+
shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
|
|
75511
76025
|
showIdleTime: showChartIdleTime,
|
|
75512
76026
|
idleTimeHourly: workspace.idle_time_hourly,
|
|
75513
76027
|
idleTimeClips,
|
|
@@ -77347,11 +77861,14 @@ var TicketService = class {
|
|
|
77347
77861
|
}
|
|
77348
77862
|
return response;
|
|
77349
77863
|
} catch (error) {
|
|
77350
|
-
|
|
77864
|
+
addSentryBreadcrumb("Ticket service request failed", {
|
|
77351
77865
|
surface: "ticket_service",
|
|
77352
|
-
url,
|
|
77353
|
-
|
|
77354
|
-
|
|
77866
|
+
route: url,
|
|
77867
|
+
severity: "warning",
|
|
77868
|
+
extras: {
|
|
77869
|
+
method: options.method || "GET",
|
|
77870
|
+
pathname: typeof window !== "undefined" ? window.location.pathname : "unknown"
|
|
77871
|
+
}
|
|
77355
77872
|
});
|
|
77356
77873
|
throw error;
|
|
77357
77874
|
}
|
|
@@ -83131,10 +83648,33 @@ var useOperationsOverviewRefresh = ({
|
|
|
83131
83648
|
if (controller.signal.aborted || requestIdsRef.current[section] !== requestId || isAbortError2(error)) {
|
|
83132
83649
|
return;
|
|
83133
83650
|
}
|
|
83651
|
+
const sentryContext = {
|
|
83652
|
+
surface: "operations_overview_refresh",
|
|
83653
|
+
route: `/api/dashboard/operations-overview/${section}`,
|
|
83654
|
+
extras: {
|
|
83655
|
+
section,
|
|
83656
|
+
reason,
|
|
83657
|
+
company_id: companyId,
|
|
83658
|
+
line_ids: lineIds,
|
|
83659
|
+
start_date: startKey,
|
|
83660
|
+
end_date: endKey,
|
|
83661
|
+
trend_mode: trendMode,
|
|
83662
|
+
comparison_strategy: comparisonStrategy || null
|
|
83663
|
+
}
|
|
83664
|
+
};
|
|
83665
|
+
if (section === "improvements") {
|
|
83666
|
+
addSentryBreadcrumb("Operations overview optional improvements refresh failed", {
|
|
83667
|
+
...sentryContext,
|
|
83668
|
+
severity: "warning",
|
|
83669
|
+
category: "operations_overview"
|
|
83670
|
+
});
|
|
83671
|
+
} else {
|
|
83672
|
+
captureHandledFrontendException(error, sentryContext);
|
|
83673
|
+
}
|
|
83134
83674
|
onError(error instanceof Error ? error.message : `Failed to refresh ${section}`);
|
|
83135
83675
|
}
|
|
83136
83676
|
},
|
|
83137
|
-
[companyId, enabled, lineIds
|
|
83677
|
+
[companyId, comparisonStrategy, enabled, endKey, lineIds, startKey, supabase, trendMode]
|
|
83138
83678
|
);
|
|
83139
83679
|
const refreshSnapshot = React144__namespace.default.useCallback(
|
|
83140
83680
|
async (reason) => {
|
|
@@ -84736,6 +85276,9 @@ exports.RegistryProvider = RegistryProvider;
|
|
|
84736
85276
|
exports.RoleBadge = RoleBadge;
|
|
84737
85277
|
exports.S3ClipsService = S3ClipsSupabaseService;
|
|
84738
85278
|
exports.S3Service = S3Service;
|
|
85279
|
+
exports.SENTRY_HANDLED_EVENT_SESSION_LIMIT = SENTRY_HANDLED_EVENT_SESSION_LIMIT;
|
|
85280
|
+
exports.SENTRY_HANDLED_EVENT_WINDOW_MS = SENTRY_HANDLED_EVENT_WINDOW_MS;
|
|
85281
|
+
exports.SENTRY_QUOTA_STORAGE_KEY = SENTRY_QUOTA_STORAGE_KEY;
|
|
84739
85282
|
exports.SKUManagementView = SKUManagementView;
|
|
84740
85283
|
exports.SOPComplianceChart = SOPComplianceChart;
|
|
84741
85284
|
exports.SSEChatClient = SSEChatClient;
|
|
@@ -84812,6 +85355,7 @@ exports.WorkspacePdfExportButton = WorkspacePdfExportButton;
|
|
|
84812
85355
|
exports.WorkspacePdfGenerator = WorkspacePdfGenerator;
|
|
84813
85356
|
exports.WorkspaceWhatsAppShareButton = WorkspaceWhatsAppShareButton;
|
|
84814
85357
|
exports.actionService = actionService;
|
|
85358
|
+
exports.addSentryBreadcrumb = addSentryBreadcrumb;
|
|
84815
85359
|
exports.aggregateKPIsFromLineMetricsRows = aggregateKPIsFromLineMetricsRows;
|
|
84816
85360
|
exports.alertsService = alertsService;
|
|
84817
85361
|
exports.apiUtils = apiUtils;
|
|
@@ -84997,6 +85541,7 @@ exports.realtimeService = realtimeService;
|
|
|
84997
85541
|
exports.refreshWorkspaceDisplayNames = refreshWorkspaceDisplayNames;
|
|
84998
85542
|
exports.resetCoreMixpanel = resetCoreMixpanel;
|
|
84999
85543
|
exports.resetFailedUrl = resetFailedUrl;
|
|
85544
|
+
exports.resetSentryQuotaForTests = resetSentryQuotaForTests;
|
|
85000
85545
|
exports.resetSubscriptionManager = resetSubscriptionManager;
|
|
85001
85546
|
exports.resolveDefaultSkuId = resolveDefaultSkuId;
|
|
85002
85547
|
exports.resolveLiveSkuId = resolveLiveSkuId;
|