@optifye/dashboard-core 6.12.51 → 6.12.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6067,7 +6067,8 @@ var getAuthToken2 = async () => {
6067
6067
  var workspaceService = {
6068
6068
  // Cache for workspace display names to avoid repeated API calls
6069
6069
  _workspaceDisplayNamesCache: /* @__PURE__ */ new Map(),
6070
- _cacheTimestamp: 0,
6070
+ _workspaceDisplayNamesInFlight: /* @__PURE__ */ new Map(),
6071
+ _workspaceDisplayNamesByLineInFlight: /* @__PURE__ */ new Map(),
6071
6072
  _cacheExpiryMs: 5 * 60 * 1e3,
6072
6073
  // 5 minutes cache
6073
6074
  // Cache for workspace lists to avoid repeated API calls (line configuration changes infrequently)
@@ -6326,12 +6327,74 @@ var workspaceService = {
6326
6327
  * Returns a map of workspace_id -> display_name
6327
6328
  */
6328
6329
  async getWorkspaceDisplayNames(companyId, lineId) {
6329
- try {
6330
+ const cacheKey = `${companyId || "all"}::${lineId || "all"}`;
6331
+ const existingInFlight = this._workspaceDisplayNamesInFlight.get(cacheKey);
6332
+ if (existingInFlight) {
6333
+ return existingInFlight;
6334
+ }
6335
+ const fetchPromise = (async () => {
6336
+ try {
6337
+ const token = await getAuthToken2();
6338
+ const apiUrl = getBackendUrl2();
6339
+ const params = new URLSearchParams();
6340
+ if (companyId) params.append("company_id", companyId);
6341
+ if (lineId) params.append("line_id", lineId);
6342
+ const response = await fetch(`${apiUrl}/api/workspaces/display-names?${params.toString()}`, {
6343
+ headers: {
6344
+ "Authorization": `Bearer ${token}`,
6345
+ "Content-Type": "application/json"
6346
+ }
6347
+ });
6348
+ if (!response.ok) {
6349
+ const errorText = await response.text();
6350
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
6351
+ }
6352
+ const data = await response.json();
6353
+ const displayNamesMap = /* @__PURE__ */ new Map();
6354
+ if (data.display_names) {
6355
+ Object.entries(data.display_names).forEach(([key, value]) => {
6356
+ displayNamesMap.set(key, value);
6357
+ });
6358
+ }
6359
+ this._workspaceDisplayNamesCache.set(cacheKey, {
6360
+ displayNames: displayNamesMap,
6361
+ timestamp: Date.now()
6362
+ });
6363
+ return displayNamesMap;
6364
+ } catch (error) {
6365
+ console.error("Error fetching workspace display names:", error);
6366
+ addSentryBreadcrumb("Workspace display-name fallback failed", {
6367
+ surface: "workspace_display_names",
6368
+ route: "/api/workspaces/display-names",
6369
+ severity: "warning",
6370
+ extras: {
6371
+ company_id: companyId || null,
6372
+ line_id: lineId || null,
6373
+ pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
6374
+ error_message: error instanceof Error ? error.message : String(error)
6375
+ }
6376
+ });
6377
+ throw error;
6378
+ } finally {
6379
+ this._workspaceDisplayNamesInFlight.delete(cacheKey);
6380
+ }
6381
+ })();
6382
+ this._workspaceDisplayNamesInFlight.set(cacheKey, fetchPromise);
6383
+ return fetchPromise;
6384
+ },
6385
+ async getWorkspaceDisplayNamesByLine(companyId, lineIds) {
6386
+ const normalizedLineIds = Array.from(new Set((lineIds || []).filter(Boolean))).sort();
6387
+ const cacheKey = `${companyId || "all"}::byLine::${normalizedLineIds.join(",") || "all"}`;
6388
+ const existingInFlight = this._workspaceDisplayNamesByLineInFlight.get(cacheKey);
6389
+ if (existingInFlight) {
6390
+ return existingInFlight;
6391
+ }
6392
+ const fetchPromise = (async () => {
6330
6393
  const token = await getAuthToken2();
6331
6394
  const apiUrl = getBackendUrl2();
6332
6395
  const params = new URLSearchParams();
6333
6396
  if (companyId) params.append("company_id", companyId);
6334
- if (lineId) params.append("line_id", lineId);
6397
+ if (normalizedLineIds.length > 0) params.append("line_ids", normalizedLineIds.join(","));
6335
6398
  const response = await fetch(`${apiUrl}/api/workspaces/display-names?${params.toString()}`, {
6336
6399
  headers: {
6337
6400
  "Authorization": `Bearer ${token}`,
@@ -6343,39 +6406,32 @@ var workspaceService = {
6343
6406
  throw new Error(`Backend API error (${response.status}): ${errorText}`);
6344
6407
  }
6345
6408
  const data = await response.json();
6346
- const displayNamesMap = /* @__PURE__ */ new Map();
6347
- if (data.display_names) {
6348
- Object.entries(data.display_names).forEach(([key, value]) => {
6349
- displayNamesMap.set(key, value);
6409
+ const source = data.display_names_by_line || {};
6410
+ const byLine = /* @__PURE__ */ new Map();
6411
+ Object.entries(source).forEach(([lineId, displayNames]) => {
6412
+ if (normalizedLineIds.length > 0 && !normalizedLineIds.includes(lineId)) return;
6413
+ const lineMap = /* @__PURE__ */ new Map();
6414
+ Object.entries(displayNames).forEach(([workspaceId, displayName]) => {
6415
+ lineMap.set(workspaceId, displayName);
6350
6416
  });
6351
- }
6352
- this._workspaceDisplayNamesCache = displayNamesMap;
6353
- this._cacheTimestamp = Date.now();
6354
- return displayNamesMap;
6355
- } catch (error) {
6356
- console.error("Error fetching workspace display names:", error);
6357
- addSentryBreadcrumb("Workspace display-name fallback failed", {
6358
- surface: "workspace_display_names",
6359
- route: "/api/workspaces/display-names",
6360
- severity: "warning",
6361
- extras: {
6362
- company_id: companyId || null,
6363
- line_id: lineId || null,
6364
- pathname: typeof window !== "undefined" ? window.location.pathname : "unknown",
6365
- error_message: error instanceof Error ? error.message : String(error)
6366
- }
6417
+ byLine.set(lineId, lineMap);
6367
6418
  });
6368
- throw error;
6369
- }
6419
+ return byLine;
6420
+ })().finally(() => {
6421
+ this._workspaceDisplayNamesByLineInFlight.delete(cacheKey);
6422
+ });
6423
+ this._workspaceDisplayNamesByLineInFlight.set(cacheKey, fetchPromise);
6424
+ return fetchPromise;
6370
6425
  },
6371
6426
  /**
6372
6427
  * Gets cached workspace display names (with cache expiry)
6373
6428
  */
6374
6429
  async getCachedWorkspaceDisplayNames(companyId, lineId) {
6375
6430
  const now4 = Date.now();
6376
- const cacheAge = now4 - this._cacheTimestamp;
6377
- if (cacheAge < this._cacheExpiryMs && this._workspaceDisplayNamesCache.size > 0) {
6378
- return this._workspaceDisplayNamesCache;
6431
+ const cacheKey = `${companyId || "all"}::${lineId || "all"}`;
6432
+ const cached = this._workspaceDisplayNamesCache.get(cacheKey);
6433
+ if (cached && now4 - cached.timestamp < this._cacheExpiryMs && cached.displayNames.size > 0) {
6434
+ return cached.displayNames;
6379
6435
  }
6380
6436
  return this.getWorkspaceDisplayNames(companyId, lineId);
6381
6437
  },
@@ -6396,7 +6452,8 @@ var workspaceService = {
6396
6452
  */
6397
6453
  clearWorkspaceDisplayNamesCache() {
6398
6454
  this._workspaceDisplayNamesCache.clear();
6399
- this._cacheTimestamp = 0;
6455
+ this._workspaceDisplayNamesInFlight.clear();
6456
+ this._workspaceDisplayNamesByLineInFlight.clear();
6400
6457
  },
6401
6458
  clearWorkspacesCache() {
6402
6459
  this._workspacesCache.clear();
@@ -6902,10 +6959,16 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
6902
6959
  hasBackendUrl() {
6903
6960
  return Boolean(process.env.NEXT_PUBLIC_BACKEND_URL);
6904
6961
  }
6962
+ isCanonicalDate(value) {
6963
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
6964
+ }
6905
6965
  async fetchBackendWorkspaceUptimeSummaries(companyId, lineIds, date, shiftId, timezone) {
6906
6966
  if (!this.hasBackendUrl()) {
6907
6967
  return null;
6908
6968
  }
6969
+ if (!this.isCanonicalDate(date)) {
6970
+ return null;
6971
+ }
6909
6972
  const supabase = _getSupabaseInstance();
6910
6973
  if (!supabase) throw new Error("Supabase client not initialized");
6911
6974
  const params = new URLSearchParams({
@@ -6955,6 +7018,9 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
6955
7018
  if (!this.hasBackendUrl()) {
6956
7019
  return null;
6957
7020
  }
7021
+ if (!this.isCanonicalDate(date)) {
7022
+ return null;
7023
+ }
6958
7024
  const supabase = _getSupabaseInstance();
6959
7025
  if (!supabase) throw new Error("Supabase client not initialized");
6960
7026
  const params = new URLSearchParams({
@@ -7689,6 +7755,24 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
7689
7755
  const currentTiming = this.getShiftTiming(timezone, shiftConfig);
7690
7756
  const queryDate = overrideDate ?? currentTiming.date;
7691
7757
  const queryShiftId = overrideShiftId ?? currentTiming.shiftId;
7758
+ if (!this.isCanonicalDate(queryDate)) {
7759
+ return {
7760
+ shiftId: queryShiftId,
7761
+ shiftLabel: currentTiming.shiftLabel,
7762
+ shiftStart: dateFnsTz.formatInTimeZone(currentTiming.shiftStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
7763
+ shiftEnd: dateFnsTz.formatInTimeZone(currentTiming.shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
7764
+ totalMinutes: currentTiming.totalMinutes,
7765
+ completedMinutes: currentTiming.completedMinutes,
7766
+ uptimeMinutes: 0,
7767
+ downtimeMinutes: 0,
7768
+ pendingMinutes: currentTiming.pendingMinutes,
7769
+ uptimePercentage: 0,
7770
+ hasData: false,
7771
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7772
+ points: [],
7773
+ downtimeSegments: []
7774
+ };
7775
+ }
7692
7776
  if (lineId) {
7693
7777
  const backendTimeline = await this.fetchBackendWorkspaceUptimeTimeline(
7694
7778
  workspaceId,
@@ -7911,6 +7995,9 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
7911
7995
  const defaultTimezone = dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
7912
7996
  const shiftConfig = passedShiftConfig || dashboardConfig?.shiftConfig;
7913
7997
  const effectiveTimezone = timezone || shiftConfig?.timezone || defaultTimezone;
7998
+ if (overrideDate !== void 0 && !this.isCanonicalDate(overrideDate)) {
7999
+ return /* @__PURE__ */ new Map();
8000
+ }
7914
8001
  if (lineShiftConfigs && lineShiftConfigs.size > 0) {
7915
8002
  return this.calculateWorkspaceUptimeMultiLine(
7916
8003
  companyId,
@@ -12003,85 +12090,290 @@ function formatReasonLabel(reason) {
12003
12090
  return reason.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
12004
12091
  }
12005
12092
 
12093
+ // src/lib/services/sessionActivityAccumulator.ts
12094
+ var TRACKED_ACTIVITY_EVENTS = ["mousemove", "pointermove", "keydown", "click", "scroll", "wheel", "touchstart"];
12095
+ function createSessionData(sessionId, userId, companyId, now4, isVisible, isFocused) {
12096
+ return {
12097
+ sessionId,
12098
+ userId,
12099
+ companyId,
12100
+ startedAt: now4,
12101
+ lastTick: now4,
12102
+ cumulativeTotalMs: 0,
12103
+ cumulativeActiveMs: 0,
12104
+ cumulativePassiveMs: 0,
12105
+ lastActivityAt: null,
12106
+ isUserActive: false,
12107
+ isVisible,
12108
+ isFocused,
12109
+ state: "ACTIVE",
12110
+ currentSegment: null,
12111
+ pendingSegments: [],
12112
+ segmentCounter: 0
12113
+ };
12114
+ }
12115
+ var SessionActivityAccumulator = class {
12116
+ constructor(idleThresholdMs, getDashboardView) {
12117
+ this.idleThresholdMs = idleThresholdMs;
12118
+ this.getDashboardView = getDashboardView;
12119
+ }
12120
+ shouldTrackTime(session) {
12121
+ if (!session) return false;
12122
+ return session.state === "ACTIVE" && session.isVisible && session.isFocused;
12123
+ }
12124
+ getActivityState(session, now4) {
12125
+ if (!session.lastActivityAt) {
12126
+ return "passive";
12127
+ }
12128
+ const timeSinceActivity = now4.getTime() - session.lastActivityAt.getTime();
12129
+ return timeSinceActivity <= this.idleThresholdMs ? "active" : "passive";
12130
+ }
12131
+ updateTimes(session, now4 = /* @__PURE__ */ new Date()) {
12132
+ if (!session || session.state === "ENDED") return;
12133
+ const elapsed = now4.getTime() - session.lastTick.getTime();
12134
+ if (elapsed <= 0) return;
12135
+ if (!this.shouldTrackTime(session)) {
12136
+ this.flushCurrentSegment(session, now4);
12137
+ session.lastTick = now4;
12138
+ return;
12139
+ }
12140
+ let cursor = new Date(session.lastTick);
12141
+ const idleCutoff = session.lastActivityAt ? new Date(session.lastActivityAt.getTime() + this.idleThresholdMs) : null;
12142
+ if (idleCutoff && cursor < idleCutoff && now4 > idleCutoff) {
12143
+ this.addTrackedDuration(session, "active", cursor, idleCutoff);
12144
+ cursor = idleCutoff;
12145
+ }
12146
+ this.addTrackedDuration(session, this.getActivityState(session, now4), cursor, now4);
12147
+ session.lastTick = now4;
12148
+ }
12149
+ flushCurrentSegment(session, endAt) {
12150
+ if (!session?.currentSegment) return;
12151
+ const current = session.currentSegment;
12152
+ const currentEndMs = new Date(current.segment_end_at).getTime();
12153
+ if (endAt.getTime() > currentEndMs) {
12154
+ current.duration_ms += endAt.getTime() - currentEndMs;
12155
+ current.segment_end_at = endAt.toISOString();
12156
+ }
12157
+ if (current.duration_ms > 0) {
12158
+ session.pendingSegments.push(current);
12159
+ }
12160
+ session.currentSegment = null;
12161
+ }
12162
+ addTrackedDuration(session, state, start, end) {
12163
+ const durationMs = end.getTime() - start.getTime();
12164
+ if (durationMs <= 0) return;
12165
+ session.cumulativeTotalMs += durationMs;
12166
+ if (state === "active") {
12167
+ session.cumulativeActiveMs += durationMs;
12168
+ session.isUserActive = true;
12169
+ } else {
12170
+ session.cumulativePassiveMs += durationMs;
12171
+ session.isUserActive = false;
12172
+ }
12173
+ this.addSegmentDuration(session, state, start, end);
12174
+ }
12175
+ addSegmentDuration(session, state, start, end) {
12176
+ const durationMs = end.getTime() - start.getTime();
12177
+ if (durationMs <= 0) return;
12178
+ const dashboardView = this.getDashboardView();
12179
+ const current = session.currentSegment;
12180
+ if (current && current.state === state && current.dashboard_view === dashboardView && current.segment_end_at === start.toISOString()) {
12181
+ current.segment_end_at = end.toISOString();
12182
+ current.duration_ms += durationMs;
12183
+ return;
12184
+ }
12185
+ this.flushCurrentSegment(session, start);
12186
+ session.segmentCounter += 1;
12187
+ session.currentSegment = {
12188
+ client_segment_id: `${session.sessionId}_seg_${session.segmentCounter}`,
12189
+ segment_start_at: start.toISOString(),
12190
+ segment_end_at: end.toISOString(),
12191
+ state,
12192
+ duration_ms: durationMs,
12193
+ dashboard_view: dashboardView,
12194
+ tracker_version: 2
12195
+ };
12196
+ }
12197
+ };
12198
+
12199
+ // src/lib/services/sessionTrackerTransport.ts
12200
+ var SessionTrackerTransport = class {
12201
+ constructor(config, trackerVersion, maxSegmentsPerRequest) {
12202
+ this.config = config;
12203
+ this.trackerVersion = trackerVersion;
12204
+ this.maxSegmentsPerRequest = maxSegmentsPerRequest;
12205
+ }
12206
+ async sendStartSession(session) {
12207
+ const token = await this.config.getAccessToken();
12208
+ if (!token) {
12209
+ console.warn("[SessionTracker] No access token, skipping start session API call");
12210
+ return;
12211
+ }
12212
+ await fetch(`${this.config.apiBaseUrl}/api/sessions/start`, {
12213
+ method: "POST",
12214
+ headers: this.authHeaders(token),
12215
+ body: JSON.stringify({
12216
+ session_id: session.sessionId,
12217
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
12218
+ dashboard_view: this.getDashboardView(),
12219
+ tracker_version: this.trackerVersion
12220
+ })
12221
+ });
12222
+ }
12223
+ async sendHeartbeat(session) {
12224
+ const token = await this.config.getAccessToken();
12225
+ if (!token) return null;
12226
+ const segmentsToSend = session.pendingSegments.slice(0, this.maxSegmentsPerRequest);
12227
+ const sentSegmentIds = new Set(segmentsToSend.map((segment) => segment.client_segment_id));
12228
+ const response = await fetch(`${this.config.apiBaseUrl}/api/sessions/heartbeat`, {
12229
+ method: "POST",
12230
+ headers: this.authHeaders(token),
12231
+ body: JSON.stringify({
12232
+ session_id: session.sessionId,
12233
+ cumulative_total_ms: session.cumulativeTotalMs,
12234
+ cumulative_active_ms: session.cumulativeActiveMs,
12235
+ cumulative_passive_ms: session.cumulativePassiveMs,
12236
+ visibility_state: typeof document !== "undefined" ? document.visibilityState : "visible",
12237
+ is_user_active: session.isUserActive,
12238
+ dashboard_view: this.getDashboardView(),
12239
+ tracker_version: this.trackerVersion,
12240
+ activity_segments: segmentsToSend
12241
+ })
12242
+ });
12243
+ const payload = await response.json().catch(() => ({}));
12244
+ return {
12245
+ ok: response.ok,
12246
+ acknowledged: payload?.acknowledged === false ? false : payload?.acknowledged === true ? true : null,
12247
+ sentSegmentIds
12248
+ };
12249
+ }
12250
+ async sendEndSession(session, reason, keepalive) {
12251
+ const token = keepalive && this.config.getCachedAccessToken ? this.config.getCachedAccessToken() : await this.config.getAccessToken();
12252
+ if (!token) {
12253
+ console.warn("[SessionTracker] No access token, skipping end session API call");
12254
+ return false;
12255
+ }
12256
+ const response = await fetch(`${this.config.apiBaseUrl}/api/sessions/end`, {
12257
+ method: "POST",
12258
+ keepalive,
12259
+ headers: this.authHeaders(token),
12260
+ body: JSON.stringify({
12261
+ session_id: session.sessionId,
12262
+ end_reason: reason,
12263
+ final_total_ms: session.cumulativeTotalMs,
12264
+ final_active_ms: session.cumulativeActiveMs,
12265
+ final_passive_ms: session.cumulativePassiveMs,
12266
+ tracker_version: this.trackerVersion,
12267
+ activity_segments: session.pendingSegments.slice(0, this.maxSegmentsPerRequest)
12268
+ })
12269
+ });
12270
+ return response.ok;
12271
+ }
12272
+ authHeaders(token) {
12273
+ return {
12274
+ "Content-Type": "application/json",
12275
+ "Authorization": `Bearer ${token}`
12276
+ };
12277
+ }
12278
+ getDashboardView() {
12279
+ return typeof window !== "undefined" ? window.location.pathname : void 0;
12280
+ }
12281
+ };
12282
+
12006
12283
  // src/lib/services/sessionTracker.ts
12007
12284
  function generateSessionId() {
12008
12285
  return `sess_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
12009
12286
  }
12287
+ function isDebugEnabled() {
12288
+ return typeof process !== "undefined" && process.env.NEXT_PUBLIC_DEBUG_DASHBOARD === "true";
12289
+ }
12290
+ function debugLog(message, data) {
12291
+ if (isDebugEnabled()) {
12292
+ console.log(`[SessionTracker] ${message}`, data || {});
12293
+ }
12294
+ }
12010
12295
  var SessionTracker = class {
12011
12296
  constructor(config) {
12012
12297
  this.session = null;
12013
12298
  this.heartbeatInterval = null;
12014
- this.graceTimeout = null;
12015
12299
  this.tickInterval = null;
12016
- // Constants
12300
+ this.heartbeatInFlight = false;
12301
+ this.pendingForcedHeartbeat = false;
12302
+ this.TRACKER_VERSION = 2;
12017
12303
  this.HEARTBEAT_INTERVAL_MS = 3e4;
12018
- // 30 seconds
12019
- this.HIDDEN_GRACE_PERIOD_MS = 3e5;
12020
- // 5 minutes
12021
12304
  this.IDLE_THRESHOLD_MS = 3e4;
12022
- // 30 seconds without activity = passive
12023
12305
  this.TICK_INTERVAL_MS = 1e3;
12306
+ this.MAX_SEGMENTS_PER_REQUEST = 200;
12024
12307
  this.config = config;
12308
+ this.activityAccumulator = new SessionActivityAccumulator(
12309
+ this.IDLE_THRESHOLD_MS,
12310
+ () => this.getDashboardView()
12311
+ );
12312
+ this.transport = new SessionTrackerTransport(
12313
+ this.config,
12314
+ this.TRACKER_VERSION,
12315
+ this.MAX_SEGMENTS_PER_REQUEST
12316
+ );
12025
12317
  this.boundHandleActivity = this.handleUserActivity.bind(this);
12026
12318
  this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
12027
12319
  this.boundHandleBeforeUnload = this.handleBeforeUnload.bind(this);
12320
+ this.boundHandlePageHide = this.handlePageHide.bind(this);
12321
+ this.boundHandlePageShow = this.handlePageShow.bind(this);
12028
12322
  this.boundHandleFocus = this.handleWindowFocus.bind(this);
12029
12323
  this.boundHandleBlur = this.handleWindowBlur.bind(this);
12030
12324
  }
12031
- /**
12032
- * Start a new session
12033
- */
12034
12325
  async startSession(userId, companyId) {
12035
12326
  if (this.session && this.session.state !== "ENDED") {
12036
- console.log("[SessionTracker] Session already active, ignoring start");
12327
+ debugLog("Session already active, ignoring start", { sessionId: this.session.sessionId });
12037
12328
  return;
12038
12329
  }
12039
12330
  const sessionId = generateSessionId();
12040
12331
  const now4 = /* @__PURE__ */ new Date();
12041
- this.session = {
12332
+ this.session = createSessionData(
12042
12333
  sessionId,
12043
12334
  userId,
12044
12335
  companyId,
12045
- startedAt: now4,
12046
- lastTick: now4,
12047
- cumulativeTotalMs: 0,
12048
- cumulativeActiveMs: 0,
12049
- cumulativePassiveMs: 0,
12050
- lastActivityAt: now4,
12051
- isUserActive: true,
12052
- isVisible: typeof document !== "undefined" ? document.visibilityState === "visible" : true,
12053
- isFocused: typeof document !== "undefined" && typeof document.hasFocus === "function" ? document.hasFocus() : true,
12054
- state: "ACTIVE",
12055
- hiddenSince: null
12056
- };
12336
+ now4,
12337
+ this.getDocumentVisibility(),
12338
+ this.getWindowFocus()
12339
+ );
12340
+ await this.sendStartSession();
12341
+ if (!this.session || this.session.sessionId !== sessionId) {
12342
+ return;
12343
+ }
12344
+ if (this.session.state === "ENDED") {
12345
+ await this.sendEndSession(this.session.endReason || "navigation");
12346
+ debugLog("Session start completed after local end", { sessionId });
12347
+ return;
12348
+ }
12349
+ const trackingStart = /* @__PURE__ */ new Date();
12350
+ this.session.lastTick = trackingStart;
12351
+ this.session.isVisible = this.getDocumentVisibility();
12352
+ this.session.isFocused = this.getWindowFocus();
12057
12353
  this.setupActivityListeners();
12058
12354
  this.setupVisibilityListener();
12059
12355
  this.setupFocusListeners();
12060
- this.setupBeforeUnloadListener();
12356
+ this.setupPageLifecycleListeners();
12061
12357
  this.startTimeTick();
12062
12358
  this.startHeartbeat();
12063
- await this.sendStartSession();
12064
12359
  this.config.onSessionStart?.(sessionId);
12065
- console.log("[SessionTracker] Session started:", sessionId);
12360
+ debugLog("Session started", { sessionId });
12066
12361
  }
12067
- /**
12068
- * End the current session
12069
- */
12070
12362
  async endSession(reason) {
12071
12363
  if (!this.session || this.session.state === "ENDED") {
12072
12364
  return;
12073
12365
  }
12074
12366
  const sessionId = this.session.sessionId;
12075
12367
  this.updateTimes();
12368
+ this.flushCurrentSegment(/* @__PURE__ */ new Date());
12369
+ this.session.endReason = reason;
12370
+ await this.drainPendingSegmentsBeforeEnd();
12076
12371
  this.session.state = "ENDED";
12077
12372
  this.cleanup();
12078
12373
  await this.sendEndSession(reason);
12079
12374
  this.config.onSessionEnd?.(sessionId, reason);
12080
- console.log("[SessionTracker] Session ended:", sessionId, "reason:", reason);
12375
+ debugLog("Session ended", { sessionId, reason });
12081
12376
  }
12082
- /**
12083
- * Get current session stats (for debugging/display)
12084
- */
12085
12377
  getSessionStats() {
12086
12378
  if (!this.session) return null;
12087
12379
  return {
@@ -12090,11 +12382,9 @@ var SessionTracker = class {
12090
12382
  passiveMs: this.session.cumulativePassiveMs
12091
12383
  };
12092
12384
  }
12093
- // ===== Private Methods =====
12094
12385
  setupActivityListeners() {
12095
12386
  if (typeof window === "undefined") return;
12096
- const events = ["mousemove", "keydown", "click", "scroll", "touchstart"];
12097
- events.forEach((event) => {
12387
+ TRACKED_ACTIVITY_EVENTS.forEach((event) => {
12098
12388
  window.addEventListener(event, this.boundHandleActivity, { passive: true });
12099
12389
  });
12100
12390
  }
@@ -12107,71 +12397,100 @@ var SessionTracker = class {
12107
12397
  window.addEventListener("focus", this.boundHandleFocus);
12108
12398
  window.addEventListener("blur", this.boundHandleBlur);
12109
12399
  }
12110
- setupBeforeUnloadListener() {
12400
+ setupPageLifecycleListeners() {
12111
12401
  if (typeof window === "undefined") return;
12112
12402
  window.addEventListener("beforeunload", this.boundHandleBeforeUnload);
12403
+ window.addEventListener("pagehide", this.boundHandlePageHide);
12404
+ window.addEventListener("pageshow", this.boundHandlePageShow);
12113
12405
  }
12114
- handleUserActivity() {
12406
+ handleUserActivity(event) {
12115
12407
  if (!this.session || this.session.state === "ENDED") return;
12408
+ if ("isTrusted" in event && event.isTrusted === false) return;
12409
+ this.updateTimes();
12116
12410
  this.session.lastActivityAt = /* @__PURE__ */ new Date();
12117
12411
  this.session.isUserActive = true;
12118
12412
  }
12119
12413
  handleVisibilityChange() {
12120
12414
  if (!this.session || this.session.state === "ENDED") return;
12415
+ const now4 = /* @__PURE__ */ new Date();
12416
+ this.updateTimes(now4);
12121
12417
  if (document.visibilityState === "hidden") {
12122
- this.session.hiddenSince = /* @__PURE__ */ new Date();
12123
- this.session.state = "BACKGROUND_GRACE";
12418
+ this.flushCurrentSegment(now4);
12124
12419
  this.session.isVisible = false;
12125
12420
  this.session.isFocused = false;
12126
- this.session.lastTick = /* @__PURE__ */ new Date();
12127
- this.graceTimeout = setTimeout(() => {
12128
- this.endSession("hidden_timeout");
12129
- }, this.HIDDEN_GRACE_PERIOD_MS);
12130
- console.log("[SessionTracker] Tab hidden, starting grace period");
12131
- } else {
12132
- if (this.graceTimeout) {
12133
- clearTimeout(this.graceTimeout);
12134
- this.graceTimeout = null;
12135
- }
12136
- this.session.hiddenSince = null;
12137
- this.session.state = "ACTIVE";
12138
- this.session.isVisible = true;
12139
- this.session.isFocused = typeof document !== "undefined" && typeof document.hasFocus === "function" ? document.hasFocus() : true;
12140
- this.session.lastActivityAt = /* @__PURE__ */ new Date();
12141
- this.session.isUserActive = true;
12142
- this.session.lastTick = /* @__PURE__ */ new Date();
12143
- console.log("[SessionTracker] Tab visible again");
12421
+ this.session.isUserActive = false;
12422
+ this.session.lastActivityAt = null;
12423
+ this.session.lastTick = now4;
12424
+ void this.sendHeartbeat(true);
12425
+ debugLog("Tab hidden");
12426
+ return;
12144
12427
  }
12428
+ this.session.isVisible = true;
12429
+ this.session.isFocused = this.getWindowFocus();
12430
+ this.session.isUserActive = false;
12431
+ this.session.lastActivityAt = null;
12432
+ this.session.lastTick = now4;
12433
+ void this.sendHeartbeat(true);
12434
+ debugLog("Tab visible");
12145
12435
  }
12146
12436
  handleWindowFocus() {
12147
12437
  if (!this.session || this.session.state === "ENDED") return;
12438
+ const now4 = /* @__PURE__ */ new Date();
12439
+ this.updateTimes(now4);
12148
12440
  this.session.isFocused = true;
12149
- this.session.lastActivityAt = /* @__PURE__ */ new Date();
12150
- this.session.isUserActive = true;
12151
- this.session.lastTick = /* @__PURE__ */ new Date();
12441
+ this.session.isUserActive = false;
12442
+ this.session.lastActivityAt = null;
12443
+ this.session.lastTick = now4;
12444
+ void this.sendHeartbeat(true);
12152
12445
  }
12153
12446
  handleWindowBlur() {
12154
12447
  if (!this.session || this.session.state === "ENDED") return;
12448
+ const now4 = /* @__PURE__ */ new Date();
12449
+ this.updateTimes(now4);
12450
+ this.flushCurrentSegment(now4);
12155
12451
  this.session.isFocused = false;
12156
- this.session.lastTick = /* @__PURE__ */ new Date();
12452
+ this.session.isUserActive = false;
12453
+ this.session.lastActivityAt = null;
12454
+ this.session.lastTick = now4;
12455
+ void this.sendHeartbeat(true);
12157
12456
  }
12158
- handleBeforeUnload(_e) {
12159
- if (this.session && this.session.state !== "ENDED") {
12160
- this.updateTimes();
12161
- const data = {
12162
- session_id: this.session.sessionId,
12163
- end_reason: "tab_close",
12164
- final_total_ms: this.session.cumulativeTotalMs,
12165
- final_active_ms: this.session.cumulativeActiveMs,
12166
- final_passive_ms: this.session.cumulativePassiveMs
12167
- };
12168
- if (navigator.sendBeacon) {
12169
- const blob = new Blob([JSON.stringify(data)], { type: "application/json" });
12170
- navigator.sendBeacon(`${this.config.apiBaseUrl}/api/sessions/end`, blob);
12171
- }
12172
- this.session.state = "ENDED";
12173
- this.cleanup();
12457
+ handleBeforeUnload() {
12458
+ this.finalizeWithKeepalive("tab_close");
12459
+ }
12460
+ handlePageHide(event) {
12461
+ if (event.persisted) {
12462
+ if (!this.session || this.session.state === "ENDED") return;
12463
+ const now4 = /* @__PURE__ */ new Date();
12464
+ this.updateTimes(now4);
12465
+ this.flushCurrentSegment(now4);
12466
+ this.session.isVisible = false;
12467
+ this.session.isFocused = false;
12468
+ this.session.isUserActive = false;
12469
+ this.session.lastActivityAt = null;
12470
+ this.session.lastTick = now4;
12471
+ void this.sendHeartbeat(true);
12472
+ return;
12174
12473
  }
12474
+ this.finalizeWithKeepalive("pagehide");
12475
+ }
12476
+ handlePageShow(event) {
12477
+ if (!event.persisted || !this.session || this.session.state === "ENDED") return;
12478
+ const now4 = /* @__PURE__ */ new Date();
12479
+ this.session.isVisible = this.getDocumentVisibility();
12480
+ this.session.isFocused = this.getWindowFocus();
12481
+ this.session.isUserActive = false;
12482
+ this.session.lastActivityAt = null;
12483
+ this.session.lastTick = now4;
12484
+ void this.sendHeartbeat(true);
12485
+ }
12486
+ finalizeWithKeepalive(reason) {
12487
+ if (!this.session || this.session.state === "ENDED") return;
12488
+ this.updateTimes();
12489
+ this.flushCurrentSegment(/* @__PURE__ */ new Date());
12490
+ this.session.endReason = reason;
12491
+ this.session.state = "ENDED";
12492
+ void this.sendEndSession(reason, true);
12493
+ this.cleanup();
12175
12494
  }
12176
12495
  startTimeTick() {
12177
12496
  this.tickInterval = setInterval(() => {
@@ -12179,30 +12498,17 @@ var SessionTracker = class {
12179
12498
  }, this.TICK_INTERVAL_MS);
12180
12499
  }
12181
12500
  shouldTrackTime() {
12182
- if (!this.session) return false;
12183
- const stateAllowsTracking = this.session.state === "ACTIVE" || this.session.state === "BACKGROUND_GRACE";
12184
- return stateAllowsTracking && this.session.isVisible && this.session.isFocused;
12501
+ return this.activityAccumulator.shouldTrackTime(this.session);
12185
12502
  }
12186
- updateTimes() {
12187
- if (!this.session || this.session.state === "ENDED") return;
12188
- const now4 = /* @__PURE__ */ new Date();
12189
- const elapsed = now4.getTime() - this.session.lastTick.getTime();
12190
- if (!this.shouldTrackTime()) {
12191
- this.session.lastTick = now4;
12192
- return;
12193
- }
12194
- if (this.session.state === "ACTIVE" || this.session.state === "BACKGROUND_GRACE") {
12195
- this.session.cumulativeTotalMs += elapsed;
12196
- const timeSinceActivity = now4.getTime() - this.session.lastActivityAt.getTime();
12197
- if (timeSinceActivity < this.IDLE_THRESHOLD_MS) {
12198
- this.session.cumulativeActiveMs += elapsed;
12199
- this.session.isUserActive = true;
12200
- } else {
12201
- this.session.cumulativePassiveMs += elapsed;
12202
- this.session.isUserActive = false;
12203
- }
12204
- }
12205
- this.session.lastTick = now4;
12503
+ getActivityState(now4) {
12504
+ if (!this.session) return "passive";
12505
+ return this.activityAccumulator.getActivityState(this.session, now4);
12506
+ }
12507
+ updateTimes(now4 = /* @__PURE__ */ new Date()) {
12508
+ this.activityAccumulator.updateTimes(this.session, now4);
12509
+ }
12510
+ flushCurrentSegment(endAt) {
12511
+ this.activityAccumulator.flushCurrentSegment(this.session, endAt);
12206
12512
  }
12207
12513
  startHeartbeat() {
12208
12514
  this.heartbeatInterval = setInterval(async () => {
@@ -12212,75 +12518,70 @@ var SessionTracker = class {
12212
12518
  async sendStartSession() {
12213
12519
  if (!this.session) return;
12214
12520
  try {
12215
- const token = await this.config.getAccessToken();
12216
- if (!token) {
12217
- console.warn("[SessionTracker] No access token, skipping start session API call");
12218
- return;
12219
- }
12220
- await fetch(`${this.config.apiBaseUrl}/api/sessions/start`, {
12221
- method: "POST",
12222
- headers: {
12223
- "Content-Type": "application/json",
12224
- "Authorization": `Bearer ${token}`
12225
- },
12226
- body: JSON.stringify({
12227
- session_id: this.session.sessionId,
12228
- user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
12229
- dashboard_view: typeof window !== "undefined" ? window.location.pathname : void 0
12230
- })
12231
- });
12521
+ await this.transport.sendStartSession(this.session);
12232
12522
  } catch (error) {
12233
12523
  console.error("[SessionTracker] Failed to send start session:", error);
12234
12524
  }
12235
12525
  }
12236
- async sendHeartbeat() {
12526
+ async sendHeartbeat(force = false, allowReplacement = true) {
12527
+ if (this.heartbeatInFlight) {
12528
+ this.pendingForcedHeartbeat || (this.pendingForcedHeartbeat = force);
12529
+ return;
12530
+ }
12237
12531
  if (!this.session || this.session.state === "ENDED") return;
12532
+ this.heartbeatInFlight = true;
12238
12533
  try {
12239
- const token = await this.config.getAccessToken();
12240
- if (!token) return;
12241
- await fetch(`${this.config.apiBaseUrl}/api/sessions/heartbeat`, {
12242
- method: "POST",
12243
- headers: {
12244
- "Content-Type": "application/json",
12245
- "Authorization": `Bearer ${token}`
12246
- },
12247
- body: JSON.stringify({
12248
- session_id: this.session.sessionId,
12249
- cumulative_total_ms: this.session.cumulativeTotalMs,
12250
- cumulative_active_ms: this.session.cumulativeActiveMs,
12251
- cumulative_passive_ms: this.session.cumulativePassiveMs,
12252
- visibility_state: typeof document !== "undefined" ? document.visibilityState : "visible",
12253
- is_user_active: this.session.isUserActive,
12254
- dashboard_view: typeof window !== "undefined" ? window.location.pathname : void 0
12255
- })
12256
- });
12534
+ this.updateTimes();
12535
+ this.flushCurrentSegment(/* @__PURE__ */ new Date());
12536
+ if (!force && !this.shouldTrackTime()) return;
12537
+ const result = await this.transport.sendHeartbeat(this.session);
12538
+ if (!result) return;
12539
+ if (result.ok && result.acknowledged !== false) {
12540
+ debugLog("Heartbeat acknowledged", { segments: result.sentSegmentIds.size });
12541
+ this.session.pendingSegments = this.session.pendingSegments.filter(
12542
+ (segment) => !result.sentSegmentIds.has(segment.client_segment_id)
12543
+ );
12544
+ } else if (result.ok && result.acknowledged === false && allowReplacement && this.shouldTrackTime()) {
12545
+ const { userId, companyId, sessionId } = this.session;
12546
+ this.session.state = "ENDED";
12547
+ this.cleanup();
12548
+ debugLog("Heartbeat not acknowledged, starting replacement session", { sessionId });
12549
+ await this.startSession(userId, companyId);
12550
+ }
12257
12551
  } catch (error) {
12258
12552
  console.error("[SessionTracker] Failed to send heartbeat:", error);
12553
+ } finally {
12554
+ this.heartbeatInFlight = false;
12555
+ if (this.pendingForcedHeartbeat && this.session?.state !== "ENDED") {
12556
+ this.pendingForcedHeartbeat = false;
12557
+ void this.sendHeartbeat(true);
12558
+ }
12259
12559
  }
12260
12560
  }
12261
- async sendEndSession(reason) {
12561
+ async drainPendingSegmentsBeforeEnd() {
12562
+ if (!this.session) return;
12563
+ let guard = 0;
12564
+ while (this.session.state !== "ENDED" && this.session.pendingSegments.length > this.MAX_SEGMENTS_PER_REQUEST && guard < 20) {
12565
+ const pendingBefore = this.session.pendingSegments.length;
12566
+ await this.sendHeartbeat(true, false);
12567
+ if (!this.session || this.session.pendingSegments.length >= pendingBefore) {
12568
+ break;
12569
+ }
12570
+ guard += 1;
12571
+ }
12572
+ }
12573
+ async sendEndSession(reason, keepalive = false) {
12262
12574
  if (!this.session) return;
12263
12575
  try {
12264
- const token = await this.config.getAccessToken();
12265
- if (!token) {
12266
- console.warn("[SessionTracker] No access token, skipping end session API call");
12267
- return;
12576
+ const ok = await this.transport.sendEndSession(this.session, reason, keepalive);
12577
+ if (ok) {
12578
+ this.session.pendingSegments = this.session.pendingSegments.slice(this.MAX_SEGMENTS_PER_REQUEST);
12268
12579
  }
12269
- await fetch(`${this.config.apiBaseUrl}/api/sessions/end`, {
12270
- method: "POST",
12271
- headers: {
12272
- "Content-Type": "application/json",
12273
- "Authorization": `Bearer ${token}`
12274
- },
12275
- body: JSON.stringify({
12276
- session_id: this.session.sessionId,
12277
- end_reason: reason,
12278
- final_total_ms: this.session.cumulativeTotalMs,
12279
- final_active_ms: this.session.cumulativeActiveMs,
12280
- final_passive_ms: this.session.cumulativePassiveMs
12281
- })
12282
- });
12283
12580
  } catch (error) {
12581
+ if (keepalive) {
12582
+ debugLog("Keepalive end session failed after page lifecycle transition", { reason });
12583
+ return;
12584
+ }
12284
12585
  console.error("[SessionTracker] Failed to send end session:", error);
12285
12586
  }
12286
12587
  }
@@ -12293,23 +12594,29 @@ var SessionTracker = class {
12293
12594
  clearInterval(this.tickInterval);
12294
12595
  this.tickInterval = null;
12295
12596
  }
12296
- if (this.graceTimeout) {
12297
- clearTimeout(this.graceTimeout);
12298
- this.graceTimeout = null;
12299
- }
12300
12597
  if (typeof window !== "undefined") {
12301
- const events = ["mousemove", "keydown", "click", "scroll", "touchstart"];
12302
- events.forEach((event) => {
12598
+ TRACKED_ACTIVITY_EVENTS.forEach((event) => {
12303
12599
  window.removeEventListener(event, this.boundHandleActivity);
12304
12600
  });
12305
12601
  window.removeEventListener("focus", this.boundHandleFocus);
12306
12602
  window.removeEventListener("blur", this.boundHandleBlur);
12307
12603
  window.removeEventListener("beforeunload", this.boundHandleBeforeUnload);
12604
+ window.removeEventListener("pagehide", this.boundHandlePageHide);
12605
+ window.removeEventListener("pageshow", this.boundHandlePageShow);
12308
12606
  }
12309
12607
  if (typeof document !== "undefined") {
12310
12608
  document.removeEventListener("visibilitychange", this.boundHandleVisibility);
12311
12609
  }
12312
12610
  }
12611
+ getDashboardView() {
12612
+ return typeof window !== "undefined" ? window.location.pathname : void 0;
12613
+ }
12614
+ getDocumentVisibility() {
12615
+ return typeof document !== "undefined" ? document.visibilityState === "visible" : true;
12616
+ }
12617
+ getWindowFocus() {
12618
+ return typeof document !== "undefined" && typeof document.hasFocus === "function" ? document.hasFocus() : true;
12619
+ }
12313
12620
  };
12314
12621
  function createSessionTracker(config) {
12315
12622
  return new SessionTracker(config);
@@ -13315,15 +13622,43 @@ function useSessionTracking(options = {}) {
13315
13622
  const trackerRef = React125.useRef(null);
13316
13623
  const isTrackingRef = React125.useRef(false);
13317
13624
  const initRef = React125.useRef(false);
13318
- const getAccessToken2 = React125.useCallback(async () => {
13319
- return session?.access_token || null;
13625
+ const accessTokenRef = React125.useRef(null);
13626
+ const isAuthenticatedRef = React125.useRef(isAuthenticated);
13627
+ const onSessionStartRef = React125.useRef(onSessionStart);
13628
+ const onSessionEndRef = React125.useRef(onSessionEnd);
13629
+ const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
13630
+ isAuthenticatedRef.current = isAuthenticated;
13631
+ React125.useEffect(() => {
13632
+ if (session?.access_token) {
13633
+ accessTokenRef.current = session.access_token;
13634
+ }
13320
13635
  }, [session?.access_token]);
13636
+ React125.useEffect(() => {
13637
+ onSessionStartRef.current = onSessionStart;
13638
+ }, [onSessionStart]);
13639
+ React125.useEffect(() => {
13640
+ onSessionEndRef.current = onSessionEnd;
13641
+ }, [onSessionEnd]);
13642
+ const getAccessToken2 = React125.useCallback(async () => {
13643
+ return accessTokenRef.current;
13644
+ }, []);
13645
+ const endCurrentTracker = React125.useCallback((reason) => {
13646
+ const tracker = trackerRef.current;
13647
+ if (!tracker) return null;
13648
+ const tokenForEndingSession = accessTokenRef.current;
13649
+ trackerRef.current = null;
13650
+ isTrackingRef.current = false;
13651
+ return tracker.endSession(reason).finally(() => {
13652
+ if (reason === "logout" && !isAuthenticatedRef.current && accessTokenRef.current === tokenForEndingSession) {
13653
+ accessTokenRef.current = null;
13654
+ }
13655
+ });
13656
+ }, []);
13321
13657
  React125.useEffect(() => {
13322
13658
  if (!enabled || !isAuthenticated || !user || !session || initRef.current) {
13323
13659
  return;
13324
13660
  }
13325
13661
  initRef.current = true;
13326
- const apiBaseUrl2 = config?.apiBaseUrl || process.env.NEXT_PUBLIC_API_BASE_URL || "";
13327
13662
  if (!apiBaseUrl2) {
13328
13663
  console.warn("[useSessionTracking] No API base URL configured, session tracking disabled");
13329
13664
  return;
@@ -13331,41 +13666,34 @@ function useSessionTracking(options = {}) {
13331
13666
  const tracker = createSessionTracker({
13332
13667
  apiBaseUrl: apiBaseUrl2,
13333
13668
  getAccessToken: getAccessToken2,
13669
+ getCachedAccessToken: () => accessTokenRef.current,
13334
13670
  onSessionStart: (sessionId) => {
13335
13671
  isTrackingRef.current = true;
13336
- onSessionStart?.(sessionId);
13672
+ onSessionStartRef.current?.(sessionId);
13337
13673
  },
13338
13674
  onSessionEnd: (sessionId, reason) => {
13339
13675
  isTrackingRef.current = false;
13340
- onSessionEnd?.(sessionId, reason);
13676
+ onSessionEndRef.current?.(sessionId, reason);
13341
13677
  }
13342
13678
  });
13343
13679
  trackerRef.current = tracker;
13344
13680
  const companyId = user?.company_id || user?.properties?.company_id || null;
13345
13681
  tracker.startSession(user.id, companyId);
13346
13682
  return () => {
13347
- if (trackerRef.current) {
13348
- trackerRef.current.endSession("navigation");
13349
- trackerRef.current = null;
13350
- }
13683
+ const reason = isAuthenticatedRef.current ? "navigation" : "logout";
13684
+ void endCurrentTracker(reason);
13351
13685
  initRef.current = false;
13352
13686
  isTrackingRef.current = false;
13353
13687
  };
13354
- }, [enabled, isAuthenticated, user?.id, session?.access_token, config?.apiBaseUrl, getAccessToken2, onSessionStart, onSessionEnd, user]);
13688
+ }, [enabled, isAuthenticated, user?.id, apiBaseUrl2, getAccessToken2, endCurrentTracker]);
13355
13689
  React125.useEffect(() => {
13356
13690
  if (!isAuthenticated && trackerRef.current && isTrackingRef.current) {
13357
- trackerRef.current.endSession("logout");
13358
- trackerRef.current = null;
13359
- isTrackingRef.current = false;
13691
+ void endCurrentTracker("logout");
13360
13692
  }
13361
- }, [isAuthenticated]);
13693
+ }, [isAuthenticated, endCurrentTracker]);
13362
13694
  const endSession = React125.useCallback(async (reason) => {
13363
- if (trackerRef.current) {
13364
- await trackerRef.current.endSession(reason);
13365
- trackerRef.current = null;
13366
- isTrackingRef.current = false;
13367
- }
13368
- }, []);
13695
+ await endCurrentTracker(reason);
13696
+ }, [endCurrentTracker]);
13369
13697
  const sessionStats = trackerRef.current?.getSessionStats() || null;
13370
13698
  return {
13371
13699
  sessionStats,
@@ -17464,7 +17792,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17464
17792
  return Number.isFinite(parsed) ? parsed : DEFAULT_MAX_MANIFEST_AGE_MS;
17465
17793
  })();
17466
17794
  const manifestStaleThresholdMs = maxManifestAgeMs > 0 ? Math.min(maxManifestAgeMs, SEGMENT_MAX_AGE_MS) : SEGMENT_MAX_AGE_MS;
17467
- const debugLog = (...args) => {
17795
+ const debugLog2 = (...args) => {
17468
17796
  if (debugEnabled) {
17469
17797
  console.log(...args);
17470
17798
  }
@@ -17646,7 +17974,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17646
17974
  return;
17647
17975
  }
17648
17976
  } catch (error) {
17649
- debugLog("[HLS] Stale manifest poll failed", error);
17977
+ debugLog2("[HLS] Stale manifest poll failed", error);
17650
17978
  }
17651
17979
  staleManifestPollDelayRef.current = Math.min(
17652
17980
  staleManifestPollDelayRef.current + 5e3,
@@ -17996,7 +18324,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17996
18324
  }
17997
18325
  return true;
17998
18326
  } catch (error) {
17999
- debugLog("[HLS] Manifest freshness check failed", error);
18327
+ debugLog2("[HLS] Manifest freshness check failed", error);
18000
18328
  {
18001
18329
  markStaleStream("manifest freshness check failed");
18002
18330
  return false;
@@ -18042,7 +18370,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18042
18370
  };
18043
18371
  const softRestart = (reason) => {
18044
18372
  if (staleManifestTriggeredRef.current) {
18045
- debugLog("[HLS] Skip soft restart while manifest is stale", reason);
18373
+ debugLog2("[HLS] Skip soft restart while manifest is stale", reason);
18046
18374
  return;
18047
18375
  }
18048
18376
  console.warn(`[HLS] Soft restart: ${reason}`);
@@ -18075,7 +18403,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18075
18403
  };
18076
18404
  const hardRestart = (reason) => {
18077
18405
  if (staleManifestTriggeredRef.current) {
18078
- debugLog("[HLS] Skip hard restart while manifest is stale", reason);
18406
+ debugLog2("[HLS] Skip hard restart while manifest is stale", reason);
18079
18407
  return;
18080
18408
  }
18081
18409
  console.warn(`[HLS] Hard restart: ${reason}`);
@@ -18329,7 +18657,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18329
18657
  hls.loadSource(resolvedHlsSrc);
18330
18658
  activeStreamUrlRef.current = resolvedHlsSrc;
18331
18659
  hls.on(Hls__default.default.Events.ERROR, (_, data) => {
18332
- debugLog("[HLS] Error", {
18660
+ debugLog2("[HLS] Error", {
18333
18661
  type: data.type,
18334
18662
  details: data.details,
18335
18663
  fatal: data.fatal,
@@ -18337,7 +18665,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18337
18665
  frag: data.frag?.sn
18338
18666
  });
18339
18667
  if (data.type === Hls__default.default.ErrorTypes.MEDIA_ERROR && data.details === Hls__default.default.ErrorDetails.BUFFER_STALLED_ERROR) {
18340
- debugLog("[HLS] Buffer stalled, waiting for next segment");
18668
+ debugLog2("[HLS] Buffer stalled, waiting for next segment");
18341
18669
  attemptPlay();
18342
18670
  return;
18343
18671
  }
@@ -18481,7 +18809,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18481
18809
  }
18482
18810
  }
18483
18811
  }
18484
- debugLog("[HLS] Level loaded", {
18812
+ debugLog2("[HLS] Level loaded", {
18485
18813
  targetduration: details.targetduration,
18486
18814
  edge: details.edge,
18487
18815
  fragments: data.details?.fragments?.length
@@ -18877,6 +19205,7 @@ var isInitializing = false;
18877
19205
  var initializedWithLineIds = [];
18878
19206
  var missingLineContextWarnings = /* @__PURE__ */ new Set();
18879
19207
  var lineLoadPromises = /* @__PURE__ */ new Map();
19208
+ var GLOBAL_CACHE_KEY = "global";
18880
19209
  var initializationPromise = null;
18881
19210
  var workspaceDisplayNamesListeners = /* @__PURE__ */ new Set();
18882
19211
  var notifyWorkspaceDisplayNamesListeners = (changedLineId) => {
@@ -18898,6 +19227,22 @@ var storeLineDisplayNames = (lineId, lineDisplayNamesMap) => {
18898
19227
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
18899
19228
  });
18900
19229
  };
19230
+ var storeGlobalDisplayNames = (displayNamesMap) => {
19231
+ runtimeWorkspaceDisplayNames[GLOBAL_CACHE_KEY] = {};
19232
+ displayNamesMap.forEach((displayName, workspaceId) => {
19233
+ runtimeWorkspaceDisplayNames[GLOBAL_CACHE_KEY][workspaceId] = displayName;
19234
+ });
19235
+ };
19236
+ var storeDisplayNamesByLine = (displayNamesByLine, requestedLineIds = []) => {
19237
+ displayNamesByLine.forEach((lineDisplayNamesMap, lineId) => {
19238
+ storeLineDisplayNames(lineId, lineDisplayNamesMap);
19239
+ });
19240
+ requestedLineIds.forEach((lineId) => {
19241
+ if (!runtimeWorkspaceDisplayNames[lineId]) {
19242
+ runtimeWorkspaceDisplayNames[lineId] = {};
19243
+ }
19244
+ });
19245
+ };
18901
19246
  var ensureLineWorkspaceDisplayNamesLoaded = async (lineId) => {
18902
19247
  if (!lineId || runtimeWorkspaceDisplayNames[lineId]) {
18903
19248
  return;
@@ -18981,7 +19326,11 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
18981
19326
  console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
18982
19327
  runtimeWorkspaceDisplayNames = {};
18983
19328
  lineLoadPromises.clear();
18984
- if (targetLineIds.length > 0) {
19329
+ if (targetLineIds.length > 1) {
19330
+ const displayNamesByLine = await workspaceService.getWorkspaceDisplayNamesByLine(void 0, targetLineIds);
19331
+ storeDisplayNamesByLine(displayNamesByLine, targetLineIds);
19332
+ console.log(`\u2705 Stored display names for ${displayNamesByLine.size} authorized lines`);
19333
+ } else if (targetLineIds.length === 1) {
18985
19334
  const results = await Promise.all(
18986
19335
  targetLineIds.map(async (lineId) => {
18987
19336
  console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
@@ -18996,10 +19345,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
18996
19345
  } else {
18997
19346
  console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
18998
19347
  const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
18999
- runtimeWorkspaceDisplayNames["global"] = {};
19000
- allWorkspacesMap.forEach((displayName, workspaceId) => {
19001
- runtimeWorkspaceDisplayNames["global"][workspaceId] = displayName;
19002
- });
19348
+ storeGlobalDisplayNames(allWorkspacesMap);
19003
19349
  }
19004
19350
  isInitialized = true;
19005
19351
  initializedWithLineIds = targetLineIds;
@@ -19015,6 +19361,37 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
19015
19361
  })();
19016
19362
  await initializationPromise;
19017
19363
  }
19364
+ var preInitializeWorkspaceDisplayNamesForLines = async (lineIds) => {
19365
+ const uniqueLineIds = Array.from(new Set((lineIds || []).filter(Boolean)));
19366
+ if (uniqueLineIds.length === 0) {
19367
+ await preInitializeWorkspaceDisplayNames();
19368
+ return;
19369
+ }
19370
+ if (uniqueLineIds.length === 1) {
19371
+ await preInitializeWorkspaceDisplayNames(uniqueLineIds[0]);
19372
+ return;
19373
+ }
19374
+ if (isInitialized && uniqueLineIds.every((lineId) => Boolean(runtimeWorkspaceDisplayNames[lineId]))) {
19375
+ return;
19376
+ }
19377
+ if (initializationPromise) {
19378
+ await initializationPromise;
19379
+ if (uniqueLineIds.every((lineId) => Boolean(runtimeWorkspaceDisplayNames[lineId]))) {
19380
+ return;
19381
+ }
19382
+ }
19383
+ isInitializing = true;
19384
+ initializationPromise = workspaceService.getWorkspaceDisplayNamesByLine(void 0, uniqueLineIds).then((displayNamesByLine) => {
19385
+ storeDisplayNamesByLine(displayNamesByLine, uniqueLineIds);
19386
+ initializedWithLineIds = uniqueLineIds;
19387
+ isInitialized = true;
19388
+ notifyWorkspaceDisplayNamesListeners();
19389
+ }).finally(() => {
19390
+ isInitializing = false;
19391
+ initializationPromise = null;
19392
+ });
19393
+ await initializationPromise;
19394
+ };
19018
19395
  var preInitializeWorkspaceDisplayNames = async (lineId) => {
19019
19396
  console.log("\u{1F504} preInitializeWorkspaceDisplayNames called for lineId:", lineId);
19020
19397
  if (isInitialized) {
@@ -38749,6 +39126,7 @@ var HourlyOutputChartComponent = ({
38749
39126
  timeRange: data2.timeRange,
38750
39127
  startTime,
38751
39128
  endTime,
39129
+ timezone,
38752
39130
  output: Math.round(data2.originalOutput || 0),
38753
39131
  target: data2.target,
38754
39132
  status: data2.status
@@ -38922,6 +39300,7 @@ var HourlyOutputChartComponent = ({
38922
39300
  timeRange: entry.timeRange,
38923
39301
  startTime,
38924
39302
  endTime,
39303
+ timezone,
38925
39304
  output: Math.round(entry.originalOutput || 0),
38926
39305
  target: entry.target,
38927
39306
  status: entry.status
@@ -42389,6 +42768,65 @@ var buildPrefetchedExplorerMetadata = (activeFilter, metadataCategoryId, categor
42389
42768
  };
42390
42769
  };
42391
42770
  var shouldDeferClipPlayerRender = (cropLoading, workspaceCrop) => cropLoading && workspaceCrop === null;
42771
+ var parseSortableCycleTime = (value) => {
42772
+ if (typeof value === "number" && Number.isFinite(value)) {
42773
+ return value;
42774
+ }
42775
+ if (typeof value === "string") {
42776
+ const parsed = Number(value);
42777
+ return Number.isFinite(parsed) ? parsed : null;
42778
+ }
42779
+ return null;
42780
+ };
42781
+ var readSortableCycleTime = (clip) => {
42782
+ const candidate = clip;
42783
+ return parseSortableCycleTime(candidate?.cycleTimeSeconds) ?? parseSortableCycleTime(candidate?.cycle_time_seconds) ?? parseSortableCycleTime(candidate?.duration) ?? parseSortableCycleTime(
42784
+ candidate?.original_task_metadata?.cycle_time
42785
+ ) ?? null;
42786
+ };
42787
+ var readSortableTimestamp = (clip) => {
42788
+ const candidate = clip;
42789
+ const timestamp = candidate?.creation_timestamp || candidate?.timestamp || candidate?.clip_timestamp || candidate?.clip_end_time || candidate?.clip_start_time;
42790
+ if (typeof timestamp !== "string") {
42791
+ return 0;
42792
+ }
42793
+ const parsed = new Date(timestamp).getTime();
42794
+ return Number.isFinite(parsed) ? parsed : 0;
42795
+ };
42796
+ var sortPercentileCycleClipsForDisplay = (categoryId, clips) => {
42797
+ if (categoryId !== "fast-cycles" && categoryId !== "slow-cycles") {
42798
+ return [...clips];
42799
+ }
42800
+ return [...clips].sort((left, right) => {
42801
+ const leftCycleTime = readSortableCycleTime(left);
42802
+ const rightCycleTime = readSortableCycleTime(right);
42803
+ const leftMissingCycleTime = leftCycleTime === null;
42804
+ const rightMissingCycleTime = rightCycleTime === null;
42805
+ if (leftMissingCycleTime !== rightMissingCycleTime) {
42806
+ return leftMissingCycleTime ? 1 : -1;
42807
+ }
42808
+ if (leftCycleTime !== null && rightCycleTime !== null && leftCycleTime !== rightCycleTime) {
42809
+ return categoryId === "fast-cycles" ? leftCycleTime - rightCycleTime : rightCycleTime - leftCycleTime;
42810
+ }
42811
+ return readSortableTimestamp(right) - readSortableTimestamp(left);
42812
+ });
42813
+ };
42814
+ var resolveInitialClipCategory = (categoryCandidates, clipTypes = [], counts = {}) => {
42815
+ const candidates = Array.from(
42816
+ new Set(
42817
+ categoryCandidates.filter((candidate) => Boolean(candidate)).map((candidate) => candidate.trim()).filter(Boolean)
42818
+ )
42819
+ );
42820
+ if (candidates.length === 0) {
42821
+ return null;
42822
+ }
42823
+ const availableCategories = /* @__PURE__ */ new Set();
42824
+ clipTypes.forEach((type) => {
42825
+ if (type?.type) availableCategories.add(String(type.type));
42826
+ if (type?.id) availableCategories.add(String(type.id));
42827
+ });
42828
+ return candidates.find((candidate) => (counts[candidate] || 0) > 0) || candidates.find((candidate) => availableCategories.has(candidate)) || null;
42829
+ };
42392
42830
  var getCategoryMetadataLoadPlanForFilterChange = ({
42393
42831
  activeFilter,
42394
42832
  currentClipId,
@@ -42400,6 +42838,12 @@ var getCategoryMetadataLoadPlanForFilterChange = ({
42400
42838
  if (activeFilter === "recent_flow_red_streak" && categoryTotal > 0) {
42401
42839
  return { shouldLoad: true, autoLoadFirstVideo: true };
42402
42840
  }
42841
+ if (activeFilter === "fast-cycles" || activeFilter === "slow-cycles") {
42842
+ return { shouldLoad: true, autoLoadFirstVideo: true };
42843
+ }
42844
+ if (categoryTotal > 0 && activeFilter === "cycle_completion") {
42845
+ return { shouldLoad: true, autoLoadFirstVideo: true };
42846
+ }
42403
42847
  return {
42404
42848
  shouldLoad: Boolean(currentClipId),
42405
42849
  autoLoadFirstVideo: false
@@ -46237,6 +46681,36 @@ var CLIP_METADATA_PAGE_SIZE = 50;
46237
46681
  var RECENT_FLOW_RED_STREAK_CLIP_TYPE2 = "recent_flow_red_streak";
46238
46682
  var RECENT_FLOW_RED_STREAK_DISPLAY_LABEL = "Low moments";
46239
46683
  var RECENT_FLOW_RED_STREAK_DISPLAY_SUBTITLE = "Moments of low efficiency";
46684
+ var REQUIRED_HOURLY_HANDOFF_CATEGORIES = {
46685
+ cycle_completion: {
46686
+ id: "cycle_completion",
46687
+ label: "Cycle Completion",
46688
+ description: "Successfully completed production cycles",
46689
+ color: "green",
46690
+ icon: "check-circle"
46691
+ },
46692
+ idle_time: {
46693
+ id: "idle_time",
46694
+ label: "Idle Time",
46695
+ description: "Idle periods",
46696
+ color: "purple",
46697
+ icon: "clock"
46698
+ },
46699
+ [RECENT_FLOW_RED_STREAK_CLIP_TYPE2]: {
46700
+ id: RECENT_FLOW_RED_STREAK_CLIP_TYPE2,
46701
+ label: RECENT_FLOW_RED_STREAK_DISPLAY_LABEL,
46702
+ description: RECENT_FLOW_RED_STREAK_DISPLAY_SUBTITLE,
46703
+ color: "red",
46704
+ icon: "alert-triangle"
46705
+ }
46706
+ };
46707
+ var REQUIRED_HOURLY_FILTER_CATEGORY_IDS = [
46708
+ RECENT_FLOW_RED_STREAK_CLIP_TYPE2,
46709
+ "cycle_completion",
46710
+ "fast-cycles",
46711
+ "slow-cycles",
46712
+ "idle_time"
46713
+ ];
46240
46714
  var parseCycleTime = (value) => {
46241
46715
  if (typeof value === "number" && Number.isFinite(value)) {
46242
46716
  return value;
@@ -46265,6 +46739,21 @@ var formatDurationLabel = (seconds) => {
46265
46739
  }
46266
46740
  return `${Math.round(roundedSeconds / 60)} min`;
46267
46741
  };
46742
+ var timeValueToMinutes = (value) => {
46743
+ const [hourValue, minuteValue] = value.substring(0, 5).split(":").map(Number);
46744
+ if (!Number.isInteger(hourValue) || !Number.isInteger(minuteValue) || hourValue < 0 || hourValue > 23 || minuteValue < 0 || minuteValue > 59) {
46745
+ return null;
46746
+ }
46747
+ return hourValue * 60 + minuteValue;
46748
+ };
46749
+ var isMinuteInTimeWindow = (minute, startValue, endValue) => {
46750
+ const startMinute = timeValueToMinutes(startValue);
46751
+ const endMinute = timeValueToMinutes(endValue);
46752
+ if (startMinute === null || endMinute === null) {
46753
+ return true;
46754
+ }
46755
+ return endMinute > startMinute ? minute >= startMinute && minute < endMinute : minute >= startMinute || minute < endMinute;
46756
+ };
46268
46757
  var sortRedFlowMetadata = (clips) => {
46269
46758
  return clips.slice().sort((left, right) => {
46270
46759
  const getOutputShortfall = (clip) => {
@@ -46289,6 +46778,27 @@ var sortRedFlowMetadata = (clips) => {
46289
46778
  return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
46290
46779
  });
46291
46780
  };
46781
+ var buildClipMetadataFromVideo = (video, index) => ({
46782
+ id: video.id,
46783
+ clipId: video.id,
46784
+ clip_timestamp: video.creation_timestamp || video.timestamp,
46785
+ description: video.description,
46786
+ severity: video.severity,
46787
+ category: video.type,
46788
+ duration: typeof video.duration === "number" ? video.duration : typeof video.cycle_time_seconds === "number" ? video.cycle_time_seconds : void 0,
46789
+ clip_start_time: video.clip_start_time,
46790
+ clip_end_time: video.clip_end_time,
46791
+ index,
46792
+ idle_start_time: video.idle_start_time,
46793
+ idle_end_time: video.idle_end_time,
46794
+ cycle_item_count: null,
46795
+ red_flow_timeline: video.red_flow_timeline,
46796
+ red_flow_severity_score: video.red_flow_severity_score,
46797
+ red_flow_output_shortfall_units: video.red_flow_output_shortfall_units,
46798
+ red_flow_worst_minute: video.red_flow_worst_minute,
46799
+ red_flow_explanation_summary: video.red_flow_explanation_summary,
46800
+ red_flow_explanation: video.red_flow_explanation
46801
+ });
46292
46802
  var getSeverityIcon = (severity, categoryId, cycleTimeSeconds, targetCycleTime, clipId) => {
46293
46803
  if (categoryId === "idle_time" || categoryId === "low_value" || categoryId === "longest-idles") {
46294
46804
  return null;
@@ -46367,6 +46877,8 @@ var FileManagerFilters = ({
46367
46877
  idleTimeVlmEnabled = false,
46368
46878
  showPercentileCycleFilters = true,
46369
46879
  prefetchedClipMetadata,
46880
+ prefetchedClipTotals,
46881
+ prefetchedPercentileClips,
46370
46882
  externallyManagedLoadingCategories,
46371
46883
  activeCategoryLoading,
46372
46884
  idleClipSort = "latest",
@@ -46374,6 +46886,7 @@ var FileManagerFilters = ({
46374
46886
  initialTimeFilter
46375
46887
  }) => {
46376
46888
  const [expandedNodes, setExpandedNodes] = React125.useState(/* @__PURE__ */ new Set());
46889
+ const [activeInitialTimeFilter, setActiveInitialTimeFilter] = React125.useState(initialTimeFilter ?? null);
46377
46890
  const [startTime, setStartTime] = React125.useState(initialTimeFilter?.startTime ?? "");
46378
46891
  const [endTime, setEndTime] = React125.useState(initialTimeFilter?.endTime ?? "");
46379
46892
  const [isTimeFilterActive, setIsTimeFilterActive] = React125.useState(
@@ -46388,14 +46901,93 @@ var FileManagerFilters = ({
46388
46901
  const [showIdleLabelFilterModal, setShowIdleLabelFilterModal] = React125.useState(false);
46389
46902
  const [isLoadingIdleReasonOptions, setIsLoadingIdleReasonOptions] = React125.useState(false);
46390
46903
  const timezone = useAppTimezone();
46904
+ const activeTimeFilterTimezone = activeInitialTimeFilter?.timezone || initialTimeFilter?.timezone || timezone;
46905
+ const activeTimeFilterKey = React125.useMemo(() => startTime && endTime && isTimeFilterActive ? `${startTime}-${endTime}-${activeTimeFilterTimezone}` : "none", [activeTimeFilterTimezone, endTime, isTimeFilterActive, startTime]);
46906
+ const initialTimeFilterCategoryIds = React125.useMemo(() => {
46907
+ if (!activeInitialTimeFilter) {
46908
+ return [];
46909
+ }
46910
+ return Array.from(new Set([
46911
+ activeInitialTimeFilter.categoryId,
46912
+ ...activeInitialTimeFilter.categoryIds || []
46913
+ ].filter((value) => typeof value === "string" && value.length > 0)));
46914
+ }, [activeInitialTimeFilter]);
46915
+ const requiredHourlyFilterCategoryIds = React125.useMemo(() => {
46916
+ if (initialTimeFilterCategoryIds.length > 0) {
46917
+ return initialTimeFilterCategoryIds;
46918
+ }
46919
+ if (!isTimeFilterActive || !startTime || !endTime) {
46920
+ return [];
46921
+ }
46922
+ return REQUIRED_HOURLY_FILTER_CATEGORY_IDS;
46923
+ }, [endTime, initialTimeFilterCategoryIds, isTimeFilterActive, startTime]);
46924
+ const categoriesForTree = React125.useMemo(() => {
46925
+ if (requiredHourlyFilterCategoryIds.length === 0) {
46926
+ return categories;
46927
+ }
46928
+ const existingCategoryIds = new Set(categories.map((category) => category.id));
46929
+ const mergedCategories = [...categories];
46930
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
46931
+ const fallbackCategory = REQUIRED_HOURLY_HANDOFF_CATEGORIES[categoryId];
46932
+ if (!fallbackCategory || existingCategoryIds.has(categoryId)) {
46933
+ return;
46934
+ }
46935
+ mergedCategories.push(fallbackCategory);
46936
+ existingCategoryIds.add(categoryId);
46937
+ });
46938
+ return mergedCategories;
46939
+ }, [categories, requiredHourlyFilterCategoryIds]);
46391
46940
  const supabase = useSupabase();
46392
46941
  const [clipMetadata, setClipMetadata] = React125.useState({});
46942
+ const [scopedCategoryTotals, setScopedCategoryTotals] = React125.useState({});
46393
46943
  const [loadingCategories, setLoadingCategories] = React125.useState(/* @__PURE__ */ new Set());
46944
+ const isCategoryScopedByTimeFilter = React125.useCallback((categoryId) => {
46945
+ if (!startTime || !endTime || !isTimeFilterActive) {
46946
+ return false;
46947
+ }
46948
+ if (!activeInitialTimeFilter) {
46949
+ return true;
46950
+ }
46951
+ if (activeFilter === categoryId || activeInitialTimeFilter.categoryId === categoryId) {
46952
+ return true;
46953
+ }
46954
+ return Boolean(activeInitialTimeFilter.categoryIds?.includes(categoryId));
46955
+ }, [
46956
+ activeFilter,
46957
+ activeInitialTimeFilter,
46958
+ endTime,
46959
+ isTimeFilterActive,
46960
+ startTime
46961
+ ]);
46962
+ const isRequiredHourlyFilterCategory = React125.useCallback((categoryId) => requiredHourlyFilterCategoryIds.includes(categoryId), [requiredHourlyFilterCategoryIds]);
46963
+ const manualHourlySnapshotKey = React125.useMemo(() => {
46964
+ if (activeInitialTimeFilter || !isTimeFilterActive || !startTime || !endTime || !workspaceId || !date || shift === void 0) {
46965
+ return null;
46966
+ }
46967
+ return `${workspaceId}:${date}:${shift}:${startTime}:${endTime}:${activeTimeFilterTimezone}`;
46968
+ }, [
46969
+ activeInitialTimeFilter,
46970
+ activeTimeFilterTimezone,
46971
+ date,
46972
+ endTime,
46973
+ isTimeFilterActive,
46974
+ shift,
46975
+ startTime,
46976
+ workspaceId
46977
+ ]);
46978
+ const manualHourlySnapshotAppliedKeyRef = React125.useRef(null);
46979
+ const manualHourlySnapshotInFlightKeyRef = React125.useRef(null);
46980
+ const [manualHourlySnapshotFailureKey, setManualHourlySnapshotFailureKey] = React125.useState(null);
46981
+ const isManualHourlySnapshotPending = Boolean(
46982
+ manualHourlySnapshotKey && manualHourlySnapshotAppliedKeyRef.current !== manualHourlySnapshotKey && manualHourlySnapshotFailureKey !== manualHourlySnapshotKey
46983
+ );
46394
46984
  const [categoryPages, setCategoryPages] = React125.useState({});
46395
46985
  const [categoryHasMore, setCategoryHasMore] = React125.useState({});
46396
46986
  const [localClipClassifications, setLocalClipClassifications] = React125.useState({});
46397
46987
  const clipMetadataRef = React125.useRef({});
46398
46988
  const inFlightMetadataRequestsRef = React125.useRef(/* @__PURE__ */ new Set());
46989
+ const inFlightPercentileClipRequestsRef = React125.useRef(/* @__PURE__ */ new Set());
46990
+ const autoSelectedScopedClipRef = React125.useRef(null);
46399
46991
  const previousIdleClipSortRef = React125.useRef(idleClipSort);
46400
46992
  const mergedClipClassifications = React125.useMemo(() => ({
46401
46993
  ...clipClassifications || {},
@@ -46408,12 +47000,14 @@ var FileManagerFilters = ({
46408
47000
  if (!initialTimeFilter?.startTime || !initialTimeFilter?.endTime) {
46409
47001
  return;
46410
47002
  }
47003
+ setActiveInitialTimeFilter(initialTimeFilter);
46411
47004
  setStartTime(initialTimeFilter.startTime);
46412
47005
  setEndTime(initialTimeFilter.endTime);
46413
47006
  setIsTimeFilterActive(true);
46414
47007
  setShowTimeFilterModal(false);
46415
47008
  setStartSearchTerm("");
46416
47009
  setEndSearchTerm("");
47010
+ setScopedCategoryTotals({});
46417
47011
  }, [initialTimeFilter?.startTime, initialTimeFilter?.endTime]);
46418
47012
  React125.useEffect(() => {
46419
47013
  if (previousIdleClipSortRef.current === idleClipSort) {
@@ -46445,18 +47039,66 @@ var FileManagerFilters = ({
46445
47039
  return next;
46446
47040
  });
46447
47041
  }, [idleClipSort]);
47042
+ React125.useEffect(() => {
47043
+ manualHourlySnapshotAppliedKeyRef.current = null;
47044
+ manualHourlySnapshotInFlightKeyRef.current = null;
47045
+ setManualHourlySnapshotFailureKey(null);
47046
+ setScopedCategoryTotals({});
47047
+ setClipMetadata({});
47048
+ setCategoryPages({});
47049
+ setCategoryHasMore({});
47050
+ setPercentileClips({});
47051
+ setPercentileCounts({
47052
+ "fast-cycles": null,
47053
+ "slow-cycles": null
47054
+ });
47055
+ }, [activeTimeFilterKey]);
46448
47056
  const isCategoryExternallyManaged = React125.useCallback((categoryId) => {
46449
47057
  if (!categoryId) {
46450
47058
  return false;
46451
47059
  }
46452
- if (prefetchedClipMetadata && Array.isArray(prefetchedClipMetadata[categoryId]) && prefetchedClipMetadata[categoryId].length > 0) {
47060
+ if (isManualHourlySnapshotPending && requiredHourlyFilterCategoryIds.includes(categoryId)) {
47061
+ return true;
47062
+ }
47063
+ if (prefetchedClipMetadata && Array.isArray(prefetchedClipMetadata[categoryId]) && (prefetchedClipMetadata[categoryId].length > 0 || typeof prefetchedClipTotals?.[categoryId] === "number")) {
47064
+ return true;
47065
+ }
47066
+ if (prefetchedPercentileClips && Array.isArray(prefetchedPercentileClips[categoryId]) && (prefetchedPercentileClips[categoryId].length > 0 || isCategoryScopedByTimeFilter(categoryId) && typeof prefetchedClipTotals?.[categoryId] === "number")) {
46453
47067
  return true;
46454
47068
  }
46455
47069
  if (externallyManagedLoadingCategories?.[categoryId]) {
46456
47070
  return true;
46457
47071
  }
46458
47072
  return false;
46459
- }, [prefetchedClipMetadata, externallyManagedLoadingCategories]);
47073
+ }, [
47074
+ externallyManagedLoadingCategories,
47075
+ isManualHourlySnapshotPending,
47076
+ isCategoryScopedByTimeFilter,
47077
+ prefetchedClipMetadata,
47078
+ prefetchedClipTotals,
47079
+ prefetchedPercentileClips,
47080
+ requiredHourlyFilterCategoryIds
47081
+ ]);
47082
+ const getAvailableClipMetadata = React125.useCallback((categoryId) => {
47083
+ const localClips = clipMetadata[categoryId];
47084
+ if (Array.isArray(localClips) && localClips.length > 0) {
47085
+ return localClips;
47086
+ }
47087
+ const prefetchedClips = prefetchedClipMetadata?.[categoryId];
47088
+ if (Array.isArray(prefetchedClips) && prefetchedClips.length > 0) {
47089
+ return prefetchedClips;
47090
+ }
47091
+ return [];
47092
+ }, [clipMetadata, prefetchedClipMetadata]);
47093
+ const hasKnownClipMetadata = React125.useCallback((categoryId) => {
47094
+ if (Array.isArray(clipMetadata[categoryId])) {
47095
+ return true;
47096
+ }
47097
+ if (Array.isArray(prefetchedClipMetadata?.[categoryId])) {
47098
+ return true;
47099
+ }
47100
+ return typeof prefetchedClipTotals?.[categoryId] === "number";
47101
+ }, [clipMetadata, prefetchedClipMetadata, prefetchedClipTotals]);
46460
47102
  React125.useEffect(() => {
46461
47103
  if (!prefetchedClipMetadata) {
46462
47104
  return;
@@ -46479,6 +47121,22 @@ var FileManagerFilters = ({
46479
47121
  });
46480
47122
  return changed ? next : prev;
46481
47123
  });
47124
+ setScopedCategoryTotals((prev) => {
47125
+ let changed = false;
47126
+ const next = { ...prev };
47127
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
47128
+ if (!Array.isArray(clips) || !isCategoryScopedByTimeFilter(categoryId)) {
47129
+ return;
47130
+ }
47131
+ const nextTotal = typeof prefetchedClipTotals?.[categoryId] === "number" ? Math.max(0, prefetchedClipTotals[categoryId]) : clips.length;
47132
+ if (next[categoryId] === nextTotal) {
47133
+ return;
47134
+ }
47135
+ next[categoryId] = nextTotal;
47136
+ changed = true;
47137
+ });
47138
+ return changed ? next : prev;
47139
+ });
46482
47140
  setCategoryPages((prev) => {
46483
47141
  let changed = false;
46484
47142
  const next = { ...prev };
@@ -46503,7 +47161,7 @@ var FileManagerFilters = ({
46503
47161
  return;
46504
47162
  }
46505
47163
  const knownTotal = typeof counts?.[categoryId] === "number" ? counts[categoryId] : null;
46506
- const inferredHasMore = knownTotal !== null ? clips.length < knownTotal : prev[categoryId];
47164
+ const inferredHasMore = isCategoryScopedByTimeFilter(categoryId) ? false : knownTotal !== null ? clips.length < knownTotal : prev[categoryId];
46507
47165
  if (typeof inferredHasMore !== "boolean" || next[categoryId] === inferredHasMore) {
46508
47166
  return;
46509
47167
  }
@@ -46512,8 +47170,68 @@ var FileManagerFilters = ({
46512
47170
  });
46513
47171
  return changed ? next : prev;
46514
47172
  });
46515
- }, [prefetchedClipMetadata, counts]);
47173
+ }, [prefetchedClipMetadata, prefetchedClipTotals, counts, isCategoryScopedByTimeFilter]);
47174
+ React125.useEffect(() => {
47175
+ if (!prefetchedPercentileClips) {
47176
+ return;
47177
+ }
47178
+ setPercentileClips((prev) => {
47179
+ let changed = false;
47180
+ const next = { ...prev };
47181
+ ["fast-cycles", "slow-cycles"].forEach((categoryId) => {
47182
+ const clips = prefetchedPercentileClips[categoryId];
47183
+ if (!Array.isArray(clips)) {
47184
+ return;
47185
+ }
47186
+ const previousClips = prev[categoryId] || [];
47187
+ const previousSignature = previousClips.map((clip) => clip.id).join("|");
47188
+ const nextSignature = clips.map((clip) => clip.id).join("|");
47189
+ if (previousSignature === nextSignature) {
47190
+ return;
47191
+ }
47192
+ next[categoryId] = clips;
47193
+ changed = true;
47194
+ });
47195
+ return changed ? next : prev;
47196
+ });
47197
+ setPercentileCounts((prev) => {
47198
+ let changed = false;
47199
+ const next = { ...prev };
47200
+ ["fast-cycles", "slow-cycles"].forEach((categoryId) => {
47201
+ const clips = prefetchedPercentileClips[categoryId];
47202
+ if (!Array.isArray(clips)) {
47203
+ return;
47204
+ }
47205
+ const nextTotal = typeof prefetchedClipTotals?.[categoryId] === "number" ? Math.max(0, prefetchedClipTotals[categoryId]) : clips.length;
47206
+ if (next[categoryId] === nextTotal) {
47207
+ return;
47208
+ }
47209
+ next[categoryId] = nextTotal;
47210
+ changed = true;
47211
+ });
47212
+ return changed ? next : prev;
47213
+ });
47214
+ }, [prefetchedClipTotals, prefetchedPercentileClips]);
46516
47215
  const { state: filterState } = useClipFilter();
47216
+ const shouldShowCategory = React125.useCallback((categoryId) => {
47217
+ switch (categoryId) {
47218
+ case "fast-cycles":
47219
+ return showPercentileCycleFilters && filterState.showFastCycles;
47220
+ case "slow-cycles":
47221
+ return showPercentileCycleFilters && filterState.showSlowCycles;
47222
+ case "longest-idles":
47223
+ return false;
47224
+ // filterState.showLongestIdles; // Temporarily disabled
47225
+ case "cycle_completion":
47226
+ return filterState.showCycleCompletion;
47227
+ case "idle_time":
47228
+ return filterState.showIdleTime;
47229
+ case "sop_deviations":
47230
+ return filterState.showSopDeviations;
47231
+ default:
47232
+ return true;
47233
+ }
47234
+ }, [filterState, showPercentileCycleFilters]);
46517
47235
  const [percentileCounts, setPercentileCounts] = React125.useState({
46518
47236
  "fast-cycles": null,
46519
47237
  "slow-cycles": null
@@ -46623,11 +47341,12 @@ var FileManagerFilters = ({
46623
47341
  return null;
46624
47342
  }
46625
47343
  }, [supabase]);
46626
- const getMetadataLoadingKey = React125.useCallback((categoryId, page) => `${categoryId}-${page}-${categoryId === RECENT_FLOW_RED_STREAK_CLIP_TYPE2 ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"}`, [idleClipSort]);
47344
+ const getMetadataLoadingKey = React125.useCallback((categoryId, page) => `${categoryId}-${page}-${categoryId === RECENT_FLOW_RED_STREAK_CLIP_TYPE2 ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"}-${activeTimeFilterKey}`, [activeTimeFilterKey, idleClipSort]);
46627
47345
  const fetchClipMetadataPage = React125.useCallback(async (categoryId, page = 1) => {
46628
47346
  if (!workspaceId || !date || shift === void 0) {
46629
47347
  throw new Error("Missing required params for clip metadata fetch");
46630
47348
  }
47349
+ const shouldScopeToTimeFilter = isCategoryScopedByTimeFilter(categoryId);
46631
47350
  const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
46632
47351
  method: "POST",
46633
47352
  headers: {
@@ -46641,9 +47360,12 @@ var FileManagerFilters = ({
46641
47360
  category: categoryId,
46642
47361
  page,
46643
47362
  limit: CLIP_METADATA_PAGE_SIZE,
46644
- knownTotal: typeof counts?.[categoryId] === "number" ? counts[categoryId] : null,
47363
+ knownTotal: shouldScopeToTimeFilter ? null : typeof counts?.[categoryId] === "number" ? counts[categoryId] : null,
46645
47364
  snapshotDateTime,
46646
47365
  snapshotClipId,
47366
+ startTime: shouldScopeToTimeFilter ? startTime : void 0,
47367
+ endTime: shouldScopeToTimeFilter ? endTime : void 0,
47368
+ timeFilterTimezone: shouldScopeToTimeFilter ? activeTimeFilterTimezone : void 0,
46647
47369
  sort: categoryId === RECENT_FLOW_RED_STREAK_CLIP_TYPE2 ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"
46648
47370
  }),
46649
47371
  redirectReason: "session_expired"
@@ -46652,7 +47374,20 @@ var FileManagerFilters = ({
46652
47374
  throw new Error(`API error: ${response.status}`);
46653
47375
  }
46654
47376
  return response.json();
46655
- }, [workspaceId, date, shift, counts, snapshotDateTime, snapshotClipId, idleClipSort, supabase]);
47377
+ }, [
47378
+ activeTimeFilterTimezone,
47379
+ endTime,
47380
+ isCategoryScopedByTimeFilter,
47381
+ startTime,
47382
+ workspaceId,
47383
+ date,
47384
+ shift,
47385
+ counts,
47386
+ snapshotDateTime,
47387
+ snapshotClipId,
47388
+ idleClipSort,
47389
+ supabase
47390
+ ]);
46656
47391
  const seedIdleClassifications = React125.useCallback(async (clips) => {
46657
47392
  if (!idleTimeVlmEnabled || clips.length === 0) {
46658
47393
  return;
@@ -46720,6 +47455,12 @@ var FileManagerFilters = ({
46720
47455
  ...prev,
46721
47456
  [categoryId]: page === 1 ? data.clips : [...prev[categoryId] || [], ...data.clips]
46722
47457
  }));
47458
+ if (isCategoryScopedByTimeFilter(categoryId)) {
47459
+ setScopedCategoryTotals((prev) => ({
47460
+ ...prev,
47461
+ [categoryId]: Math.max(0, Number(data.total || 0))
47462
+ }));
47463
+ }
46723
47464
  if (categoryId === "idle_time" && idleTimeVlmEnabled) {
46724
47465
  await seedIdleClassifications(data.clips || []);
46725
47466
  }
@@ -46736,7 +47477,94 @@ var FileManagerFilters = ({
46736
47477
  return newSet;
46737
47478
  });
46738
47479
  }
46739
- }, [workspaceId, date, shift, fetchClipMetadataPage, idleTimeVlmEnabled, seedIdleClassifications, getMetadataLoadingKey]);
47480
+ }, [workspaceId, date, shift, fetchClipMetadataPage, idleTimeVlmEnabled, seedIdleClassifications, getMetadataLoadingKey, isCategoryScopedByTimeFilter]);
47481
+ React125.useCallback(async (categoryId) => {
47482
+ if (!workspaceId || !date || shift === void 0) {
47483
+ console.warn("[FileManager] Missing required params for full clip metadata fetch");
47484
+ return;
47485
+ }
47486
+ const loadingKey = `${getMetadataLoadingKey(categoryId, 1)}-all`;
47487
+ if (inFlightMetadataRequestsRef.current.has(loadingKey)) {
47488
+ return;
47489
+ }
47490
+ inFlightMetadataRequestsRef.current.add(loadingKey);
47491
+ setLoadingCategories((prev) => /* @__PURE__ */ new Set([...prev, loadingKey]));
47492
+ try {
47493
+ let accumulatedClips = [];
47494
+ let currentPage = 0;
47495
+ let hasMore = true;
47496
+ while (hasMore) {
47497
+ const nextPage = currentPage + 1;
47498
+ const pageData = await fetchClipMetadataPage(categoryId, nextPage);
47499
+ const pageClips = pageData.clips || [];
47500
+ accumulatedClips = [...accumulatedClips, ...pageClips];
47501
+ currentPage = nextPage;
47502
+ hasMore = Boolean(pageData.hasMore && pageClips.length > 0);
47503
+ }
47504
+ const dedupedClips = accumulatedClips.filter((clip, index, arr) => {
47505
+ const clipKey = clip.clipId || clip.id;
47506
+ return arr.findIndex((item) => (item.clipId || item.id) === clipKey) === index;
47507
+ });
47508
+ setClipMetadata((prev) => ({
47509
+ ...prev,
47510
+ [categoryId]: dedupedClips
47511
+ }));
47512
+ setCategoryPages((prev) => ({ ...prev, [categoryId]: Math.max(currentPage, 1) }));
47513
+ setCategoryHasMore((prev) => ({ ...prev, [categoryId]: false }));
47514
+ if (categoryId === "idle_time" && idleTimeVlmEnabled) {
47515
+ await seedIdleClassifications(dedupedClips);
47516
+ }
47517
+ console.log(`[FileManager] Loaded all ${dedupedClips.length} clips for ${categoryId} due to chart time filter`);
47518
+ } catch (error) {
47519
+ console.error("[FileManager] Error fetching full clip metadata:", error);
47520
+ } finally {
47521
+ inFlightMetadataRequestsRef.current.delete(loadingKey);
47522
+ setLoadingCategories((prev) => {
47523
+ const newSet = new Set(prev);
47524
+ newSet.delete(loadingKey);
47525
+ return newSet;
47526
+ });
47527
+ }
47528
+ }, [
47529
+ workspaceId,
47530
+ date,
47531
+ shift,
47532
+ fetchClipMetadataPage,
47533
+ idleTimeVlmEnabled,
47534
+ seedIdleClassifications,
47535
+ getMetadataLoadingKey
47536
+ ]);
47537
+ const isInitialTimeFilterCategory = React125.useCallback((categoryId) => {
47538
+ if (!startTime || !endTime || !activeInitialTimeFilter) {
47539
+ return false;
47540
+ }
47541
+ if (activeFilter === categoryId) {
47542
+ return true;
47543
+ }
47544
+ if (activeInitialTimeFilter.categoryId === categoryId) {
47545
+ return true;
47546
+ }
47547
+ return Array.isArray(activeInitialTimeFilter.categoryIds) && activeInitialTimeFilter.categoryIds.includes(categoryId);
47548
+ }, [
47549
+ activeFilter,
47550
+ activeInitialTimeFilter,
47551
+ endTime,
47552
+ startTime
47553
+ ]);
47554
+ const hasCompleteMetadataForInitialTimeFilter = React125.useCallback((categoryId) => {
47555
+ if (!isInitialTimeFilterCategory(categoryId)) {
47556
+ return true;
47557
+ }
47558
+ const loadedClips = clipMetadataRef.current[categoryId] || [];
47559
+ const knownTotal = typeof scopedCategoryTotals[categoryId] === "number" ? scopedCategoryTotals[categoryId] : null;
47560
+ if (categoryHasMore[categoryId]) {
47561
+ return false;
47562
+ }
47563
+ if (knownTotal !== null && loadedClips.length < knownTotal) {
47564
+ return false;
47565
+ }
47566
+ return loadedClips.length > 0 || knownTotal === 0;
47567
+ }, [categoryHasMore, isInitialTimeFilterCategory, scopedCategoryTotals]);
46740
47568
  const ensureAllIdleTimeClipMetadataLoaded = React125.useCallback(async () => {
46741
47569
  if (!workspaceId || !date || shift === void 0) {
46742
47570
  return;
@@ -46802,6 +47630,12 @@ var FileManagerFilters = ({
46802
47630
  console.warn("[FileManager] Missing required params for percentile clips fetch");
46803
47631
  return;
46804
47632
  }
47633
+ const shouldScopeToTimeFilter = isCategoryScopedByTimeFilter(type);
47634
+ const requestKey = `${type}:${date}:${shift}:${filterState.percentile}:${shouldScopeToTimeFilter ? activeTimeFilterKey : "unscoped"}`;
47635
+ if (inFlightPercentileClipRequestsRef.current.has(requestKey)) {
47636
+ return;
47637
+ }
47638
+ inFlightPercentileClipRequestsRef.current.add(requestKey);
46805
47639
  try {
46806
47640
  const startDate = `${date}T00:00:00Z`;
46807
47641
  const endDate = `${date}T23:59:59Z`;
@@ -46817,7 +47651,10 @@ var FileManagerFilters = ({
46817
47651
  endDate,
46818
47652
  percentile: filterState.percentile,
46819
47653
  shiftId: shift,
46820
- limit: 50,
47654
+ limit: shouldScopeToTimeFilter ? 500 : 100,
47655
+ startTime: shouldScopeToTimeFilter ? startTime : void 0,
47656
+ endTime: shouldScopeToTimeFilter ? endTime : void 0,
47657
+ timeFilterTimezone: shouldScopeToTimeFilter ? activeTimeFilterTimezone : void 0,
46821
47658
  // The actual percentile action (fast-cycles, slow-cycles, idle-times)
46822
47659
  percentileAction: type
46823
47660
  }),
@@ -46832,7 +47669,7 @@ var FileManagerFilters = ({
46832
47669
  [type]: data.clips || []
46833
47670
  }));
46834
47671
  setPercentileCounts((prev) => {
46835
- if (typeof prev[type] === "number") {
47672
+ if (!shouldScopeToTimeFilter && typeof prev[type] === "number") {
46836
47673
  return prev;
46837
47674
  }
46838
47675
  return {
@@ -46843,8 +47680,206 @@ var FileManagerFilters = ({
46843
47680
  console.log(`[FileManager] Loaded ${data.clips?.length || 0} ${type} clips (percentile: ${filterState.percentile}%)`);
46844
47681
  } catch (error) {
46845
47682
  console.error(`[FileManager] Error fetching ${type} clips:`, error);
47683
+ } finally {
47684
+ inFlightPercentileClipRequestsRef.current.delete(requestKey);
47685
+ }
47686
+ }, [
47687
+ activeTimeFilterTimezone,
47688
+ activeTimeFilterKey,
47689
+ endTime,
47690
+ isCategoryScopedByTimeFilter,
47691
+ startTime,
47692
+ workspaceId,
47693
+ date,
47694
+ shift,
47695
+ filterState.percentile,
47696
+ showPercentileCycleFilters,
47697
+ supabase
47698
+ ]);
47699
+ React125.useEffect(() => {
47700
+ if (!manualHourlySnapshotKey) {
47701
+ return;
47702
+ }
47703
+ if (manualHourlySnapshotAppliedKeyRef.current === manualHourlySnapshotKey) {
47704
+ return;
47705
+ }
47706
+ if (manualHourlySnapshotInFlightKeyRef.current === manualHourlySnapshotKey) {
47707
+ return;
47708
+ }
47709
+ manualHourlySnapshotInFlightKeyRef.current = manualHourlySnapshotKey;
47710
+ setManualHourlySnapshotFailureKey((prev) => prev === manualHourlySnapshotKey ? null : prev);
47711
+ const applyHourlySnapshot = async () => {
47712
+ try {
47713
+ const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
47714
+ method: "POST",
47715
+ headers: {
47716
+ "Content-Type": "application/json"
47717
+ },
47718
+ body: JSON.stringify({
47719
+ action: "hourly-snapshot",
47720
+ workspaceId,
47721
+ date,
47722
+ shift,
47723
+ startTime,
47724
+ endTime,
47725
+ timeFilterTimezone: activeTimeFilterTimezone
47726
+ }),
47727
+ redirectReason: "session_expired"
47728
+ });
47729
+ if (!response.ok) {
47730
+ throw new Error(`API error: ${response.status}`);
47731
+ }
47732
+ const snapshot = await response.json();
47733
+ const metadataByCategory = snapshot?.metadataByCategory && typeof snapshot.metadataByCategory === "object" ? snapshot.metadataByCategory : {};
47734
+ const percentileClipsByCategory = snapshot?.percentileClipsByCategory && typeof snapshot.percentileClipsByCategory === "object" ? snapshot.percentileClipsByCategory : {};
47735
+ const rawTotalsByCategory = snapshot?.totalsByCategory && typeof snapshot.totalsByCategory === "object" ? snapshot.totalsByCategory : {};
47736
+ const totalsByCategory = requiredHourlyFilterCategoryIds.reduce((accumulator, categoryId) => {
47737
+ const rawTotal = rawTotalsByCategory[categoryId];
47738
+ if (typeof rawTotal === "number" && Number.isFinite(rawTotal)) {
47739
+ accumulator[categoryId] = Math.max(0, rawTotal);
47740
+ return accumulator;
47741
+ }
47742
+ if (categoryId === "fast-cycles" || categoryId === "slow-cycles") {
47743
+ accumulator[categoryId] = Array.isArray(percentileClipsByCategory[categoryId]) ? percentileClipsByCategory[categoryId].length : 0;
47744
+ return accumulator;
47745
+ }
47746
+ accumulator[categoryId] = Array.isArray(metadataByCategory[categoryId]) ? metadataByCategory[categoryId].length : 0;
47747
+ return accumulator;
47748
+ }, {});
47749
+ setClipMetadata((prev) => {
47750
+ const next = { ...prev };
47751
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
47752
+ if (categoryId === "fast-cycles" || categoryId === "slow-cycles") {
47753
+ return;
47754
+ }
47755
+ next[categoryId] = Array.isArray(metadataByCategory[categoryId]) ? metadataByCategory[categoryId] : [];
47756
+ });
47757
+ return next;
47758
+ });
47759
+ setScopedCategoryTotals((prev) => ({
47760
+ ...prev,
47761
+ ...totalsByCategory
47762
+ }));
47763
+ if (showPercentileCycleFilters) {
47764
+ setPercentileClips((prev) => ({
47765
+ ...prev,
47766
+ "fast-cycles": Array.isArray(percentileClipsByCategory["fast-cycles"]) ? percentileClipsByCategory["fast-cycles"] : [],
47767
+ "slow-cycles": Array.isArray(percentileClipsByCategory["slow-cycles"]) ? percentileClipsByCategory["slow-cycles"] : []
47768
+ }));
47769
+ setPercentileCounts((prev) => ({
47770
+ ...prev,
47771
+ "fast-cycles": totalsByCategory["fast-cycles"] ?? 0,
47772
+ "slow-cycles": totalsByCategory["slow-cycles"] ?? 0
47773
+ }));
47774
+ }
47775
+ setCategoryPages((prev) => {
47776
+ const next = { ...prev };
47777
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
47778
+ next[categoryId] = 1;
47779
+ });
47780
+ return next;
47781
+ });
47782
+ setCategoryHasMore((prev) => {
47783
+ const next = { ...prev };
47784
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
47785
+ next[categoryId] = false;
47786
+ });
47787
+ return next;
47788
+ });
47789
+ const idleClips = metadataByCategory.idle_time;
47790
+ if (Array.isArray(idleClips) && idleClips.length > 0) {
47791
+ await seedIdleClassifications(idleClips);
47792
+ }
47793
+ manualHourlySnapshotAppliedKeyRef.current = manualHourlySnapshotKey;
47794
+ setManualHourlySnapshotFailureKey((prev) => prev === manualHourlySnapshotKey ? null : prev);
47795
+ } catch (error) {
47796
+ console.error("[FileManager] Error fetching manual hourly snapshot:", error);
47797
+ setManualHourlySnapshotFailureKey(manualHourlySnapshotKey);
47798
+ } finally {
47799
+ if (manualHourlySnapshotInFlightKeyRef.current === manualHourlySnapshotKey) {
47800
+ manualHourlySnapshotInFlightKeyRef.current = null;
47801
+ }
47802
+ }
47803
+ };
47804
+ void applyHourlySnapshot();
47805
+ }, [
47806
+ activeTimeFilterTimezone,
47807
+ date,
47808
+ endTime,
47809
+ manualHourlySnapshotKey,
47810
+ requiredHourlyFilterCategoryIds,
47811
+ seedIdleClassifications,
47812
+ shift,
47813
+ showPercentileCycleFilters,
47814
+ startTime,
47815
+ supabase,
47816
+ workspaceId
47817
+ ]);
47818
+ React125.useEffect(() => {
47819
+ if (!startTime || !endTime || !activeInitialTimeFilter || initialTimeFilterCategoryIds.length === 0) {
47820
+ return;
46846
47821
  }
46847
- }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase]);
47822
+ initialTimeFilterCategoryIds.forEach((categoryId) => {
47823
+ if (categoryId === "fast-cycles" || categoryId === "slow-cycles") {
47824
+ const hasScopedPercentileResult = typeof percentileCounts[categoryId] === "number";
47825
+ if (!isCategoryExternallyManaged(categoryId) && !hasScopedPercentileResult && showPercentileCycleFilters) {
47826
+ fetchPercentileClips(categoryId);
47827
+ }
47828
+ return;
47829
+ }
47830
+ if (isCategoryExternallyManaged(categoryId)) {
47831
+ return;
47832
+ }
47833
+ if (!hasCompleteMetadataForInitialTimeFilter(categoryId)) {
47834
+ fetchClipMetadata(categoryId, 1);
47835
+ }
47836
+ });
47837
+ }, [
47838
+ activeInitialTimeFilter,
47839
+ endTime,
47840
+ fetchClipMetadata,
47841
+ fetchPercentileClips,
47842
+ hasCompleteMetadataForInitialTimeFilter,
47843
+ initialTimeFilterCategoryIds,
47844
+ isCategoryExternallyManaged,
47845
+ percentileCounts,
47846
+ showPercentileCycleFilters,
47847
+ startTime
47848
+ ]);
47849
+ React125.useEffect(() => {
47850
+ if (!startTime || !endTime || !isTimeFilterActive || activeInitialTimeFilter) {
47851
+ return;
47852
+ }
47853
+ categoriesForTree.forEach((category) => {
47854
+ if (!shouldShowCategory(category.id) || isCategoryExternallyManaged(category.id)) {
47855
+ return;
47856
+ }
47857
+ if (typeof scopedCategoryTotals[category.id] !== "number") {
47858
+ fetchClipMetadata(category.id, 1);
47859
+ }
47860
+ });
47861
+ if (showPercentileCycleFilters) {
47862
+ if (!isCategoryExternallyManaged("fast-cycles") && typeof percentileCounts["fast-cycles"] !== "number") {
47863
+ fetchPercentileClips("fast-cycles");
47864
+ }
47865
+ if (!isCategoryExternallyManaged("slow-cycles") && typeof percentileCounts["slow-cycles"] !== "number") {
47866
+ fetchPercentileClips("slow-cycles");
47867
+ }
47868
+ }
47869
+ }, [
47870
+ activeInitialTimeFilter,
47871
+ categoriesForTree,
47872
+ endTime,
47873
+ fetchClipMetadata,
47874
+ fetchPercentileClips,
47875
+ isCategoryExternallyManaged,
47876
+ isTimeFilterActive,
47877
+ percentileCounts,
47878
+ scopedCategoryTotals,
47879
+ shouldShowCategory,
47880
+ showPercentileCycleFilters,
47881
+ startTime
47882
+ ]);
46848
47883
  const percentileCountsKey = React125.useMemo(() => {
46849
47884
  if (!workspaceId || !date || shift === void 0) {
46850
47885
  return null;
@@ -46867,12 +47902,15 @@ var FileManagerFilters = ({
46867
47902
  }
46868
47903
  percentileCountsKeyRef.current = percentileCountsKey;
46869
47904
  percentilePrefetchRef.current = { key: null, types: /* @__PURE__ */ new Set() };
47905
+ if (prefetchedPercentileClips && (Array.isArray(prefetchedPercentileClips["fast-cycles"]) || Array.isArray(prefetchedPercentileClips["slow-cycles"]))) {
47906
+ return;
47907
+ }
46870
47908
  setPercentileCounts({
46871
47909
  "fast-cycles": null,
46872
47910
  "slow-cycles": null
46873
47911
  });
46874
47912
  setPercentileClips({});
46875
- }, [showPercentileCycleFilters, percentileCountsKey]);
47913
+ }, [prefetchedPercentileClips, showPercentileCycleFilters, percentileCountsKey]);
46876
47914
  React125.useEffect(() => {
46877
47915
  if (!showPercentileCycleFilters) {
46878
47916
  return;
@@ -46928,11 +47966,20 @@ var FileManagerFilters = ({
46928
47966
  const data = await response.json();
46929
47967
  const fastCycles = data?.counts?.["fast-cycles"];
46930
47968
  const slowCycles = data?.counts?.["slow-cycles"];
46931
- setPercentileCounts((prev) => ({
46932
- ...prev,
46933
- "fast-cycles": typeof fastCycles === "number" ? fastCycles : prev["fast-cycles"],
46934
- "slow-cycles": typeof slowCycles === "number" ? slowCycles : prev["slow-cycles"]
46935
- }));
47969
+ if (typeof fastCycles === "number" || typeof slowCycles === "number") {
47970
+ setPercentileCounts((prev) => {
47971
+ const nextFastCycles = typeof fastCycles === "number" ? fastCycles : prev["fast-cycles"];
47972
+ const nextSlowCycles = typeof slowCycles === "number" ? slowCycles : prev["slow-cycles"];
47973
+ if (prev["fast-cycles"] === nextFastCycles && prev["slow-cycles"] === nextSlowCycles) {
47974
+ return prev;
47975
+ }
47976
+ return {
47977
+ ...prev,
47978
+ "fast-cycles": nextFastCycles,
47979
+ "slow-cycles": nextSlowCycles
47980
+ };
47981
+ });
47982
+ }
46936
47983
  if (options?.prefetchClips) {
46937
47984
  if (percentilePrefetchRef.current.key !== requestKey) {
46938
47985
  percentilePrefetchRef.current = { key: requestKey, types: /* @__PURE__ */ new Set() };
@@ -46951,37 +47998,30 @@ var FileManagerFilters = ({
46951
47998
  }
46952
47999
  }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase, percentileCounts, percentileClips, fetchPercentileClips]);
46953
48000
  React125.useEffect(() => {
46954
- if (!showPercentileCycleFilters || !isReady || !percentileCountsKey) {
48001
+ if (!showPercentileCycleFilters || !isReady || !percentileCountsKey || isTimeFilterActive) {
46955
48002
  return;
46956
48003
  }
46957
- const schedule = () => {
46958
- fetchPercentileCounts({ prefetchClips: true });
46959
- };
46960
- if (typeof window !== "undefined" && "requestIdleCallback" in window) {
46961
- window.requestIdleCallback(schedule, { timeout: 1e3 });
46962
- } else {
46963
- setTimeout(schedule, 0);
46964
- }
46965
- }, [showPercentileCycleFilters, isReady, percentileCountsKey, fetchPercentileCounts]);
46966
- const shouldShowCategory = React125.useCallback((categoryId) => {
46967
- switch (categoryId) {
46968
- case "fast-cycles":
46969
- return showPercentileCycleFilters && filterState.showFastCycles;
46970
- case "slow-cycles":
46971
- return showPercentileCycleFilters && filterState.showSlowCycles;
46972
- case "longest-idles":
46973
- return false;
46974
- // filterState.showLongestIdles; // Temporarily disabled
46975
- case "cycle_completion":
46976
- return filterState.showCycleCompletion;
46977
- case "idle_time":
46978
- return filterState.showIdleTime;
46979
- case "sop_deviations":
46980
- return filterState.showSopDeviations;
46981
- default:
46982
- return true;
48004
+ fetchPercentileCounts({ prefetchClips: true });
48005
+ }, [showPercentileCycleFilters, isReady, percentileCountsKey, fetchPercentileCounts, isTimeFilterActive]);
48006
+ React125.useEffect(() => {
48007
+ if (!isReady || isTimeFilterActive || activeFilter !== RECENT_FLOW_RED_STREAK_CLIP_TYPE2) {
48008
+ return;
46983
48009
  }
46984
- }, [filterState, showPercentileCycleFilters]);
48010
+ ["cycle_completion", "idle_time"].forEach((categoryId) => {
48011
+ if ((counts?.[categoryId] || 0) <= 0 || hasKnownClipMetadata(categoryId) || isCategoryExternallyManaged(categoryId)) {
48012
+ return;
48013
+ }
48014
+ fetchClipMetadata(categoryId, 1);
48015
+ });
48016
+ }, [
48017
+ activeFilter,
48018
+ counts,
48019
+ fetchClipMetadata,
48020
+ hasKnownClipMetadata,
48021
+ isCategoryExternallyManaged,
48022
+ isReady,
48023
+ isTimeFilterActive
48024
+ ]);
46985
48025
  const getPercentileIcon = React125.useCallback((type, isExpanded, colorClasses) => {
46986
48026
  const iconMap = {
46987
48027
  "fast-cycles": { icon: lucideReact.TrendingUp, color: "text-green-600" },
@@ -47010,12 +48050,29 @@ var FileManagerFilters = ({
47010
48050
  newExpanded.add(activeFilter);
47011
48051
  return newExpanded;
47012
48052
  });
47013
- const category = categories.find((cat) => cat.id === activeFilter);
47014
- if (category && !isCategoryExternallyManaged(activeFilter) && !clipMetadataRef.current[activeFilter]) {
47015
- fetchClipMetadata(activeFilter, 1);
48053
+ const category = categoriesForTree.find((cat) => cat.id === activeFilter);
48054
+ if (category) {
48055
+ if (isInitialTimeFilterCategory(activeFilter) && !isCategoryExternallyManaged(activeFilter) && !hasCompleteMetadataForInitialTimeFilter(activeFilter)) {
48056
+ fetchClipMetadata(activeFilter, 1);
48057
+ } else if (!isCategoryExternallyManaged(activeFilter) && !clipMetadataRef.current[activeFilter]) {
48058
+ fetchClipMetadata(activeFilter, 1);
48059
+ }
47016
48060
  }
47017
48061
  }
47018
48062
  }, [activeFilter]);
48063
+ React125.useEffect(() => {
48064
+ const requestedCategory = activeFilter;
48065
+ if (!requestedCategory || !isInitialTimeFilterCategory(requestedCategory) || isCategoryExternallyManaged(requestedCategory) || hasCompleteMetadataForInitialTimeFilter(requestedCategory)) {
48066
+ return;
48067
+ }
48068
+ fetchClipMetadata(requestedCategory, 1);
48069
+ }, [
48070
+ activeFilter,
48071
+ fetchClipMetadata,
48072
+ hasCompleteMetadataForInitialTimeFilter,
48073
+ isCategoryExternallyManaged,
48074
+ isInitialTimeFilterCategory
48075
+ ]);
47019
48076
  React125.useEffect(() => {
47020
48077
  const handleEscape = (e) => {
47021
48078
  if (e.key === "Escape") {
@@ -47079,25 +48136,34 @@ var FileManagerFilters = ({
47079
48136
  }
47080
48137
  try {
47081
48138
  const clipDate = new Date(clipTimestamp);
47082
- const clipTimeStr = clipDate.toLocaleTimeString("en-US", {
48139
+ const clipParts = new Intl.DateTimeFormat("en-US", {
47083
48140
  hour12: false,
47084
48141
  hour: "2-digit",
47085
48142
  minute: "2-digit",
47086
- timeZone: timezone
47087
- });
47088
- return clipTimeStr >= startTime && clipTimeStr <= endTime;
48143
+ timeZone: activeTimeFilterTimezone
48144
+ }).formatToParts(clipDate);
48145
+ const hourValue = clipParts.find((part) => part.type === "hour")?.value;
48146
+ const minuteValue = clipParts.find((part) => part.type === "minute")?.value;
48147
+ const clipMinute = timeValueToMinutes(`${hourValue}:${minuteValue}`);
48148
+ return clipMinute === null ? false : isMinuteInTimeWindow(clipMinute, startTime, endTime);
47089
48149
  } catch (error) {
47090
48150
  console.error("[FileManager] Error parsing clip timestamp:", error);
47091
48151
  return true;
47092
48152
  }
47093
- }, [isTimeFilterActive, startTime, endTime, timezone]);
48153
+ }, [isTimeFilterActive, startTime, endTime, activeTimeFilterTimezone]);
47094
48154
  const filterTree = React125.useMemo(() => {
47095
48155
  const tree = [];
47096
48156
  const regularCategoryNodes = [];
47097
- categories.forEach((category) => {
48157
+ categoriesForTree.forEach((category) => {
48158
+ if (category.id === "fast-cycles" || category.id === "slow-cycles") {
48159
+ return;
48160
+ }
47098
48161
  const categoryCount = counts?.[category.id] || 0;
47099
- const categoryClips = clipMetadata[category.id] || [];
47100
- let filteredClips = categoryClips.filter((clip) => isClipInTimeRange(clip.clip_timestamp));
48162
+ const categoryMetadataClips = getAvailableClipMetadata(category.id);
48163
+ const categoryVideoFallbackClips = categoryMetadataClips.length === 0 && isInitialTimeFilterCategory(category.id) ? videos.filter((video) => video.type === category.id).map(buildClipMetadataFromVideo).filter((clip) => isClipInTimeRange(clip.clip_timestamp)) : [];
48164
+ const categoryClips = categoryMetadataClips.length > 0 ? categoryMetadataClips : categoryVideoFallbackClips;
48165
+ const timeFilteredClips = categoryClips.filter((clip) => isClipInTimeRange(clip.clip_timestamp));
48166
+ let filteredClips = timeFilteredClips;
47101
48167
  if (category.id === RECENT_FLOW_RED_STREAK_CLIP_TYPE2) {
47102
48168
  filteredClips = sortRedFlowMetadata(filteredClips);
47103
48169
  }
@@ -47107,9 +48173,22 @@ var FileManagerFilters = ({
47107
48173
  return classification?.label === idleLabelFilter;
47108
48174
  });
47109
48175
  }
47110
- const displayCount = isTimeFilterActive || category.id === "idle_time" && idleLabelFilter ? filteredClips.length : categoryCount;
48176
+ const scopedTotal = typeof scopedCategoryTotals[category.id] === "number" ? scopedCategoryTotals[category.id] : null;
48177
+ const isScopedByTimeFilter = isCategoryScopedByTimeFilter(category.id);
48178
+ const scopedPageLoaded = isScopedByTimeFilter && typeof scopedCategoryTotals[category.id] === "number";
48179
+ const scopedResponseHadOutOfHourClips = categoryClips.length > filteredClips.length;
48180
+ const shouldTrustScopedTotal = !scopedResponseHadOutOfHourClips || Boolean(categoryHasMore[category.id]);
48181
+ const isCategoryMetadataLoading = Array.from(loadingCategories).some((key) => key.startsWith(`${category.id}-`));
48182
+ const isScopedTotalPending = isTimeFilterActive && isScopedByTimeFilter && scopedTotal === null;
48183
+ const displayCount = isScopedTotalPending ? null : isTimeFilterActive && isScopedByTimeFilter && scopedTotal !== null ? shouldTrustScopedTotal ? scopedTotal : filteredClips.length : isTimeFilterActive || category.id === "idle_time" && idleLabelFilter ? filteredClips.length : categoryCount;
48184
+ const shouldShowScopedEmptyCategory = Boolean(
48185
+ scopedPageLoaded && !isCategoryMetadataLoading && !categoryHasMore[category.id] && filteredClips.length === 0 && (isRequiredHourlyFilterCategory(category.id) || activeFilter === category.id)
48186
+ );
48187
+ const shouldShowScopedLoadingCategory = Boolean(
48188
+ isScopedTotalPending && (isRequiredHourlyFilterCategory(category.id) || !activeInitialTimeFilter || activeFilter === category.id)
48189
+ );
47111
48190
  const shouldShowEmptyIdleTime = category.id === "idle_time" && idleLabelFilter;
47112
- if ((displayCount > 0 || shouldShowEmptyIdleTime) && shouldShowCategory(category.id)) {
48191
+ if ((typeof displayCount === "number" && displayCount > 0 || shouldShowEmptyIdleTime || isCategoryMetadataLoading || shouldShowScopedEmptyCategory || shouldShowScopedLoadingCategory) && shouldShowCategory(category.id)) {
47113
48192
  const colorClasses = getColorClasses(category.color);
47114
48193
  const clipNodes = filteredClips.map((clip, index) => {
47115
48194
  const cycleTime = extractCycleTimeSeconds(clip);
@@ -47119,7 +48198,7 @@ var FileManagerFilters = ({
47119
48198
  const baseTimeLabel = formatClipExplorerTimeLabel({
47120
48199
  categoryId: category.id,
47121
48200
  clipTimestamp: clip.clip_timestamp,
47122
- timezone,
48201
+ timezone: activeTimeFilterTimezone,
47123
48202
  durationSeconds: idleDuration ?? clip.duration,
47124
48203
  idleStartTime: clip.idle_start_time,
47125
48204
  idleEndTime: clip.idle_end_time,
@@ -47157,6 +48236,7 @@ var FileManagerFilters = ({
47157
48236
  type: "category",
47158
48237
  count: displayCount,
47159
48238
  // Use filtered count when time filter is active
48239
+ countLoading: isScopedTotalPending || isCategoryMetadataLoading && scopedTotal === null,
47160
48240
  children: clipNodes,
47161
48241
  // Use clip nodes from metadata
47162
48242
  icon: expandedNodes.has(category.id) ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: `h-4 w-4 ${colorClasses.text}` }) : getCategoryIcon(category.icon, colorClasses),
@@ -47164,10 +48244,18 @@ var FileManagerFilters = ({
47164
48244
  });
47165
48245
  }
47166
48246
  });
47167
- const filteredFastCycles = (percentileClips["fast-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""));
47168
- const filteredSlowCycles = (percentileClips["slow-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""));
48247
+ const filteredFastCycles = sortPercentileCycleClipsForDisplay(
48248
+ "fast-cycles",
48249
+ (percentileClips["fast-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""))
48250
+ );
48251
+ const filteredSlowCycles = sortPercentileCycleClipsForDisplay(
48252
+ "slow-cycles",
48253
+ (percentileClips["slow-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""))
48254
+ );
47169
48255
  const fastCount = typeof percentileCounts["fast-cycles"] === "number" ? percentileCounts["fast-cycles"] : null;
47170
48256
  const slowCount = typeof percentileCounts["slow-cycles"] === "number" ? percentileCounts["slow-cycles"] : null;
48257
+ const isFastCountPending = showPercentileCycleFilters && fastCount === null;
48258
+ const isSlowCountPending = showPercentileCycleFilters && slowCount === null;
47171
48259
  const percentileCategories = showPercentileCycleFilters ? [
47172
48260
  {
47173
48261
  id: "fast-cycles",
@@ -47176,6 +48264,7 @@ var FileManagerFilters = ({
47176
48264
  description: "Top 10% fastest performance",
47177
48265
  type: "percentile-category",
47178
48266
  count: isTimeFilterActive ? fastCount === null && filteredFastCycles.length === 0 ? null : filteredFastCycles.length : fastCount,
48267
+ countLoading: isFastCountPending,
47179
48268
  icon: getPercentileIcon("fast-cycles", expandedNodes.has("fast-cycles"), { text: "text-green-600" }),
47180
48269
  color: "green",
47181
48270
  percentileType: "fast-cycles",
@@ -47187,7 +48276,7 @@ var FileManagerFilters = ({
47187
48276
  label: `${formatClipExplorerTimeLabel({
47188
48277
  categoryId: "fast-cycles",
47189
48278
  clipTimestamp: clip.creation_timestamp || clip.timestamp || "",
47190
- timezone,
48279
+ timezone: activeTimeFilterTimezone,
47191
48280
  durationSeconds: cycleTime
47192
48281
  })}${clip.cycle_time_seconds ? ` - (${clip.cycle_time_seconds.toFixed(1)}s)` : ""}`,
47193
48282
  type: "video",
@@ -47207,6 +48296,7 @@ var FileManagerFilters = ({
47207
48296
  description: "Bottom 10% slowest performance",
47208
48297
  type: "percentile-category",
47209
48298
  count: isTimeFilterActive ? slowCount === null && filteredSlowCycles.length === 0 ? null : filteredSlowCycles.length : slowCount,
48299
+ countLoading: isSlowCountPending,
47210
48300
  icon: getPercentileIcon("slow-cycles", expandedNodes.has("slow-cycles"), { text: "text-red-600" }),
47211
48301
  color: "red",
47212
48302
  percentileType: "slow-cycles",
@@ -47218,7 +48308,7 @@ var FileManagerFilters = ({
47218
48308
  label: `${formatClipExplorerTimeLabel({
47219
48309
  categoryId: "slow-cycles",
47220
48310
  clipTimestamp: clip.creation_timestamp || clip.timestamp || "",
47221
- timezone,
48311
+ timezone: activeTimeFilterTimezone,
47222
48312
  durationSeconds: cycleTime
47223
48313
  })}${clip.cycle_time_seconds ? ` - (${clip.cycle_time_seconds.toFixed(1)}s)` : ""}`,
47224
48314
  type: "video",
@@ -47249,7 +48339,7 @@ var FileManagerFilters = ({
47249
48339
  hour12: true,
47250
48340
  hour: 'numeric',
47251
48341
  minute: '2-digit',
47252
- timeZone: timezone
48342
+ timeZone: activeTimeFilterTimezone
47253
48343
  });
47254
48344
 
47255
48345
  return {
@@ -47268,7 +48358,11 @@ var FileManagerFilters = ({
47268
48358
  const orderedIds = [RECENT_FLOW_RED_STREAK_CLIP_TYPE2, "cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
47269
48359
  orderedIds.forEach((orderedId) => {
47270
48360
  const percentileCategory = percentileCategories.find((cat) => cat.id === orderedId);
47271
- const shouldIncludePercentile = percentileCategory ? typeof percentileCategory.count === "number" && percentileCategory.count > 0 : false;
48361
+ const shouldIncludePercentile = percentileCategory ? typeof percentileCategory.count === "number" || Boolean(
48362
+ isTimeFilterActive && isRequiredHourlyFilterCategory(orderedId) && typeof percentileCategory.count === "number" && percentileCategory.count === 0
48363
+ ) || Boolean(
48364
+ percentileCategory.countLoading && (isRequiredHourlyFilterCategory(orderedId) || !activeInitialTimeFilter || activeFilter === orderedId)
48365
+ ) : false;
47272
48366
  if (percentileCategory && shouldIncludePercentile && shouldShowCategory(orderedId)) {
47273
48367
  tree.push(percentileCategory);
47274
48368
  }
@@ -47283,23 +48377,263 @@ var FileManagerFilters = ({
47283
48377
  }
47284
48378
  });
47285
48379
  percentileCategories.forEach((category) => {
47286
- const shouldIncludePercentile = typeof category.count === "number" && category.count > 0;
48380
+ const shouldIncludePercentile = typeof category.count === "number" || Boolean(
48381
+ isTimeFilterActive && isRequiredHourlyFilterCategory(category.id) && typeof category.count === "number" && category.count === 0
48382
+ ) || Boolean(
48383
+ category.countLoading && (isRequiredHourlyFilterCategory(category.id) || !activeInitialTimeFilter || activeFilter === category.id)
48384
+ );
47287
48385
  if (!orderedIds.includes(category.id) && shouldIncludePercentile && shouldShowCategory(category.id)) {
47288
48386
  tree.push(category);
47289
48387
  }
47290
48388
  });
47291
48389
  return tree;
47292
- }, [categories, expandedNodes, counts, clipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive, showPercentileCycleFilters]);
48390
+ }, [categoriesForTree, expandedNodes, counts, getAvailableClipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive, showPercentileCycleFilters, loadingCategories, activeTimeFilterTimezone, isInitialTimeFilterCategory, isRequiredHourlyFilterCategory, hasCompleteMetadataForInitialTimeFilter, scopedCategoryTotals, isCategoryScopedByTimeFilter, categoryHasMore, activeFilter]);
48391
+ const chartHandoffVideoFallbackTree = React125.useMemo(() => {
48392
+ if (!startTime || !endTime || !activeInitialTimeFilter || !activeFilter) {
48393
+ return [];
48394
+ }
48395
+ const fallbackCategory = categoriesForTree.find((category) => category.id === activeFilter) || categoriesForTree.find((category) => videos.some((video) => video.type === category.id));
48396
+ if (!fallbackCategory) {
48397
+ return [];
48398
+ }
48399
+ const fallbackVideos = videos.filter((video) => video.type === fallbackCategory.id);
48400
+ const sourceVideos = fallbackVideos.map(buildClipMetadataFromVideo).filter((clip) => isClipInTimeRange(clip.clip_timestamp));
48401
+ if (sourceVideos.length === 0) {
48402
+ return [];
48403
+ }
48404
+ const colorClasses = getColorClasses(fallbackCategory.color);
48405
+ const clipNodes = sourceVideos.map((clip, index) => {
48406
+ const cycleTime = extractCycleTimeSeconds(clip);
48407
+ const baseTimeLabel = formatClipExplorerTimeLabel({
48408
+ categoryId: fallbackCategory.id,
48409
+ clipTimestamp: clip.clip_timestamp,
48410
+ timezone: activeTimeFilterTimezone,
48411
+ durationSeconds: clip.duration,
48412
+ idleStartTime: clip.idle_start_time,
48413
+ idleEndTime: clip.idle_end_time,
48414
+ clipStartTime: clip.clip_start_time,
48415
+ clipEndTime: clip.clip_end_time
48416
+ });
48417
+ const displayLabel = `${baseTimeLabel}${clip.duration && fallbackCategory.id !== "idle_time" && fallbackCategory.id !== "low_value" && fallbackCategory.id !== RECENT_FLOW_RED_STREAK_CLIP_TYPE2 ? ` - (${clip.duration.toFixed(1)}s)` : ""}`;
48418
+ return {
48419
+ id: clip.id,
48420
+ label: displayLabel,
48421
+ type: "video",
48422
+ icon: getSeverityIcon(clip.severity, fallbackCategory.id, cycleTime, resolvedTargetCycleTime, clip.clipId),
48423
+ timestamp: clip.clip_timestamp,
48424
+ severity: clip.severity,
48425
+ clipId: clip.clipId,
48426
+ categoryId: fallbackCategory.id,
48427
+ clipPosition: index + 1,
48428
+ cycleTimeSeconds: cycleTime,
48429
+ duration: clip.duration,
48430
+ cycleItemCount: null,
48431
+ redFlowSeverityScore: clip.red_flow_severity_score ?? null,
48432
+ redFlowExplanationSummary: clip.red_flow_explanation_summary ?? clip.red_flow_explanation?.summary ?? null
48433
+ };
48434
+ });
48435
+ return [{
48436
+ id: activeFilter,
48437
+ label: fallbackCategory.id === RECENT_FLOW_RED_STREAK_CLIP_TYPE2 ? RECENT_FLOW_RED_STREAK_DISPLAY_LABEL : fallbackCategory.label,
48438
+ subtitle: fallbackCategory.subtitle || fallbackCategory.description,
48439
+ description: fallbackCategory.description,
48440
+ type: "category",
48441
+ count: clipNodes.length,
48442
+ children: clipNodes,
48443
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: `h-4 w-4 ${colorClasses.text}` }),
48444
+ color: fallbackCategory.color
48445
+ }];
48446
+ }, [
48447
+ activeFilter,
48448
+ activeInitialTimeFilter,
48449
+ activeTimeFilterTimezone,
48450
+ categoriesForTree,
48451
+ counts,
48452
+ currentVideoId,
48453
+ endTime,
48454
+ getDisplayValue,
48455
+ isClipInTimeRange,
48456
+ resolvedTargetCycleTime,
48457
+ startTime,
48458
+ videos
48459
+ ]);
48460
+ const displayedFilterTree = React125.useMemo(() => {
48461
+ if (chartHandoffVideoFallbackTree.length === 0) {
48462
+ return filterTree;
48463
+ }
48464
+ if (filterTree.length === 0) {
48465
+ return chartHandoffVideoFallbackTree;
48466
+ }
48467
+ const fallbackNode = chartHandoffVideoFallbackTree[0];
48468
+ const activeNode = filterTree.find((node) => node.id === activeFilter);
48469
+ const activeNodeHasChildren = (activeNode?.children?.length || 0) > 0;
48470
+ const shouldReplaceActiveNode = Boolean(
48471
+ activeFilter && fallbackNode && activeNode && !activeNodeHasChildren && isInitialTimeFilterCategory(activeFilter)
48472
+ );
48473
+ if (!shouldReplaceActiveNode) {
48474
+ return filterTree;
48475
+ }
48476
+ return filterTree.map((node) => node.id === activeFilter ? fallbackNode : node);
48477
+ }, [activeFilter, chartHandoffVideoFallbackTree, filterTree, isInitialTimeFilterCategory]);
48478
+ const getSelectionContextForNodes = React125.useCallback((categoryId, clipNodes) => {
48479
+ const normalizeSeverity = (severity) => severity === "low" || severity === "medium" || severity === "high" ? severity : "medium";
48480
+ const scopedNodes = clipNodes.filter((node) => node.type === "video" && Boolean(node.clipId || node.id));
48481
+ const scopedNodeClips = scopedNodes.map((node, index) => ({
48482
+ id: node.clipId || node.id,
48483
+ clipId: node.clipId || node.id,
48484
+ clip_timestamp: node.timestamp || "",
48485
+ creation_timestamp: node.timestamp || "",
48486
+ description: node.label,
48487
+ severity: normalizeSeverity(node.severity),
48488
+ category: categoryId,
48489
+ duration: node.duration,
48490
+ cycle_time_seconds: node.cycleTimeSeconds,
48491
+ index
48492
+ }));
48493
+ const categoryClips = getAvailableClipMetadata(categoryId);
48494
+ const contextClips = isCategoryScopedByTimeFilter(categoryId) && scopedNodeClips.length > 0 ? scopedNodeClips : categoryClips.length > 0 ? categoryClips : scopedNodeClips;
48495
+ const scopedTotal = scopedCategoryTotals[categoryId];
48496
+ const total = typeof scopedTotal === "number" ? scopedTotal : counts?.[categoryId];
48497
+ return contextClips.length ? { clips: contextClips, total } : void 0;
48498
+ }, [counts, getAvailableClipMetadata, isCategoryScopedByTimeFilter, scopedCategoryTotals]);
48499
+ React125.useEffect(() => {
48500
+ if (!activeInitialTimeFilter || !startTime || !endTime || currentVideoId || !onClipSelect) {
48501
+ return;
48502
+ }
48503
+ const categoryPriority = Array.from(new Set([
48504
+ activeInitialTimeFilter.categoryId,
48505
+ activeFilter,
48506
+ ...activeInitialTimeFilter.categoryIds || []
48507
+ ].filter((value) => typeof value === "string" && value.length > 0)));
48508
+ const preferredCategoryId = categoryPriority[0];
48509
+ const orderedCategoryNodes = displayedFilterTree.filter((categoryNode) => isInitialTimeFilterCategory(categoryNode.id)).sort((left, right) => {
48510
+ const leftIndex = categoryPriority.indexOf(left.id);
48511
+ const rightIndex = categoryPriority.indexOf(right.id);
48512
+ const leftPriority = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
48513
+ const rightPriority = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
48514
+ return leftPriority - rightPriority;
48515
+ });
48516
+ const preferredCategoryNode = preferredCategoryId ? orderedCategoryNodes.find((categoryNode) => categoryNode.id === preferredCategoryId) : void 0;
48517
+ if (preferredCategoryId && !preferredCategoryNode) {
48518
+ const preferredKnownTotal = preferredCategoryId === "fast-cycles" || preferredCategoryId === "slow-cycles" ? percentileCounts[preferredCategoryId] : scopedCategoryTotals[preferredCategoryId];
48519
+ if (preferredKnownTotal !== 0) {
48520
+ return;
48521
+ }
48522
+ }
48523
+ const preferredFirstClipNode = preferredCategoryNode?.children?.find((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48524
+ if (preferredCategoryId && preferredCategoryNode && !preferredFirstClipNode) {
48525
+ const preferredKnownTotal = preferredCategoryId === "fast-cycles" || preferredCategoryId === "slow-cycles" ? percentileCounts[preferredCategoryId] : scopedCategoryTotals[preferredCategoryId];
48526
+ if (preferredKnownTotal !== 0) {
48527
+ return;
48528
+ }
48529
+ }
48530
+ for (const categoryNode of orderedCategoryNodes) {
48531
+ const firstClipNode = categoryNode.children?.find((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48532
+ if (!firstClipNode?.clipId || !firstClipNode.categoryId) {
48533
+ continue;
48534
+ }
48535
+ const selectionKey = `${activeTimeFilterKey}:${firstClipNode.categoryId}:${firstClipNode.clipId}:${currentVideoId || "none"}`;
48536
+ if (autoSelectedScopedClipRef.current === selectionKey) {
48537
+ return;
48538
+ }
48539
+ autoSelectedScopedClipRef.current = selectionKey;
48540
+ const selectionContext = getSelectionContextForNodes(firstClipNode.categoryId, categoryNode.children || []);
48541
+ onClipSelect(
48542
+ firstClipNode.categoryId,
48543
+ firstClipNode.clipId,
48544
+ firstClipNode.clipPosition,
48545
+ selectionContext
48546
+ );
48547
+ return;
48548
+ }
48549
+ }, [
48550
+ activeInitialTimeFilter,
48551
+ activeFilter,
48552
+ activeTimeFilterKey,
48553
+ currentVideoId,
48554
+ displayedFilterTree,
48555
+ endTime,
48556
+ getSelectionContextForNodes,
48557
+ isInitialTimeFilterCategory,
48558
+ onClipSelect,
48559
+ percentileCounts,
48560
+ scopedCategoryTotals,
48561
+ startTime
48562
+ ]);
48563
+ const isChartHandoffLoading = React125.useMemo(() => {
48564
+ if (!startTime || !endTime || !activeInitialTimeFilter || !activeFilter || !isInitialTimeFilterCategory(activeFilter)) {
48565
+ return false;
48566
+ }
48567
+ const pageOneLoadingKey = getMetadataLoadingKey(activeFilter, 1);
48568
+ return Boolean(
48569
+ activeCategoryLoading || Array.from(loadingCategories).some((key) => key === pageOneLoadingKey || key.startsWith(`${pageOneLoadingKey}-all`))
48570
+ );
48571
+ }, [
48572
+ activeCategoryLoading,
48573
+ activeFilter,
48574
+ activeInitialTimeFilter,
48575
+ endTime,
48576
+ getMetadataLoadingKey,
48577
+ isInitialTimeFilterCategory,
48578
+ loadingCategories,
48579
+ startTime
48580
+ ]);
48581
+ React125.useEffect(() => {
48582
+ if (!isTimeFilterActive || !startTime || !endTime || !activeFilter || activeFilter === "all" || !onClipSelect) {
48583
+ return;
48584
+ }
48585
+ const activeCategoryNode = displayedFilterTree.find((categoryNode) => categoryNode.id === activeFilter);
48586
+ const activeClipNodes = (activeCategoryNode?.children || []).filter((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48587
+ if (activeClipNodes.length === 0) {
48588
+ return;
48589
+ }
48590
+ const currentClipIsVisible = Boolean(
48591
+ currentVideoId && activeClipNodes.some((child) => (child.clipId || child.id) === currentVideoId)
48592
+ );
48593
+ if (currentClipIsVisible) {
48594
+ return;
48595
+ }
48596
+ const firstClipNode = activeClipNodes[0];
48597
+ if (!firstClipNode?.clipId || !firstClipNode.categoryId) {
48598
+ return;
48599
+ }
48600
+ const selectionKey = `${activeTimeFilterKey}:${firstClipNode.categoryId}:${firstClipNode.clipId}:${currentVideoId || "none"}`;
48601
+ if (autoSelectedScopedClipRef.current === selectionKey) {
48602
+ return;
48603
+ }
48604
+ autoSelectedScopedClipRef.current = selectionKey;
48605
+ const selectionContext = getSelectionContextForNodes(firstClipNode.categoryId, activeClipNodes);
48606
+ onClipSelect(
48607
+ firstClipNode.categoryId,
48608
+ firstClipNode.clipId,
48609
+ firstClipNode.clipPosition,
48610
+ selectionContext
48611
+ );
48612
+ }, [
48613
+ activeFilter,
48614
+ activeTimeFilterKey,
48615
+ currentVideoId,
48616
+ displayedFilterTree,
48617
+ endTime,
48618
+ getSelectionContextForNodes,
48619
+ isTimeFilterActive,
48620
+ onClipSelect,
48621
+ startTime
48622
+ ]);
47293
48623
  const toggleExpanded = (nodeId) => {
47294
48624
  const newExpanded = new Set(expandedNodes);
47295
48625
  if (newExpanded.has(nodeId)) {
47296
48626
  newExpanded.delete(nodeId);
47297
48627
  } else {
47298
48628
  newExpanded.add(nodeId);
47299
- const category = categories.find((cat) => cat.id === nodeId);
47300
- if (category && !clipMetadata[nodeId] && !isCategoryExternallyManaged(nodeId)) {
48629
+ const category = categoriesForTree.find((cat) => cat.id === nodeId);
48630
+ if (category) {
47301
48631
  console.log(`[FileManager] Fetching clips for expanded category: ${nodeId}`);
47302
- fetchClipMetadata(nodeId, 1);
48632
+ if (isInitialTimeFilterCategory(nodeId) && !hasCompleteMetadataForInitialTimeFilter(nodeId)) {
48633
+ fetchClipMetadata(nodeId, 1);
48634
+ } else if (!hasKnownClipMetadata(nodeId) && !isCategoryExternallyManaged(nodeId)) {
48635
+ fetchClipMetadata(nodeId, 1);
48636
+ }
47303
48637
  }
47304
48638
  if (!isCategoryExternallyManaged(nodeId) && showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
47305
48639
  fetchPercentileClips("fast-cycles");
@@ -47317,10 +48651,14 @@ var FileManagerFilters = ({
47317
48651
  newExpanded.delete(node.id);
47318
48652
  } else {
47319
48653
  newExpanded.add(node.id);
47320
- const category = categories.find((cat) => cat.id === node.id);
47321
- if (category && !clipMetadata[node.id] && !isCategoryExternallyManaged(node.id)) {
48654
+ const category = categoriesForTree.find((cat) => cat.id === node.id);
48655
+ if (category) {
47322
48656
  console.log(`[FileManager] Fetching clips for expanded category: ${node.id}`);
47323
- fetchClipMetadata(node.id, 1);
48657
+ if (isInitialTimeFilterCategory(node.id) && !hasCompleteMetadataForInitialTimeFilter(node.id)) {
48658
+ fetchClipMetadata(node.id, 1);
48659
+ } else if (!hasKnownClipMetadata(node.id) && !isCategoryExternallyManaged(node.id)) {
48660
+ fetchClipMetadata(node.id, 1);
48661
+ }
47324
48662
  }
47325
48663
  if (!isCategoryExternallyManaged(node.id) && showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
47326
48664
  fetchPercentileClips("fast-cycles");
@@ -47330,7 +48668,21 @@ var FileManagerFilters = ({
47330
48668
  }
47331
48669
  }
47332
48670
  setExpandedNodes(newExpanded);
47333
- onFilterChange(node.id);
48671
+ if (node.id !== activeFilter) {
48672
+ onFilterChange(node.id);
48673
+ }
48674
+ if (isCategoryScopedByTimeFilter(node.id) && onClipSelect && node.children?.length) {
48675
+ const firstClipNode = node.children.find((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48676
+ if (firstClipNode?.clipId && firstClipNode.categoryId) {
48677
+ const selectionContext = getSelectionContextForNodes(firstClipNode.categoryId, node.children);
48678
+ onClipSelect(
48679
+ firstClipNode.categoryId,
48680
+ firstClipNode.clipId,
48681
+ firstClipNode.clipPosition,
48682
+ selectionContext
48683
+ );
48684
+ }
48685
+ }
47334
48686
  if (node.id === "fast-cycles") {
47335
48687
  trackCoreEvent("Fast Clips Clicked", {
47336
48688
  workspaceId,
@@ -47368,8 +48720,11 @@ var FileManagerFilters = ({
47368
48720
  } else if (node.type === "video") {
47369
48721
  if (onClipSelect && node.categoryId !== void 0 && node.clipId !== void 0) {
47370
48722
  console.log(`[FileManager] Selecting clip: category=${node.categoryId}, clipId=${node.clipId}, position=${node.clipPosition}`);
47371
- const categoryClips = node.categoryId ? clipMetadata[node.categoryId] : void 0;
47372
- const selectionContext = categoryClips?.length ? { clips: categoryClips, total: counts?.[node.categoryId] } : void 0;
48723
+ const categoryNode = displayedFilterTree.find((candidate) => candidate.id === node.categoryId);
48724
+ const selectionContext = getSelectionContextForNodes(
48725
+ node.categoryId,
48726
+ categoryNode?.children || [node]
48727
+ );
47373
48728
  onClipSelect(node.categoryId, node.clipId, node.clipPosition, selectionContext);
47374
48729
  } else {
47375
48730
  const videoIndex = videos.findIndex((v) => v.id === node.id);
@@ -47382,24 +48737,40 @@ var FileManagerFilters = ({
47382
48737
  const renderNode = (node, depth = 0) => {
47383
48738
  const isExpanded = expandedNodes.has(node.id);
47384
48739
  const isActive = activeFilter === node.id;
47385
- const isCurrentVideo = currentVideoId === node.id;
47386
- const isCountUnknown = node.type === "percentile-category" && node.count === null;
47387
- const hasChildren = isCountUnknown || (node.count || 0) > 0;
47388
- const hasLoadedChildren = (clipMetadata[node.id]?.length || percentileClips[node.id]?.length || 0) > 0;
48740
+ const nodeClipId = node.clipId || node.id;
48741
+ const isCurrentVideo = Boolean(
48742
+ node.type === "video" && currentVideoId && currentVideoId === nodeClipId
48743
+ );
48744
+ const isCountUnknown = Boolean(
48745
+ (node.type === "category" || node.type === "percentile-category") && (node.count === null || node.countLoading)
48746
+ );
48747
+ const hasRenderedChildren = (node.children?.length || 0) > 0;
47389
48748
  const loadedPage = categoryPages[node.id] || 0;
47390
48749
  const pageOneLoadingKey = getMetadataLoadingKey(node.id, 1);
47391
48750
  const nextMetadataPage = loadedPage + 1;
47392
48751
  const nextLoadMorePage = (categoryPages[node.id] || 1) + 1;
47393
48752
  const isPageOneLoading = loadingCategories.has(pageOneLoadingKey);
48753
+ const isFullCategoryLoading = Array.from(loadingCategories).some((key) => key.startsWith(`${pageOneLoadingKey}-all`));
47394
48754
  const isLoadMoreLoading = loadedPage > 0 && loadingCategories.has(getMetadataLoadingKey(node.id, nextMetadataPage));
48755
+ const isNodeLoading = Boolean(
48756
+ isPageOneLoading || isFullCategoryLoading || activeCategoryLoading && node.id === activeFilter
48757
+ );
48758
+ const totalForLoadMore = isCategoryScopedByTimeFilter(node.id) && typeof scopedCategoryTotals[node.id] === "number" ? scopedCategoryTotals[node.id] : counts?.[node.id] || 0;
47395
48759
  const showInitialLoadingState = Boolean(
47396
- isExpanded && !hasLoadedChildren && (node.type === "category" || node.type === "percentile-category") && (isPageOneLoading || activeCategoryLoading && node.id === activeFilter)
48760
+ isExpanded && !hasRenderedChildren && (node.type === "category" || node.type === "percentile-category") && isNodeLoading
48761
+ );
48762
+ const showScopedEmptyState = Boolean(
48763
+ isExpanded && !hasRenderedChildren && !isNodeLoading && !categoryHasMore[node.id] && isCategoryScopedByTimeFilter(node.id) && (node.type === "category" || node.type === "percentile-category")
47397
48764
  );
48765
+ const hasChildren = isCountUnknown || (node.count || 0) > 0 || isNodeLoading || showScopedEmptyState;
47398
48766
  const colorClasses = node.color ? getColorClasses(node.color) : null;
47399
48767
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "select-none animate-in", children: [
47400
48768
  /* @__PURE__ */ jsxRuntime.jsxs(
47401
48769
  "div",
47402
48770
  {
48771
+ "aria-current": isCurrentVideo ? "true" : void 0,
48772
+ "data-qa-clips-row-clip-id": node.type === "video" ? nodeClipId : void 0,
48773
+ "data-qa-clips-row-category-id": node.type === "video" ? node.categoryId : void 0,
47403
48774
  className: `flex items-center cursor-pointer transition-all duration-300 ease-out group relative overflow-hidden ${node.type === "category" && depth === 0 ? `py-3 px-4 rounded-2xl hover:bg-gradient-to-r hover:from-slate-50 hover:to-blue-50/30 hover:shadow-lg hover:shadow-blue-100/20 hover:scale-[1.02] hover:-translate-y-0.5 ${isActive ? "bg-gradient-to-r from-blue-50 via-blue-50/80 to-indigo-50/60 border border-blue-200/50 shadow-lg shadow-blue-100/30 scale-[1.02]" : "border border-transparent"}` : `py-2 px-3 rounded-xl hover:bg-gradient-to-r hover:from-slate-50 hover:to-slate-50/50 hover:shadow-sm ${isActive ? "bg-gradient-to-r from-blue-50/80 to-blue-50/40 border border-blue-200/30 shadow-sm" : "border border-transparent"} ${isCurrentVideo ? "bg-gradient-to-r from-blue-50 to-blue-50/60 border border-blue-200/50 shadow-md shadow-blue-100/20" : ""}`} ${node.type === "video" ? "ml-6" : ""}`,
47404
48775
  onClick: () => handleNodeClick(node),
47405
48776
  children: [
@@ -47435,7 +48806,7 @@ var FileManagerFilters = ({
47435
48806
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex items-center justify-between", children: [
47436
48807
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
47437
48808
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: `font-semibold tracking-tight ${node.type === "category" || node.type === "percentile-category" ? "text-slate-800 text-sm" : "text-slate-700 text-xs"} ${isCurrentVideo ? "text-blue-700 font-bold" : ""} group-hover:text-slate-900 transition-colors duration-200`, children: node.label }),
47438
- node.type === "category" && (node.subtitle || categories.find((c) => c.id === node.id)?.description) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 mt-0.5 font-normal", children: node.subtitle || categories.find((c) => c.id === node.id)?.description }),
48809
+ node.type === "category" && (node.subtitle || categoriesForTree.find((c) => c.id === node.id)?.description) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 mt-0.5 font-normal", children: node.subtitle || categoriesForTree.find((c) => c.id === node.id)?.description }),
47439
48810
  node.type === "percentile-category" && node.subtitle && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 mt-0.5 font-normal", children: node.subtitle }),
47440
48811
  node.type === "video" && (node.severity || node.categoryId === "cycle_completion" || node.categoryId === "idle_time" || node.categoryId === "low_value" || node.categoryId === RECENT_FLOW_RED_STREAK_CLIP_TYPE2) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs mt-0.5 font-medium", children: node.categoryId === "idle_time" ? (
47441
48812
  // Show root cause label for idle time clips (text only, icon is on the left)
@@ -47471,7 +48842,7 @@ var FileManagerFilters = ({
47471
48842
  })()
47472
48843
  ) })
47473
48844
  ] }),
47474
- node.count !== void 0 && (node.type === "category" || node.type === "percentile-category") && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center ml-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2.5 py-1 text-sm font-bold rounded-lg shadow-sm border backdrop-blur-sm flex-shrink-0 group-hover:scale-105 transition-all duration-200 ${colorClasses ? `${colorClasses.bg} ${colorClasses.text} ${colorClasses.border} bg-opacity-80` : "bg-slate-100/80 text-slate-700 border-slate-200/60"}`, children: node.count === null ? "\u2014" : node.count }) })
48845
+ node.count !== void 0 && (node.type === "category" || node.type === "percentile-category") && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center ml-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2.5 py-1 text-sm font-bold rounded-lg shadow-sm border backdrop-blur-sm flex-shrink-0 group-hover:scale-105 transition-all duration-200 ${colorClasses ? `${colorClasses.bg} ${colorClasses.text} ${colorClasses.border} bg-opacity-80` : "bg-slate-100/80 text-slate-700 border-slate-200/60"}`, children: isCountUnknown ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : node.count }) })
47475
48846
  ] })
47476
48847
  ]
47477
48848
  }
@@ -47482,6 +48853,7 @@ var FileManagerFilters = ({
47482
48853
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
47483
48854
  "Loading clips..."
47484
48855
  ] }) }),
48856
+ showScopedEmptyState && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 px-3 text-center text-sm text-slate-500", children: "No clips found" }),
47485
48857
  isLoadMoreLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center text-sm text-slate-500", children: [
47486
48858
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin mr-2 h-4 w-4 border-2 border-slate-300 border-t-blue-500 rounded-full" }),
47487
48859
  "Loading more clips..."
@@ -47496,7 +48868,7 @@ var FileManagerFilters = ({
47496
48868
  className: "w-full py-2 px-3 text-sm text-blue-600 hover:bg-blue-50 rounded-lg transition-colors duration-200 text-center",
47497
48869
  children: [
47498
48870
  "Load more clips (",
47499
- (counts?.[node.id] || 0) - (clipMetadata[node.id]?.length || 0),
48871
+ Math.max(0, totalForLoadMore - getAvailableClipMetadata(node.id).length),
47500
48872
  " remaining)"
47501
48873
  ]
47502
48874
  }
@@ -47559,6 +48931,8 @@ var FileManagerFilters = ({
47559
48931
  e.stopPropagation();
47560
48932
  setStartTime("");
47561
48933
  setEndTime("");
48934
+ setActiveInitialTimeFilter(null);
48935
+ setScopedCategoryTotals({});
47562
48936
  setIsTimeFilterActive(false);
47563
48937
  },
47564
48938
  className: "rounded-full p-0.5 transition-colors hover:bg-blue-100",
@@ -47692,6 +49066,8 @@ var FileManagerFilters = ({
47692
49066
  onClick: () => {
47693
49067
  setStartTime("");
47694
49068
  setEndTime("");
49069
+ setActiveInitialTimeFilter(null);
49070
+ setScopedCategoryTotals({});
47695
49071
  setStartSearchTerm("");
47696
49072
  setEndSearchTerm("");
47697
49073
  setIsTimeFilterActive(false);
@@ -47780,8 +49156,13 @@ var FileManagerFilters = ({
47780
49156
  )
47781
49157
  ] }),
47782
49158
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3 flex-1 min-h-0 overflow-y-auto scrollbar-thin", children: [
47783
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: filterTree.map((node) => renderNode(node)) }),
47784
- filterTree.length === 0 && (startTime || endTime) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
49159
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: displayedFilterTree.map((node) => renderNode(node)) }),
49160
+ displayedFilterTree.length === 0 && isChartHandoffLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
49161
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-8 w-8 animate-spin text-blue-500" }) }),
49162
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "Loading clips..." }),
49163
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500", children: "Finding clips from the selected hour" })
49164
+ ] }),
49165
+ displayedFilterTree.length === 0 && !isChartHandoffLoading && (startTime || endTime) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
47785
49166
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-12 w-12 mx-auto" }) }),
47786
49167
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clips found" }),
47787
49168
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500 mb-4", children: "No clips match the selected time range" }),
@@ -47791,6 +49172,8 @@ var FileManagerFilters = ({
47791
49172
  onClick: () => {
47792
49173
  setStartTime("");
47793
49174
  setEndTime("");
49175
+ setActiveInitialTimeFilter(null);
49176
+ setScopedCategoryTotals({});
47794
49177
  setIsTimeFilterActive(false);
47795
49178
  },
47796
49179
  className: "inline-flex items-center px-4 py-2 bg-blue-50 text-blue-600 text-sm font-medium rounded-lg hover:bg-blue-100 transition-colors duration-200",
@@ -47798,12 +49181,12 @@ var FileManagerFilters = ({
47798
49181
  }
47799
49182
  )
47800
49183
  ] }),
47801
- filterTree.length === 0 && !startTime && !endTime && categories.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
49184
+ displayedFilterTree.length === 0 && !startTime && !endTime && categories.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
47802
49185
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HelpCircle, { className: "h-12 w-12 mx-auto" }) }),
47803
49186
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clip types available" }),
47804
49187
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500", children: "Loading clip categories..." })
47805
49188
  ] }),
47806
- filterTree.length === 0 && !startTime && !endTime && categories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
49189
+ displayedFilterTree.length === 0 && !startTime && !endTime && categories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
47807
49190
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-12 w-12 mx-auto" }) }),
47808
49191
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clips available" }),
47809
49192
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500", children: "No clips found for the selected time period" })
@@ -48210,6 +49593,65 @@ function useClipsRealtimeUpdates({
48210
49593
  }
48211
49594
  var LOW_EFFICIENCY_CATEGORY_ID = "recent_flow_red_streak";
48212
49595
  var LOW_EFFICIENCY_AI_SUMMARY_ENABLED = process.env.NEXT_PUBLIC_LOW_EFFICIENCY_AI_SUMMARY_ENABLED === "true";
49596
+ var timeValueToMinutes2 = (value) => {
49597
+ const [hourValue, minuteValue] = value.substring(0, 5).split(":").map(Number);
49598
+ if (!Number.isInteger(hourValue) || !Number.isInteger(minuteValue) || hourValue < 0 || hourValue > 23 || minuteValue < 0 || minuteValue > 59) {
49599
+ return null;
49600
+ }
49601
+ return hourValue * 60 + minuteValue;
49602
+ };
49603
+ var isMinuteInTimeWindow2 = (minute, startValue, endValue) => {
49604
+ const startMinute = timeValueToMinutes2(startValue);
49605
+ const endMinute = timeValueToMinutes2(endValue);
49606
+ if (startMinute === null || endMinute === null) {
49607
+ return true;
49608
+ }
49609
+ return endMinute > startMinute ? minute >= startMinute && minute < endMinute : minute >= startMinute || minute < endMinute;
49610
+ };
49611
+ var CHART_HANDOFF_CATEGORY_FALLBACKS = {
49612
+ cycle_completion: {
49613
+ label: "Cycle Completion",
49614
+ description: "Successfully completed production cycles",
49615
+ color: "blue",
49616
+ icon: "check-circle"
49617
+ },
49618
+ "fast-cycles": {
49619
+ label: "Fast Cycles",
49620
+ description: "Fastest cycle clips",
49621
+ color: "green",
49622
+ icon: "trending-up"
49623
+ },
49624
+ "slow-cycles": {
49625
+ label: "Slow Cycles",
49626
+ description: "Slowest cycle clips",
49627
+ color: "red",
49628
+ icon: "trending-down"
49629
+ },
49630
+ idle_time: {
49631
+ label: "Idle Time",
49632
+ description: "Low value activities and idle moments",
49633
+ color: "amber",
49634
+ icon: "clock"
49635
+ },
49636
+ recent_flow_red_streak: {
49637
+ label: "Low moments",
49638
+ description: "Moments of low efficiency",
49639
+ color: "rose",
49640
+ icon: "alert-triangle"
49641
+ },
49642
+ worst_cycle_time: {
49643
+ label: "Slow Cycles",
49644
+ description: "Slowest cycle clips",
49645
+ color: "red",
49646
+ icon: "trending-down"
49647
+ },
49648
+ best_cycle_time: {
49649
+ label: "Fast Cycles",
49650
+ description: "Fastest cycle clips",
49651
+ color: "green",
49652
+ icon: "trending-up"
49653
+ }
49654
+ };
48213
49655
  var parseFiniteNumber2 = (value) => {
48214
49656
  if (typeof value === "number" && Number.isFinite(value)) {
48215
49657
  return value;
@@ -48257,6 +49699,7 @@ var BottlenecksContent = ({
48257
49699
  ticketId,
48258
49700
  prefetchedPercentileCounts,
48259
49701
  lowMomentsPrefetch,
49702
+ initialTimePrefetch,
48260
49703
  initialTimeFilter
48261
49704
  }) => {
48262
49705
  console.log("\u{1F3AB} [BottlenecksContent] Rendered with ticketId:", ticketId || "NONE", "workspaceId:", workspaceId, "date:", date, "shift:", shift);
@@ -48469,6 +49912,108 @@ var BottlenecksContent = ({
48469
49912
  enabled: isEffectiveShiftReady
48470
49913
  });
48471
49914
  const isLowMomentsCategoryAvailable = React125.useMemo(() => Array.isArray(clipTypes) && clipTypes.some((type) => type?.type === LOW_EFFICIENCY_CATEGORY_ID || type?.id === LOW_EFFICIENCY_CATEGORY_ID), [clipTypes]);
49915
+ const requestedInitialCategoryCandidates = React125.useMemo(() => {
49916
+ const requestedCategories = [
49917
+ ...Array.isArray(initialTimeFilter?.categoryIds) ? initialTimeFilter.categoryIds : [],
49918
+ initialTimeFilter?.categoryId
49919
+ ];
49920
+ return Array.from(
49921
+ new Set(
49922
+ requestedCategories.filter((category) => Boolean(category)).map((category) => category.trim()).filter(Boolean)
49923
+ )
49924
+ );
49925
+ }, [initialTimeFilter?.categoryId, initialTimeFilter?.categoryIds]);
49926
+ const hasInitialTimeHandoff = Boolean(initialTimeFilter?.startTime && initialTimeFilter?.endTime);
49927
+ const initialTimeHandoffCategorySet = React125.useMemo(() => new Set(requestedInitialCategoryCandidates), [requestedInitialCategoryCandidates]);
49928
+ const isInitialTimeHandoffCategory = React125.useCallback((categoryId) => Boolean(
49929
+ hasInitialTimeHandoff && categoryId && (initialTimeHandoffCategorySet.size === 0 || initialTimeHandoffCategorySet.has(categoryId))
49930
+ ), [hasInitialTimeHandoff, initialTimeHandoffCategorySet]);
49931
+ const isTimestampInInitialTimeHandoff = React125.useCallback((timestamp) => {
49932
+ if (!hasInitialTimeHandoff || !initialTimeFilter?.startTime || !initialTimeFilter?.endTime || !timestamp) {
49933
+ return true;
49934
+ }
49935
+ try {
49936
+ const clipDate = new Date(timestamp);
49937
+ if (Number.isNaN(clipDate.getTime())) {
49938
+ return false;
49939
+ }
49940
+ const clipParts = new Intl.DateTimeFormat("en-US", {
49941
+ hour12: false,
49942
+ hour: "2-digit",
49943
+ minute: "2-digit",
49944
+ timeZone: initialTimeFilter.timezone || timezone
49945
+ }).formatToParts(clipDate);
49946
+ const hourValue = clipParts.find((part) => part.type === "hour")?.value;
49947
+ const minuteValue = clipParts.find((part) => part.type === "minute")?.value;
49948
+ const clipMinute = timeValueToMinutes2(`${hourValue}:${minuteValue}`);
49949
+ return clipMinute === null ? false : isMinuteInTimeWindow2(clipMinute, initialTimeFilter.startTime, initialTimeFilter.endTime);
49950
+ } catch {
49951
+ return false;
49952
+ }
49953
+ }, [
49954
+ hasInitialTimeHandoff,
49955
+ initialTimeFilter?.endTime,
49956
+ initialTimeFilter?.startTime,
49957
+ initialTimeFilter?.timezone,
49958
+ timezone
49959
+ ]);
49960
+ const resolvedInitialCategory = React125.useMemo(() => resolveInitialClipCategory(requestedInitialCategoryCandidates, clipTypes, dynamicCounts), [clipTypes, dynamicCounts, requestedInitialCategoryCandidates]);
49961
+ const initialTimeFilterKey = React125.useMemo(() => hasInitialTimeHandoff ? [
49962
+ initialTimeFilter?.startTime,
49963
+ initialTimeFilter?.endTime,
49964
+ initialTimeFilter?.timezone || timezone,
49965
+ initialTimeFilter?.categoryId || "",
49966
+ ...initialTimeFilter?.categoryIds || []
49967
+ ].join("|") : "", [
49968
+ hasInitialTimeHandoff,
49969
+ initialTimeFilter?.categoryId,
49970
+ initialTimeFilter?.categoryIds,
49971
+ initialTimeFilter?.endTime,
49972
+ initialTimeFilter?.startTime,
49973
+ initialTimeFilter?.timezone,
49974
+ timezone
49975
+ ]);
49976
+ const preferredInitialTimeCategory = resolvedInitialCategory || requestedInitialCategoryCandidates[0] || "";
49977
+ React125.useEffect(() => {
49978
+ if (!initialTimeFilterKey) {
49979
+ return;
49980
+ }
49981
+ setPendingVideo(null);
49982
+ setAllVideos([]);
49983
+ setCurrentIndex(0);
49984
+ setCurrentClipId(null);
49985
+ currentClipIdRef.current = null;
49986
+ setCurrentMetadataIndex(0);
49987
+ currentMetadataIndexRef.current = 0;
49988
+ setCurrentPosition(0);
49989
+ currentPositionRef.current = 0;
49990
+ setCurrentTotal(0);
49991
+ currentTotalRef.current = 0;
49992
+ setCategoryMetadata([]);
49993
+ categoryMetadataRef.current = [];
49994
+ setCategoryMetadataCategoryId(null);
49995
+ setCategoryMetadataSort(null);
49996
+ clearRetryTimeout();
49997
+ navigationLockRef.current = false;
49998
+ loadingCategoryRef.current = null;
49999
+ if (loadingTimeoutRef.current) {
50000
+ clearTimeout(loadingTimeoutRef.current);
50001
+ loadingTimeoutRef.current = null;
50002
+ }
50003
+ setIsTransitioning(false);
50004
+ setIsNavigating(false);
50005
+ setIsCategoryLoading(false);
50006
+ if (preferredInitialTimeCategory) {
50007
+ setInitialFilter(preferredInitialTimeCategory);
50008
+ updateActiveFilter(preferredInitialTimeCategory);
50009
+ previousFilterRef.current = "";
50010
+ }
50011
+ }, [
50012
+ clearRetryTimeout,
50013
+ initialTimeFilterKey,
50014
+ preferredInitialTimeCategory,
50015
+ updateActiveFilter
50016
+ ]);
48472
50017
  console.log("[BottlenecksContent] Clip types data:", {
48473
50018
  clipTypes,
48474
50019
  clipTypesLength: clipTypes?.length,
@@ -48516,6 +50061,15 @@ var BottlenecksContent = ({
48516
50061
  }
48517
50062
  });
48518
50063
  React125.useEffect(() => {
50064
+ if (resolvedInitialCategory) {
50065
+ if (initialFilter !== resolvedInitialCategory) {
50066
+ setInitialFilter(resolvedInitialCategory);
50067
+ }
50068
+ if (activeFilterRef.current !== resolvedInitialCategory) {
50069
+ updateActiveFilter(resolvedInitialCategory);
50070
+ }
50071
+ return;
50072
+ }
48519
50073
  if (initialFilter) {
48520
50074
  return;
48521
50075
  }
@@ -48532,7 +50086,15 @@ var BottlenecksContent = ({
48532
50086
  activeFilterRef.current = defaultCategory;
48533
50087
  return;
48534
50088
  }
48535
- }, [clipTypes, dynamicCounts, defaultCategory, initialFilter, isLowMomentsCategoryAvailable]);
50089
+ }, [
50090
+ clipTypes,
50091
+ dynamicCounts,
50092
+ defaultCategory,
50093
+ initialFilter,
50094
+ isLowMomentsCategoryAvailable,
50095
+ resolvedInitialCategory,
50096
+ updateActiveFilter
50097
+ ]);
48536
50098
  const mergedCounts = React125.useMemo(() => {
48537
50099
  return { ...dynamicCounts };
48538
50100
  }, [dynamicCounts]);
@@ -48581,6 +50143,9 @@ var BottlenecksContent = ({
48581
50143
  const loadFirstVideoForCategory = React125.useCallback(async (category) => {
48582
50144
  if (!workspaceId || !s3ClipsService || !isMountedRef.current || !isEffectiveShiftReady) return;
48583
50145
  const targetCategory = category || activeFilterRef.current;
50146
+ if (targetCategory === "fast-cycles" || targetCategory === "slow-cycles") {
50147
+ return;
50148
+ }
48584
50149
  const operationKey = `loadFirstVideo:${targetCategory}:${effectiveDateString}:${effectiveShiftId}`;
48585
50150
  if (loadingCategoryRef.current === targetCategory || fetchInProgressRef.current.has(operationKey)) {
48586
50151
  console.log(`[BottlenecksContent] Load first video already in progress for ${targetCategory}`);
@@ -49026,8 +50591,9 @@ var BottlenecksContent = ({
49026
50591
  });
49027
50592
  }
49028
50593
  setVisibleCategoryMetadata(categoryId, cachedMetadata);
49029
- if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
49030
- const firstClipMeta = cachedMetadata[0];
50594
+ const cachedAutoloadCandidates = isInitialTimeHandoffCategory(categoryId) ? cachedMetadata.filter((clip) => isTimestampInInitialTimeHandoff(clip.clip_timestamp || clip.creation_timestamp || clip.timestamp)) : cachedMetadata;
50595
+ if (autoLoadFirstVideo && cachedAutoloadCandidates.length > 0 && s3ClipsService) {
50596
+ const firstClipMeta = cachedAutoloadCandidates[0];
49031
50597
  const firstClipId = firstClipMeta.clipId || firstClipMeta.id;
49032
50598
  const prefetchedFirstVideo = matchingLowMomentsPrefetch?.firstVideo ?? null;
49033
50599
  try {
@@ -49066,7 +50632,10 @@ var BottlenecksContent = ({
49066
50632
  endDate: `${resolvedDate}T23:59:59Z`,
49067
50633
  percentile: 10,
49068
50634
  shiftId: effectiveShiftId,
49069
- limit: 100
50635
+ limit: isInitialTimeHandoffCategory(categoryId) ? 500 : 100,
50636
+ startTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.startTime : void 0,
50637
+ endTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.endTime : void 0,
50638
+ timeFilterTimezone: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.timezone || timezone : void 0
49070
50639
  }),
49071
50640
  redirectReason: "session_expired"
49072
50641
  });
@@ -49083,10 +50652,13 @@ var BottlenecksContent = ({
49083
50652
  shift: effectiveShiftId,
49084
50653
  category: categoryId,
49085
50654
  page: 1,
49086
- limit: 100,
49087
- knownTotal: mergedCounts[categoryId] ?? null,
50655
+ limit: isInitialTimeHandoffCategory(categoryId) ? 500 : 100,
50656
+ knownTotal: isInitialTimeHandoffCategory(categoryId) ? null : mergedCounts[categoryId] ?? null,
49088
50657
  snapshotDateTime,
49089
50658
  snapshotClipId,
50659
+ startTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.startTime : void 0,
50660
+ endTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.endTime : void 0,
50661
+ timeFilterTimezone: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.timezone || timezone : void 0,
49090
50662
  sort: categoryId === LOW_EFFICIENCY_CATEGORY_ID ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"
49091
50663
  }),
49092
50664
  redirectReason: "session_expired"
@@ -49099,7 +50671,8 @@ var BottlenecksContent = ({
49099
50671
  if (categoryData.clips && isMountedRef.current) {
49100
50672
  let metadataClips;
49101
50673
  if (isPercentileCategory(categoryId)) {
49102
- metadataClips = categoryData.clips.map((clip, index) => ({
50674
+ const sortedPercentileClips = sortPercentileCycleClipsForDisplay(categoryId, categoryData.clips);
50675
+ metadataClips = sortedPercentileClips.map((clip, index) => ({
49103
50676
  id: clip.id,
49104
50677
  clipId: clip.id,
49105
50678
  clip_timestamp: clip.creation_timestamp || clip.timestamp,
@@ -49146,8 +50719,9 @@ var BottlenecksContent = ({
49146
50719
  }));
49147
50720
  setVisibleCategoryMetadata(categoryId, metadataClips);
49148
50721
  console.log(`[BottlenecksContent] Loaded metadata for ${categoryId}: ${metadataClips.length} clips`);
49149
- if (autoLoadFirstVideo && metadataClips.length > 0 && s3ClipsService) {
49150
- const firstClipMeta = metadataClips[0];
50722
+ const autoloadCandidates = isInitialTimeHandoffCategory(categoryId) ? metadataClips.filter((clip) => isTimestampInInitialTimeHandoff(clip.clip_timestamp || clip.creation_timestamp || clip.timestamp)) : metadataClips;
50723
+ if (autoLoadFirstVideo && autoloadCandidates.length > 0 && s3ClipsService) {
50724
+ const firstClipMeta = autoloadCandidates[0];
49151
50725
  const firstClipId = firstClipMeta.clipId || firstClipMeta.id;
49152
50726
  try {
49153
50727
  const video = await s3ClipsService.getClipById(firstClipId);
@@ -49181,7 +50755,7 @@ var BottlenecksContent = ({
49181
50755
  setIsCategoryLoading(false);
49182
50756
  }
49183
50757
  }
49184
- }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, idleClipSort, supabase, setVisibleCategoryMetadata, lowMomentsPrefetch, applyPrefetchedFirstVideo, applyMetadataSnapshot, isLowMomentsCategoryAvailable]);
50758
+ }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, idleClipSort, supabase, setVisibleCategoryMetadata, lowMomentsPrefetch, applyPrefetchedFirstVideo, applyMetadataSnapshot, isLowMomentsCategoryAvailable, isInitialTimeHandoffCategory, isTimestampInInitialTimeHandoff, initialTimeFilter?.endTime, initialTimeFilter?.startTime, initialTimeFilter?.timezone, timezone]);
49185
50759
  React125.useEffect(() => {
49186
50760
  if (activeFilter !== LOW_EFFICIENCY_CATEGORY_ID || !isLowMomentsCategoryAvailable) {
49187
50761
  return;
@@ -49213,6 +50787,37 @@ var BottlenecksContent = ({
49213
50787
  applyMetadataSnapshot,
49214
50788
  isLowMomentsCategoryAvailable
49215
50789
  ]);
50790
+ React125.useEffect(() => {
50791
+ if (!initialTimeFilter?.startTime || !initialTimeFilter?.endTime || !initialTimePrefetch || initialTimePrefetch.loading || initialTimePrefetch.categoryId !== activeFilter) {
50792
+ return;
50793
+ }
50794
+ if (initialTimePrefetch.metadata.length > 0) {
50795
+ applyMetadataSnapshot(
50796
+ initialTimePrefetch.categoryId,
50797
+ initialTimePrefetch.metadata,
50798
+ initialTimePrefetch.total
50799
+ );
50800
+ }
50801
+ if (initialTimePrefetch.firstVideo && isTimestampInInitialTimeHandoff(
50802
+ initialTimePrefetch.firstVideo.creation_timestamp || initialTimePrefetch.firstVideo.timestamp
50803
+ )) {
50804
+ applyPrefetchedFirstVideo(initialTimePrefetch.firstVideo);
50805
+ if (isMountedRef.current) {
50806
+ setIsCategoryLoading(false);
50807
+ setIsInitialLoading(false);
50808
+ setIsTransitioning(false);
50809
+ setIsNavigating(false);
50810
+ }
50811
+ }
50812
+ }, [
50813
+ activeFilter,
50814
+ applyMetadataSnapshot,
50815
+ applyPrefetchedFirstVideo,
50816
+ initialTimeFilter?.endTime,
50817
+ initialTimeFilter?.startTime,
50818
+ initialTimePrefetch,
50819
+ isTimestampInInitialTimeHandoff
50820
+ ]);
49216
50821
  React125.useEffect(() => {
49217
50822
  if (previousIdleClipSortRef.current === idleClipSort) {
49218
50823
  return;
@@ -49252,7 +50857,7 @@ var BottlenecksContent = ({
49252
50857
  currentTotalRef.current = total;
49253
50858
  setCurrentTotal(total);
49254
50859
  previousFilterRef.current = activeFilter;
49255
- const metadataLoadPlan = getCategoryMetadataLoadPlanForFilterChange({
50860
+ const metadataLoadPlan = isInitialTimeHandoffCategory(activeFilter) ? { shouldLoad: false, autoLoadFirstVideo: false } : getCategoryMetadataLoadPlanForFilterChange({
49256
50861
  activeFilter,
49257
50862
  currentClipId,
49258
50863
  categoryTotal: total
@@ -49276,7 +50881,7 @@ var BottlenecksContent = ({
49276
50881
  }
49277
50882
  }
49278
50883
  }
49279
- }, [activeFilter, allVideos, mergedCounts, currentClipId, loadCategoryMetadata]);
50884
+ }, [activeFilter, allVideos, mergedCounts, currentClipId, loadCategoryMetadata, isInitialTimeHandoffCategory]);
49280
50885
  React125.useEffect(() => {
49281
50886
  if (!currentClipId || activeFilter === "all") {
49282
50887
  return;
@@ -49304,10 +50909,19 @@ var BottlenecksContent = ({
49304
50909
  console.warn("[BottlenecksContent] Error disposing player:", e);
49305
50910
  }
49306
50911
  }
49307
- loadingTimeoutRef.current = setTimeout(() => {
50912
+ if (loadingTimeoutRef.current) {
50913
+ clearTimeout(loadingTimeoutRef.current);
50914
+ loadingTimeoutRef.current = null;
50915
+ }
50916
+ const loadingTimeout = setTimeout(() => {
50917
+ if (loadingTimeoutRef.current !== loadingTimeout) {
50918
+ return;
50919
+ }
50920
+ loadingTimeoutRef.current = null;
49308
50921
  console.warn("[BottlenecksContent] Loading timeout - clearing stuck loading state");
49309
50922
  clearLoadingState();
49310
50923
  }, 2e3);
50924
+ loadingTimeoutRef.current = loadingTimeout;
49311
50925
  if (activeFilterRef.current !== categoryId) {
49312
50926
  updateActiveFilter(categoryId);
49313
50927
  }
@@ -49327,17 +50941,20 @@ var BottlenecksContent = ({
49327
50941
  setCurrentClipId(clipId);
49328
50942
  setAllVideos([video]);
49329
50943
  setCurrentIndex(0);
50944
+ clearLoadingState();
49330
50945
  } else {
49331
50946
  throw new Error(`Failed to load video data for clip ${clipId}`);
49332
50947
  }
49333
50948
  await metadataPromise;
49334
50949
  let metadataArray = categoryMetadataRef.current;
49335
- const fallbackHasClip = fallbackMetadata?.some((clip) => clip.clipId === clipId);
49336
- if ((metadataArray.length === 0 || !metadataArray.some((clip) => clip.clipId === clipId)) && fallbackHasClip) {
50950
+ const getMetadataClipId = (clip) => clip?.clipId || clip?.id;
50951
+ const metadataHasClip = (clips) => clips.some((clip) => getMetadataClipId(clip) === clipId);
50952
+ const fallbackHasClip = fallbackMetadata?.some((clip) => getMetadataClipId(clip) === clipId);
50953
+ if ((metadataArray.length === 0 || !metadataHasClip(metadataArray)) && fallbackHasClip) {
49337
50954
  applyMetadataSnapshot(categoryId, fallbackMetadata, fallbackTotal);
49338
50955
  metadataArray = fallbackMetadata;
49339
50956
  }
49340
- if (metadataArray.length === 0 || !metadataArray.some((clip) => clip.clipId === clipId)) {
50957
+ if (metadataArray.length === 0 || !metadataHasClip(metadataArray)) {
49341
50958
  if (!fallbackHasClip) {
49342
50959
  console.warn(`[BottlenecksContent] Clip ${clipId} not found in metadata for ${categoryId} (cache hit: ${metadataArray.length > 0}) - forcing refresh`);
49343
50960
  await loadCategoryMetadata(categoryId, false, true);
@@ -49356,7 +50973,7 @@ var BottlenecksContent = ({
49356
50973
  }
49357
50974
  return;
49358
50975
  }
49359
- const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
50976
+ const clickedClipIndex = metadataArray.findIndex((clip) => getMetadataClipId(clip) === clipId);
49360
50977
  if (clickedClipIndex === -1) {
49361
50978
  console.warn(`[BottlenecksContent] Clip ${clipId} not found after metadata refresh`);
49362
50979
  if (!shouldUseMetadataNavigation(categoryId)) {
@@ -49744,6 +51361,32 @@ var BottlenecksContent = ({
49744
51361
  }
49745
51362
  return filteredVideos[currentIndex];
49746
51363
  }, [filteredVideos, currentIndex]);
51364
+ const currentVideoMatchesInitialTimeHandoff = React125.useMemo(() => {
51365
+ if (!hasInitialTimeHandoff) {
51366
+ return true;
51367
+ }
51368
+ if (!currentVideo) {
51369
+ return false;
51370
+ }
51371
+ if (!isInitialTimeHandoffCategory(activeFilter) || !isInitialTimeHandoffCategory(currentVideo.type)) {
51372
+ return false;
51373
+ }
51374
+ return isTimestampInInitialTimeHandoff(
51375
+ currentVideo.creation_timestamp || currentVideo.timestamp || currentVideo.clip_end_time || currentVideo.clip_start_time
51376
+ );
51377
+ }, [
51378
+ activeFilter,
51379
+ currentVideo,
51380
+ hasInitialTimeHandoff,
51381
+ isInitialTimeHandoffCategory,
51382
+ isTimestampInInitialTimeHandoff
51383
+ ]);
51384
+ const shouldHoldInitialTimeHandoffVideo = Boolean(
51385
+ hasInitialTimeHandoff && !currentVideoMatchesInitialTimeHandoff && (isCategoryLoading || filteredVideos.length > 0 || (mergedCounts[activeFilter] || 0) > 0)
51386
+ );
51387
+ const canRenderCurrentVideo = Boolean(
51388
+ filteredVideos.length > 0 && currentVideo && !isFullscreen && !shouldHoldInitialTimeHandoffVideo
51389
+ );
49747
51390
  const currentLowEfficiencyClipId = currentVideo?.id || currentClipId || null;
49748
51391
  const isCurrentLowEfficiencyClip = Boolean(
49749
51392
  currentVideo?.type === "recent_flow_red_streak" || currentVideo?.red_flow_timeline
@@ -49910,11 +51553,111 @@ var BottlenecksContent = ({
49910
51553
  }
49911
51554
  return currentPosition;
49912
51555
  }, [activeFilter, categoryMetadata.length, currentMetadataIndex, currentPosition, shouldUseMetadataNavigation]);
49913
- const prefetchedExplorerMetadata = React125.useMemo(() => activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && !lowMomentsPrefetch.loading && lowMomentsPrefetch.metadata.length > 0 ? { [LOW_EFFICIENCY_CATEGORY_ID]: lowMomentsPrefetch.metadata } : activeFilter === "idle_time" && categoryMetadataSort !== idleClipSort ? void 0 : buildPrefetchedExplorerMetadata(
51556
+ const initialTimePrefetchedMetadata = React125.useMemo(() => {
51557
+ if (!initialTimeFilter?.startTime || !initialTimeFilter?.endTime || !initialTimePrefetch || initialTimePrefetch.loading) {
51558
+ return void 0;
51559
+ }
51560
+ if (initialTimePrefetch.metadataByCategory) {
51561
+ return initialTimePrefetch.metadataByCategory;
51562
+ }
51563
+ if (initialTimePrefetch.categoryId === activeFilter && initialTimePrefetch.metadata.length > 0) {
51564
+ return { [activeFilter]: initialTimePrefetch.metadata };
51565
+ }
51566
+ return void 0;
51567
+ }, [
49914
51568
  activeFilter,
51569
+ initialTimePrefetch,
51570
+ initialTimeFilter?.endTime,
51571
+ initialTimeFilter?.startTime
51572
+ ]);
51573
+ const prefetchedExplorerMetadata = React125.useMemo(() => {
51574
+ if (initialTimePrefetchedMetadata) {
51575
+ return initialTimePrefetchedMetadata;
51576
+ }
51577
+ if (activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && !lowMomentsPrefetch.loading && lowMomentsPrefetch.metadata.length > 0) {
51578
+ return { [LOW_EFFICIENCY_CATEGORY_ID]: lowMomentsPrefetch.metadata };
51579
+ }
51580
+ if (activeFilter === "idle_time" && categoryMetadataSort !== idleClipSort) {
51581
+ return void 0;
51582
+ }
51583
+ const metadataSnapshot = buildPrefetchedExplorerMetadata(
51584
+ activeFilter,
51585
+ categoryMetadataCategoryId,
51586
+ categoryMetadata
51587
+ );
51588
+ if (metadataSnapshot) {
51589
+ return metadataSnapshot;
51590
+ }
51591
+ if (initialTimeFilter?.startTime && initialTimeFilter?.endTime && currentVideo && currentVideo.type === activeFilter) {
51592
+ return {
51593
+ [activeFilter]: [{
51594
+ id: currentVideo.id,
51595
+ clipId: currentVideo.id,
51596
+ clip_timestamp: currentVideo.creation_timestamp || currentVideo.timestamp,
51597
+ description: currentVideo.description,
51598
+ severity: currentVideo.severity,
51599
+ category: activeFilter,
51600
+ duration: typeof currentVideo.duration === "number" ? currentVideo.duration : typeof currentVideo.cycle_time_seconds === "number" ? currentVideo.cycle_time_seconds : void 0,
51601
+ clip_start_time: currentVideo.clip_start_time,
51602
+ clip_end_time: currentVideo.clip_end_time,
51603
+ index: 0,
51604
+ idle_start_time: currentVideo.idle_start_time,
51605
+ idle_end_time: currentVideo.idle_end_time,
51606
+ cycle_item_count: null,
51607
+ red_flow_timeline: currentVideo.red_flow_timeline,
51608
+ red_flow_severity_score: currentVideo.red_flow_severity_score,
51609
+ red_flow_output_shortfall_units: currentVideo.red_flow_output_shortfall_units,
51610
+ red_flow_worst_minute: currentVideo.red_flow_worst_minute,
51611
+ red_flow_explanation_summary: currentVideo.red_flow_explanation_summary,
51612
+ red_flow_explanation: currentVideo.red_flow_explanation
51613
+ }]
51614
+ };
51615
+ }
51616
+ return void 0;
51617
+ }, [
51618
+ activeFilter,
51619
+ categoryMetadata,
49915
51620
  categoryMetadataCategoryId,
49916
- categoryMetadata
49917
- ), [activeFilter, categoryMetadata, categoryMetadataCategoryId, categoryMetadataSort, idleClipSort, lowMomentsPrefetch, workspaceId, effectiveDateString, effectiveShiftId, isLowMomentsCategoryAvailable]);
51621
+ categoryMetadataSort,
51622
+ currentVideo,
51623
+ idleClipSort,
51624
+ initialTimePrefetchedMetadata,
51625
+ initialTimePrefetch,
51626
+ initialTimeFilter?.endTime,
51627
+ initialTimeFilter?.startTime,
51628
+ lowMomentsPrefetch,
51629
+ workspaceId,
51630
+ effectiveDateString,
51631
+ effectiveShiftId,
51632
+ isLowMomentsCategoryAvailable
51633
+ ]);
51634
+ const externallyManagedLoadingCategories = React125.useMemo(() => {
51635
+ const managedCategories = {
51636
+ recent_flow_red_streak: Boolean(
51637
+ activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && lowMomentsPrefetch.loading
51638
+ )
51639
+ };
51640
+ const isInitialTimeHandoffPending = Boolean(
51641
+ initialTimeFilter?.startTime && initialTimeFilter?.endTime && requestedInitialCategoryCandidates.length > 0 && (!initialTimePrefetch || initialTimePrefetch.loading)
51642
+ );
51643
+ if (isInitialTimeHandoffPending) {
51644
+ requestedInitialCategoryCandidates.forEach((categoryId) => {
51645
+ managedCategories[categoryId] = true;
51646
+ });
51647
+ }
51648
+ return managedCategories;
51649
+ }, [
51650
+ activeFilter,
51651
+ effectiveDateString,
51652
+ effectiveShiftId,
51653
+ initialTimeFilter?.endTime,
51654
+ initialTimeFilter?.startTime,
51655
+ initialTimePrefetch,
51656
+ isLowMomentsCategoryAvailable,
51657
+ lowMomentsPrefetch,
51658
+ requestedInitialCategoryCandidates,
51659
+ workspaceId
51660
+ ]);
49918
51661
  const classificationClipIds = React125.useMemo(() => {
49919
51662
  if (!idleTimeVlmEnabled) {
49920
51663
  return [];
@@ -50324,7 +52067,7 @@ var BottlenecksContent = ({
50324
52067
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: "S3 configuration is required to load video clips. Please check your dashboard configuration." })
50325
52068
  ] });
50326
52069
  }
50327
- if (clipTypesLoading && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
52070
+ if (!hasInitialTimeHandoff && clipTypesLoading && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
50328
52071
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-grow p-4 flex items-center justify-center min-h-[calc(100dvh-12rem)]", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading clips..." }) });
50329
52072
  }
50330
52073
  if (error && error.type === "fatal" && !hasInitialLoad || clipTypesError) {
@@ -50334,7 +52077,30 @@ var BottlenecksContent = ({
50334
52077
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: error?.message || clipTypesError })
50335
52078
  ] });
50336
52079
  }
50337
- const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
52080
+ const categoriesToShow = (() => {
52081
+ const categories = clipTypes.length > 0 ? [...clipTypes] : [];
52082
+ const existingTypes = new Set(categories.map((category) => category.type));
52083
+ requestedInitialCategoryCandidates.forEach((categoryType) => {
52084
+ if (existingTypes.has(categoryType) || !hasInitialTimeHandoff && (mergedCounts[categoryType] || 0) <= 0) {
52085
+ return;
52086
+ }
52087
+ const fallback = CHART_HANDOFF_CATEGORY_FALLBACKS[categoryType];
52088
+ if (!fallback) {
52089
+ return;
52090
+ }
52091
+ categories.push({
52092
+ id: categoryType,
52093
+ type: categoryType,
52094
+ label: fallback.label,
52095
+ description: fallback.description,
52096
+ color: fallback.color,
52097
+ icon: fallback.icon,
52098
+ sort_order: 50
52099
+ });
52100
+ existingTypes.add(categoryType);
52101
+ });
52102
+ return categories;
52103
+ })();
50338
52104
  console.log("[BottlenecksContent] Categories to show:", {
50339
52105
  categoriesToShow,
50340
52106
  categoriesToShowLength: categoriesToShow?.length,
@@ -50353,190 +52119,204 @@ var BottlenecksContent = ({
50353
52119
  }
50354
52120
  ),
50355
52121
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 lg:flex-row lg:h-full", children: [
50356
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 w-full lg:flex-[3] lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white rounded-lg shadow-sm overflow-hidden lg:h-full", children: filteredVideos.length > 0 && currentVideo && !isFullscreen ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative group lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-video lg:aspect-auto lg:h-full overflow-hidden rounded-md shadow-inner bg-gray-900", children: [
50357
- /* @__PURE__ */ jsxRuntime.jsx(
50358
- "div",
50359
- {
50360
- className: "w-full h-full",
50361
- style: {
50362
- opacity: isTransitioning ? 0 : 1,
50363
- transition: "opacity 0.1s ease-in-out"
50364
- },
50365
- children: !shouldDeferPlayerRenderForCrop && /* @__PURE__ */ jsxRuntime.jsx(
50366
- CroppedVideoPlayer,
52122
+ /* @__PURE__ */ jsxRuntime.jsx(
52123
+ "div",
52124
+ {
52125
+ className: "min-w-0 w-full lg:flex-[3] lg:h-full",
52126
+ "data-qa-clips-active-filter": activeFilter || "",
52127
+ "data-qa-clips-current-video-type": currentVideo?.type || "",
52128
+ "data-qa-clips-current-video-id": currentVideo?.id || "",
52129
+ "data-qa-clips-current-clip-id": currentClipId || "",
52130
+ "data-qa-clips-filtered-count": filteredVideos.length,
52131
+ "data-qa-clips-all-count": allVideos.length,
52132
+ "data-qa-clips-current-video-matches-hour": String(currentVideoMatchesInitialTimeHandoff),
52133
+ "data-qa-clips-hold-hour-video": String(shouldHoldInitialTimeHandoffVideo),
52134
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white rounded-lg shadow-sm overflow-hidden lg:h-full", children: canRenderCurrentVideo && currentVideo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative group lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-video lg:aspect-auto lg:h-full overflow-hidden rounded-md shadow-inner bg-gray-900", children: [
52135
+ /* @__PURE__ */ jsxRuntime.jsx(
52136
+ "div",
50367
52137
  {
50368
- ref: videoRef,
50369
- src: currentVideo.src,
50370
- poster: "",
50371
52138
  className: "w-full h-full",
50372
- crop: workspaceCrop?.crop,
50373
- autoplay: true,
50374
- playsInline: true,
50375
- loop: false,
50376
- externalLoadingControl: true,
50377
- onReady: handleVideoReady,
50378
- onPlay: handleVideoPlay,
50379
- onPause: handleVideoPause,
50380
- onTimeUpdate: handleTimeUpdate,
50381
- onDurationChange: handleDurationChange,
50382
- onEnded: handleVideoEnded,
50383
- onError: handleVideoError,
50384
- onLoadedData: handleLoadedData,
50385
- onPlaying: handleVideoPlaying,
50386
- onLoadingChange: handleVideoLoadingChange,
50387
- onShare: handleShareClip,
50388
- isShareLoading,
50389
- isShareCopied,
50390
- timelineAnnotations: currentVideo.red_flow_timeline,
50391
- timelineExplanation: currentVideo.red_flow_explanation,
50392
- timelineTimezone: timezone,
50393
- options: videoPlayerOptions
50394
- },
50395
- `${currentVideo.id}-${playerInstanceNonce}-inline`
50396
- )
50397
- }
50398
- ),
50399
- currentVideo.type === "recent_flow_red_streak" && !shouldDeferPlayerRenderForCrop ? /* @__PURE__ */ jsxRuntime.jsx(
50400
- RedFlowDiagnosticOverlay,
50401
- {
50402
- timeline: currentVideo.red_flow_timeline,
50403
- explanation: currentVideo.red_flow_explanation,
50404
- aiSummary: currentLowEfficiencyAiSummary,
50405
- aiSummaryLoading: isCurrentLowEfficiencyAiSummaryLoading,
50406
- aiSummaryError: currentLowEfficiencyAiSummaryError,
50407
- className: "right-4 top-4"
50408
- }
50409
- ) : null,
50410
- showBlockingVideoLoader && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
50411
- !shouldDeferPlayerRenderForCrop && !isTransitioning && isVideoBuffering && !isInitialLoading && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
50412
- error && error.type === "retrying" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-40 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
50413
- /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md" }),
50414
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white text-sm mt-4 font-medium", children: error.message })
50415
- ] }) }),
50416
- error && error.type === "fatal" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/90 text-white p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md", children: [
50417
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 mx-auto mb-4 text-red-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" }) }),
50418
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-2", children: error.code === 3 ? "Stream Corrupted" : error.code === 4 ? "Format Not Supported" : error.code === 2 ? "Network Error" : error.code === 1 ? "Loading Interrupted" : "Playback Error" }),
50419
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-300 mb-6", children: error.message }),
50420
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 justify-center", children: [
50421
- error.canSkip && /* @__PURE__ */ jsxRuntime.jsx(
50422
- "button",
50423
- {
50424
- onClick: () => {
50425
- setError(null);
50426
- videoRetryCountRef.current = 0;
50427
- handleNext();
52139
+ style: {
52140
+ opacity: isTransitioning ? 0 : 1,
52141
+ transition: "opacity 0.1s ease-in-out"
50428
52142
  },
50429
- className: "px-5 py-2.5 bg-blue-600 hover:bg-blue-700 rounded-md text-sm font-medium transition-colors",
50430
- children: "Skip to Next Clip"
52143
+ children: !shouldDeferPlayerRenderForCrop && /* @__PURE__ */ jsxRuntime.jsx(
52144
+ CroppedVideoPlayer,
52145
+ {
52146
+ ref: videoRef,
52147
+ src: currentVideo.src,
52148
+ poster: "",
52149
+ className: "w-full h-full",
52150
+ crop: workspaceCrop?.crop,
52151
+ autoplay: true,
52152
+ playsInline: true,
52153
+ loop: false,
52154
+ externalLoadingControl: true,
52155
+ onReady: handleVideoReady,
52156
+ onPlay: handleVideoPlay,
52157
+ onPause: handleVideoPause,
52158
+ onTimeUpdate: handleTimeUpdate,
52159
+ onDurationChange: handleDurationChange,
52160
+ onEnded: handleVideoEnded,
52161
+ onError: handleVideoError,
52162
+ onLoadedData: handleLoadedData,
52163
+ onPlaying: handleVideoPlaying,
52164
+ onLoadingChange: handleVideoLoadingChange,
52165
+ onShare: handleShareClip,
52166
+ isShareLoading,
52167
+ isShareCopied,
52168
+ timelineAnnotations: currentVideo.red_flow_timeline,
52169
+ timelineExplanation: currentVideo.red_flow_explanation,
52170
+ timelineTimezone: timezone,
52171
+ options: videoPlayerOptions
52172
+ },
52173
+ `${currentVideo.id}-${playerInstanceNonce}-inline`
52174
+ )
50431
52175
  }
50432
52176
  ),
50433
- error.canRetry && /* @__PURE__ */ jsxRuntime.jsx(
50434
- "button",
52177
+ currentVideo.type === "recent_flow_red_streak" && !shouldDeferPlayerRenderForCrop ? /* @__PURE__ */ jsxRuntime.jsx(
52178
+ RedFlowDiagnosticOverlay,
50435
52179
  {
50436
- onClick: () => {
50437
- videoRetryCountRef.current = 0;
50438
- restartCurrentClipPlayback();
50439
- },
50440
- className: "px-5 py-2.5 bg-gray-600 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors",
50441
- children: "Retry"
52180
+ timeline: currentVideo.red_flow_timeline,
52181
+ explanation: currentVideo.red_flow_explanation,
52182
+ aiSummary: currentLowEfficiencyAiSummary,
52183
+ aiSummaryLoading: isCurrentLowEfficiencyAiSummaryLoading,
52184
+ aiSummaryError: currentLowEfficiencyAiSummaryError,
52185
+ className: "right-4 top-4"
50442
52186
  }
50443
- )
50444
- ] })
50445
- ] }) }),
50446
- (currentVideo.type === "cycle_completion" || currentVideo.type === "bottleneck" && currentVideo.description.toLowerCase().includes("cycle time")) && currentVideo.cycle_time_seconds || currentVideo.type === "idle_time" || currentVideo.type === "low_value" ? currentVideo.type === "idle_time" ? (
50447
- // Show full colored badge for idle time
50448
- (() => {
50449
- const classification = getIdleTimeClassification(currentVideo);
50450
- const confidence = getIdleTimeConfidence(currentVideo);
50451
- const config = getRootCauseConfig(classification);
50452
- if (!config) return null;
50453
- const IconComponent = config.Icon;
50454
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
50455
- idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 left-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `inline-flex max-w-[18rem] items-center gap-1.5 rounded-md border px-2.5 py-1.5 shadow-lg ${config.bgColor} ${config.borderColor}`, children: [
50456
- /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: `h-3.5 w-3.5 flex-shrink-0 ${config.iconColor}`, strokeWidth: 2.5 }),
50457
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `truncate font-medium text-xs ${config.color}`, children: config.displayName || "Analyzing..." })
50458
- ] }) }),
50459
- idleTimeVlmEnabled && confidence !== null && (() => {
50460
- const confidencePercent = confidence * 100;
50461
- let confidenceLabel = "Low";
50462
- let confidenceColor = "text-red-500";
50463
- if (confidencePercent > 95) {
50464
- confidenceLabel = "High";
50465
- confidenceColor = "text-green-500";
50466
- } else if (confidencePercent >= 91) {
50467
- confidenceLabel = "Medium";
50468
- confidenceColor = "text-yellow-500";
50469
- }
50470
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 right-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-black/70 backdrop-blur-sm shadow-lg border border-white/10", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-xs text-white", children: [
50471
- "AI Confidence: ",
50472
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: confidenceColor, children: confidenceLabel })
50473
- ] }) }) });
52187
+ ) : null,
52188
+ showBlockingVideoLoader && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
52189
+ !shouldDeferPlayerRenderForCrop && !isTransitioning && isVideoBuffering && !isInitialLoading && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
52190
+ error && error.type === "retrying" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-40 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
52191
+ /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md" }),
52192
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white text-sm mt-4 font-medium", children: error.message })
52193
+ ] }) }),
52194
+ error && error.type === "fatal" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/90 text-white p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md", children: [
52195
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 mx-auto mb-4 text-red-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" }) }),
52196
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-2", children: error.code === 3 ? "Stream Corrupted" : error.code === 4 ? "Format Not Supported" : error.code === 2 ? "Network Error" : error.code === 1 ? "Loading Interrupted" : "Playback Error" }),
52197
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-300 mb-6", children: error.message }),
52198
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 justify-center", children: [
52199
+ error.canSkip && /* @__PURE__ */ jsxRuntime.jsx(
52200
+ "button",
52201
+ {
52202
+ onClick: () => {
52203
+ setError(null);
52204
+ videoRetryCountRef.current = 0;
52205
+ handleNext();
52206
+ },
52207
+ className: "px-5 py-2.5 bg-blue-600 hover:bg-blue-700 rounded-md text-sm font-medium transition-colors",
52208
+ children: "Skip to Next Clip"
52209
+ }
52210
+ ),
52211
+ error.canRetry && /* @__PURE__ */ jsxRuntime.jsx(
52212
+ "button",
52213
+ {
52214
+ onClick: () => {
52215
+ videoRetryCountRef.current = 0;
52216
+ restartCurrentClipPlayback();
52217
+ },
52218
+ className: "px-5 py-2.5 bg-gray-600 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors",
52219
+ children: "Retry"
52220
+ }
52221
+ )
52222
+ ] })
52223
+ ] }) }),
52224
+ (currentVideo.type === "cycle_completion" || currentVideo.type === "bottleneck" && currentVideo.description.toLowerCase().includes("cycle time")) && currentVideo.cycle_time_seconds || currentVideo.type === "idle_time" || currentVideo.type === "low_value" ? currentVideo.type === "idle_time" ? (
52225
+ // Show full colored badge for idle time
52226
+ (() => {
52227
+ const classification = getIdleTimeClassification(currentVideo);
52228
+ const confidence = getIdleTimeConfidence(currentVideo);
52229
+ const config = getRootCauseConfig(classification);
52230
+ if (!config) return null;
52231
+ const IconComponent = config.Icon;
52232
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52233
+ idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 left-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `inline-flex max-w-[18rem] items-center gap-1.5 rounded-md border px-2.5 py-1.5 shadow-lg ${config.bgColor} ${config.borderColor}`, children: [
52234
+ /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: `h-3.5 w-3.5 flex-shrink-0 ${config.iconColor}`, strokeWidth: 2.5 }),
52235
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `truncate font-medium text-xs ${config.color}`, children: config.displayName || "Analyzing..." })
52236
+ ] }) }),
52237
+ idleTimeVlmEnabled && confidence !== null && (() => {
52238
+ const confidencePercent = confidence * 100;
52239
+ let confidenceLabel = "Low";
52240
+ let confidenceColor = "text-red-500";
52241
+ if (confidencePercent > 95) {
52242
+ confidenceLabel = "High";
52243
+ confidenceColor = "text-green-500";
52244
+ } else if (confidencePercent >= 91) {
52245
+ confidenceLabel = "Medium";
52246
+ confidenceColor = "text-yellow-500";
52247
+ }
52248
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 right-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-black/70 backdrop-blur-sm shadow-lg border border-white/10", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-xs text-white", children: [
52249
+ "AI Confidence: ",
52250
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: confidenceColor, children: confidenceLabel })
52251
+ ] }) }) });
52252
+ })()
52253
+ ] });
50474
52254
  })()
50475
- ] });
50476
- })()
50477
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 left-3 z-10 bg-black/60 backdrop-blur-sm px-3 py-1.5 rounded-lg text-white shadow-lg text-xs", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
50478
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${currentVideo.type === "low_value" ? "bg-purple-400" : isPercentileCategory(activeFilterRef.current) ? activeFilterRef.current === "fast-cycles" ? "bg-green-600" : activeFilterRef.current === "slow-cycles" ? "bg-red-700" : "bg-orange-500" : currentVideo.type === "cycle_completion" ? "bg-blue-600" : "bg-gray-500"} mr-2 animate-pulse` }),
50479
- (currentVideo.type === "cycle_completion" || currentVideo.type === "bottleneck" && currentVideo.description.toLowerCase().includes("cycle time")) && currentVideo.cycle_time_seconds ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "opacity-90 font-mono bg-black/30 px-2 py-0.5 rounded", children: [
50480
- "Cycle time: ",
50481
- currentVideo.cycle_time_seconds.toFixed(1),
50482
- "s"
50483
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
50484
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
50485
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
50486
- ] })
50487
- ] }) }) : (
50488
- /* Right side display for other video types */
50489
- currentVideo.type === "idle_time" ? (
50490
- // Show full colored badge for idle time
50491
- (() => {
50492
- const classification = getIdleTimeClassification(currentVideo);
50493
- const confidence = getIdleTimeConfidence(currentVideo);
50494
- const config = getRootCauseConfig(classification);
50495
- if (!config) return null;
50496
- const IconComponent = config.Icon;
50497
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
50498
- idleTimeVlmEnabled && confidence !== null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 left-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-black/70 backdrop-blur-sm shadow-lg border border-white/10", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-xs text-white", children: [
50499
- "AI Confidence: ",
50500
- (confidence * 100).toFixed(0),
50501
- "%"
50502
- ] }) }) }),
50503
- idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 right-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `inline-flex max-w-[18rem] items-center gap-1.5 rounded-md border px-2.5 py-1.5 shadow-lg ${config.bgColor} ${config.borderColor}`, children: [
50504
- /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: `h-3.5 w-3.5 flex-shrink-0 ${config.iconColor}`, strokeWidth: 2.5 }),
50505
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `truncate font-medium text-xs ${config.color}`, children: config.displayName || "Analyzing..." })
50506
- ] }) })
50507
- ] });
50508
- })()
50509
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-3 z-10 bg-black/60 backdrop-blur-sm px-3 py-1.5 rounded-lg text-white shadow-lg text-xs ${currentVideo.type === "recent_flow_red_streak" ? "left-3" : "right-3"}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
50510
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${getSeverityColor(currentVideo.severity)} mr-2 animate-pulse` }),
50511
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
50512
- currentVideo.type !== "recent_flow_red_streak" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
50513
- ] }) })
50514
- )
50515
- ] }) }) }) : (
50516
- /* Priority 5: Show "no clips found" only if we have counts and there are truly no clips for workspace */
50517
- hasInitialLoad && Object.keys(mergedCounts).length > 0 && Object.values(mergedCounts).every((count) => count === 0) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[220px] sm:min-h-[320px] lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
50518
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
50519
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Clips Found" }),
50520
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There were no video clips found for this workspace today." })
50521
- ] }) }) : (
50522
- /* Priority 5.5: Show "no folder selected" if activeFilter is empty */
50523
- hasInitialLoad && !activeFilter ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[220px] sm:min-h-[320px] lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
50524
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
50525
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Folder Selected" }),
50526
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "Please select a folder to view clips." })
50527
- ] }) }) : (
50528
- /* Priority 6: Show "no matching clips" only if we have data loaded and specifically no clips for this filter */
50529
- hasInitialLoad && (mergedCounts[activeFilter] || 0) === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[220px] sm:min-h-[320px] lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
52255
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 left-3 z-10 bg-black/60 backdrop-blur-sm px-3 py-1.5 rounded-lg text-white shadow-lg text-xs", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
52256
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${currentVideo.type === "low_value" ? "bg-purple-400" : isPercentileCategory(activeFilterRef.current) ? activeFilterRef.current === "fast-cycles" ? "bg-green-600" : activeFilterRef.current === "slow-cycles" ? "bg-red-700" : "bg-orange-500" : currentVideo.type === "cycle_completion" ? "bg-blue-600" : "bg-gray-500"} mr-2 animate-pulse` }),
52257
+ (currentVideo.type === "cycle_completion" || currentVideo.type === "bottleneck" && currentVideo.description.toLowerCase().includes("cycle time")) && currentVideo.cycle_time_seconds ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "opacity-90 font-mono bg-black/30 px-2 py-0.5 rounded", children: [
52258
+ "Cycle time: ",
52259
+ currentVideo.cycle_time_seconds.toFixed(1),
52260
+ "s"
52261
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52262
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
52263
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
52264
+ ] })
52265
+ ] }) }) : (
52266
+ /* Right side display for other video types */
52267
+ currentVideo.type === "idle_time" ? (
52268
+ // Show full colored badge for idle time
52269
+ (() => {
52270
+ const classification = getIdleTimeClassification(currentVideo);
52271
+ const confidence = getIdleTimeConfidence(currentVideo);
52272
+ const config = getRootCauseConfig(classification);
52273
+ if (!config) return null;
52274
+ const IconComponent = config.Icon;
52275
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52276
+ idleTimeVlmEnabled && confidence !== null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 left-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-black/70 backdrop-blur-sm shadow-lg border border-white/10", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-xs text-white", children: [
52277
+ "AI Confidence: ",
52278
+ (confidence * 100).toFixed(0),
52279
+ "%"
52280
+ ] }) }) }),
52281
+ idleTimeVlmEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 right-3 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `inline-flex max-w-[18rem] items-center gap-1.5 rounded-md border px-2.5 py-1.5 shadow-lg ${config.bgColor} ${config.borderColor}`, children: [
52282
+ /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: `h-3.5 w-3.5 flex-shrink-0 ${config.iconColor}`, strokeWidth: 2.5 }),
52283
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `truncate font-medium text-xs ${config.color}`, children: config.displayName || "Analyzing..." })
52284
+ ] }) })
52285
+ ] });
52286
+ })()
52287
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute top-3 z-10 bg-black/60 backdrop-blur-sm px-3 py-1.5 rounded-lg text-white shadow-lg text-xs ${currentVideo.type === "recent_flow_red_streak" ? "left-3" : "right-3"}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
52288
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${getSeverityColor(currentVideo.severity)} mr-2 animate-pulse` }),
52289
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
52290
+ currentVideo.type !== "recent_flow_red_streak" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
52291
+ ] }) })
52292
+ )
52293
+ ] }) }) }) : shouldHoldInitialTimeHandoffVideo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full min-h-[220px] sm:min-h-[320px] lg:min-h-0 lg:h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading clips..." }) }) }) }) : (
52294
+ /* Priority 5: Show "no clips found" only if we have counts and there are truly no clips for workspace */
52295
+ hasInitialLoad && Object.keys(mergedCounts).length > 0 && Object.values(mergedCounts).every((count) => count === 0) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[220px] sm:min-h-[320px] lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
50530
52296
  /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
50531
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Matching Clips" }),
50532
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There are no clips matching the selected filter." })
52297
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Clips Found" }),
52298
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There were no video clips found for this workspace today." })
50533
52299
  ] }) }) : (
50534
- /* Priority 7: Default loading state for any other case */
50535
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full min-h-[220px] sm:min-h-[320px] lg:min-h-0 lg:h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading..." }) }) }) })
52300
+ /* Priority 5.5: Show "no folder selected" if activeFilter is empty */
52301
+ hasInitialLoad && !activeFilter ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[220px] sm:min-h-[320px] lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
52302
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
52303
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Folder Selected" }),
52304
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "Please select a folder to view clips." })
52305
+ ] }) }) : (
52306
+ /* Priority 6: Show "no matching clips" only if we have data loaded and specifically no clips for this filter */
52307
+ hasInitialLoad && (mergedCounts[activeFilter] || 0) === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[220px] sm:min-h-[320px] lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
52308
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
52309
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Matching Clips" }),
52310
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There are no clips matching the selected filter." })
52311
+ ] }) }) : (
52312
+ /* Priority 7: Default loading state for any other case */
52313
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative lg:h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full min-h-[220px] sm:min-h-[320px] lg:min-h-0 lg:h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading..." }) }) }) })
52314
+ )
52315
+ )
50536
52316
  )
50537
- )
50538
- )
50539
- ) }) }),
52317
+ ) })
52318
+ }
52319
+ ),
50540
52320
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full lg:flex-shrink-0 lg:flex-[1] lg:min-w-[280px] lg:max-w-[320px] lg:h-full", children: triageMode ? (
50541
52321
  /* Triage Mode - Direct tile view for cycle completions and idle time */
50542
52322
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg shadow-sm h-full overflow-hidden flex flex-col", children: [
@@ -50671,7 +52451,7 @@ var BottlenecksContent = ({
50671
52451
  })),
50672
52452
  videos: allVideos || [],
50673
52453
  activeFilter,
50674
- currentVideoId: currentVideo?.id,
52454
+ currentVideoId: currentVideo?.id || currentClipId || void 0,
50675
52455
  counts: mergedCounts,
50676
52456
  isReady: hasInitialLoad,
50677
52457
  prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts || void 0 : void 0,
@@ -50685,11 +52465,9 @@ var BottlenecksContent = ({
50685
52465
  idleTimeVlmEnabled,
50686
52466
  showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
50687
52467
  prefetchedClipMetadata: prefetchedExplorerMetadata,
50688
- externallyManagedLoadingCategories: {
50689
- recent_flow_red_streak: Boolean(
50690
- activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && lowMomentsPrefetch.loading
50691
- )
50692
- },
52468
+ prefetchedClipTotals: initialTimePrefetch?.loading ? void 0 : initialTimePrefetch?.totalsByCategory,
52469
+ prefetchedPercentileClips: initialTimePrefetch?.loading ? void 0 : initialTimePrefetch?.percentileClipsByCategory,
52470
+ externallyManagedLoadingCategories,
50693
52471
  activeCategoryLoading: isCategoryLoading,
50694
52472
  idleClipSort,
50695
52473
  onIdleClipSortChange: setIdleClipSort,
@@ -70503,9 +72281,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
70503
72281
  await preInitializeWorkspaceDisplayNames(selectedLineId);
70504
72282
  }
70505
72283
  } else if (lineIdArray.length > 0) {
70506
- await Promise.all(
70507
- lineIdArray.map((lineId) => preInitializeWorkspaceDisplayNames(lineId))
70508
- );
72284
+ await preInitializeWorkspaceDisplayNamesForLines(lineIdArray);
70509
72285
  } else {
70510
72286
  await preInitializeWorkspaceDisplayNames();
70511
72287
  }
@@ -80969,7 +82745,6 @@ var TargetsViewUI = ({
80969
82745
  skuRequired = false,
80970
82746
  onUpdateWorkspaceSelectedSku
80971
82747
  }) => {
80972
- const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
80973
82748
  const mobileMenuContext = useMobileMenu();
80974
82749
  useHideMobileHeader(!!mobileMenuContext);
80975
82750
  if (isLoading) {
@@ -81151,7 +82926,7 @@ var TargetsViewUI = ({
81151
82926
  ] })
81152
82927
  ] }) }),
81153
82928
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
81154
- const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
82929
+ const formattedName = workspace.displayName || formatWorkspaceName(workspace.name, lineId);
81155
82930
  const realSkuOptions = (workspace.skuRows || []).filter((r2) => !r2.is_dummy);
81156
82931
  const showSkuDropdown = !!onUpdateWorkspaceSelectedSku && realSkuOptions.length >= 1;
81157
82932
  const isPlanLocked = !!workspace.productionPlanLock?.locked;
@@ -81702,6 +83477,7 @@ var TargetsView = ({
81702
83477
  return {
81703
83478
  id: ws.id,
81704
83479
  name: ws.workspace_id,
83480
+ displayName: ws.display_name || ws.workspace_id,
81705
83481
  targetPPH: selectedRow?.pph_threshold ?? (threshold?.pph_threshold ? Math.round(threshold.pph_threshold) : ""),
81706
83482
  targetCycleTime: selectedRow?.ideal_cycle_time ?? (threshold?.ideal_cycle_time ?? ""),
81707
83483
  targetDayOutput: selectedRow?.total_day_output ?? (threshold?.total_day_output ?? ""),
@@ -81760,6 +83536,7 @@ var TargetsView = ({
81760
83536
  return {
81761
83537
  id: ws.id,
81762
83538
  name: ws.workspace_id,
83539
+ displayName: ws.display_name || ws.workspace_id,
81763
83540
  targetPPH: "",
81764
83541
  targetCycleTime: "",
81765
83542
  targetDayOutput: "",
@@ -82240,15 +84017,31 @@ var TargetsView = ({
82240
84017
  const handleUpdateWorkspaceDisplayName = React125.useCallback(async (workspaceId, displayName) => {
82241
84018
  try {
82242
84019
  const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
84020
+ const nextDisplayName = updated?.display_name || displayName;
84021
+ setAllShiftsData((prev) => {
84022
+ const next = { ...prev };
84023
+ Object.entries(prev).forEach(([shiftId, shiftData]) => {
84024
+ const numericShiftId = Number(shiftId);
84025
+ const updatedShiftData = {};
84026
+ Object.entries(shiftData).forEach(([lineId, line]) => {
84027
+ updatedShiftData[lineId] = {
84028
+ ...line,
84029
+ workspaces: line.workspaces.map(
84030
+ (ws) => ws.id === workspaceId ? { ...ws, displayName: nextDisplayName } : ws
84031
+ )
84032
+ };
84033
+ });
84034
+ next[numericShiftId] = updatedShiftData;
84035
+ });
84036
+ return next;
84037
+ });
82243
84038
  if (updated?.line_id && updated?.workspace_id) {
82244
84039
  upsertWorkspaceDisplayNameInCache({
82245
84040
  lineId: updated.line_id,
82246
84041
  workspaceId: updated.workspace_id,
82247
- displayName: updated?.display_name || displayName,
84042
+ displayName: nextDisplayName,
82248
84043
  enabled: updated?.enable
82249
84044
  });
82250
- } else {
82251
- await forceRefreshWorkspaceDisplayNames();
82252
84045
  }
82253
84046
  sonner.toast.success("Workspace name updated successfully");
82254
84047
  } catch (error) {
@@ -82292,9 +84085,8 @@ var TargetsView = ({
82292
84085
  }
82293
84086
  );
82294
84087
  };
82295
- var TargetsViewWithDisplayNames = withAllWorkspaceDisplayNames(TargetsView);
82296
- var TargetsView_default = TargetsViewWithDisplayNames;
82297
- var AuthenticatedTargetsView = withAuth(React125__namespace.default.memo(TargetsViewWithDisplayNames));
84088
+ var TargetsView_default = TargetsView;
84089
+ var AuthenticatedTargetsView = withAuth(React125__namespace.default.memo(TargetsView));
82298
84090
  function useTimezone(options = {}) {
82299
84091
  const dashboardConfig = useDashboardConfig();
82300
84092
  const workspaceConfig = useWorkspaceConfig();
@@ -82474,17 +84266,22 @@ var WorkspaceHourSummaryPanel = ({
82474
84266
  return;
82475
84267
  }
82476
84268
  autoSummaryKeyRef.current = requestKey;
82477
- void summarize({
82478
- workspaceId,
82479
- companyId,
82480
- date,
82481
- shiftId,
82482
- hourIndex: selectedHour.hourIndex,
82483
- hourStart: selectedHour.startTime,
82484
- hourEnd: selectedHour.endTime,
82485
- forceRefresh: false,
82486
- selectionSource: selectedHour.source ?? "unknown"
82487
- }).catch(() => void 0);
84269
+ const summaryTimer = window.setTimeout(() => {
84270
+ void summarize({
84271
+ workspaceId,
84272
+ companyId,
84273
+ date,
84274
+ shiftId,
84275
+ hourIndex: selectedHour.hourIndex,
84276
+ hourStart: selectedHour.startTime,
84277
+ hourEnd: selectedHour.endTime,
84278
+ forceRefresh: false,
84279
+ selectionSource: selectedHour.source ?? "unknown"
84280
+ }).catch(() => void 0);
84281
+ }, 1500);
84282
+ return () => {
84283
+ window.clearTimeout(summaryTimer);
84284
+ };
82488
84285
  }, [canSummarize, companyId, date, selectedHour, selectedKey, shiftId, summarize, workspaceId]);
82489
84286
  if (!selectedHour) {
82490
84287
  return null;
@@ -82668,10 +84465,18 @@ var WorkspaceDetailView = ({
82668
84465
  setSelectedHour(null);
82669
84466
  }, [workspaceId, date, shift]);
82670
84467
  const dashboardConfig = useDashboardConfig();
84468
+ const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
82671
84469
  const { legend: efficiencyLegend } = useEfficiencyLegend();
82672
84470
  const prewarmedClipsRef = React125.useRef(/* @__PURE__ */ new Set());
82673
84471
  const prewarmInFlightRef = React125.useRef(/* @__PURE__ */ new Set());
82674
84472
  const [lowMomentsPrefetch, setLowMomentsPrefetch] = React125.useState(null);
84473
+ const selectedHourClipPrefetchInFlightRef = React125.useRef(/* @__PURE__ */ new Set());
84474
+ const selectedHourClipPrefetchAbortRef = React125.useRef(null);
84475
+ const [initialTimePrefetch, setInitialTimePrefetch] = React125.useState(null);
84476
+ const {
84477
+ isFastSlowClipFiltersEnabled,
84478
+ isResolved: isFastSlowClipFiltersResolved
84479
+ } = useCompanyFastSlowClipFiltersEnabled();
82675
84480
  const [aiSummaryHour, setAiSummaryHour] = React125.useState(null);
82676
84481
  const buildHourlyOutputActionTrackingProps = React125.useCallback((payload) => ({
82677
84482
  workspace_id: workspaceId,
@@ -82701,24 +84506,286 @@ var WorkspaceDetailView = ({
82701
84506
  timezone,
82702
84507
  workspaceId
82703
84508
  ]);
84509
+ const prefetchClipsForSelectedHour = React125.useCallback((hour) => {
84510
+ if (!isClipsEnabled || !dashboardConfig?.s3Config || !workspaceId || !supabase) {
84511
+ return;
84512
+ }
84513
+ const resolvedDate = date || getOperationalDate(timezone);
84514
+ const resolvedShiftId = parsedShiftId ?? selectedShift;
84515
+ if (!resolvedDate || resolvedShiftId === null || resolvedShiftId === void 0) {
84516
+ return;
84517
+ }
84518
+ const categoryId = "cycle_completion";
84519
+ const regularCategoryIds = ["cycle_completion", "idle_time", "recent_flow_red_streak"];
84520
+ const percentileCategoryIds = isFastSlowClipFiltersEnabled ? ["fast-cycles", "slow-cycles"] : [];
84521
+ const allPrefetchCategoryIds = [...regularCategoryIds, ...percentileCategoryIds];
84522
+ const effectiveTimezone = hour.timezone || timezone;
84523
+ const prefetchKey = [
84524
+ workspaceId,
84525
+ resolvedDate,
84526
+ resolvedShiftId,
84527
+ hour.startTime,
84528
+ hour.endTime,
84529
+ effectiveTimezone || "",
84530
+ allPrefetchCategoryIds.join(",")
84531
+ ].join("|");
84532
+ if (initialTimePrefetch?.key === prefetchKey && !initialTimePrefetch.loading) {
84533
+ return;
84534
+ }
84535
+ if (selectedHourClipPrefetchInFlightRef.current.has(prefetchKey)) {
84536
+ return;
84537
+ }
84538
+ selectedHourClipPrefetchAbortRef.current?.abort();
84539
+ const controller = new AbortController();
84540
+ selectedHourClipPrefetchAbortRef.current = controller;
84541
+ selectedHourClipPrefetchInFlightRef.current.add(prefetchKey);
84542
+ const existingSnapshot = initialTimePrefetch?.key === prefetchKey ? initialTimePrefetch : null;
84543
+ setInitialTimePrefetch({
84544
+ key: prefetchKey,
84545
+ categoryId,
84546
+ metadata: existingSnapshot?.metadata || [],
84547
+ metadataByCategory: existingSnapshot?.metadataByCategory || {},
84548
+ totalsByCategory: existingSnapshot?.totalsByCategory || {},
84549
+ percentileClipsByCategory: existingSnapshot?.percentileClipsByCategory || {},
84550
+ firstVideo: existingSnapshot?.firstVideo || null,
84551
+ total: existingSnapshot?.total || 0,
84552
+ loading: true,
84553
+ error: null
84554
+ });
84555
+ const s3Service = videoPrefetchManager.getS3Service(dashboardConfig);
84556
+ const fetchHourlySnapshot = async () => {
84557
+ try {
84558
+ const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
84559
+ method: "POST",
84560
+ headers: {
84561
+ "Content-Type": "application/json"
84562
+ },
84563
+ body: JSON.stringify({
84564
+ action: "hourly-snapshot",
84565
+ workspaceId,
84566
+ date: resolvedDate,
84567
+ shift: resolvedShiftId,
84568
+ startTime: hour.startTime,
84569
+ endTime: hour.endTime,
84570
+ timeFilterTimezone: effectiveTimezone
84571
+ }),
84572
+ signal: controller.signal,
84573
+ redirectReason: "session_expired"
84574
+ });
84575
+ if (!response.ok) {
84576
+ return false;
84577
+ }
84578
+ const snapshot = await response.json();
84579
+ const metadataByCategory = snapshot?.metadataByCategory && typeof snapshot.metadataByCategory === "object" ? snapshot.metadataByCategory : {};
84580
+ const percentileClipsByCategory = snapshot?.percentileClipsByCategory && typeof snapshot.percentileClipsByCategory === "object" ? snapshot.percentileClipsByCategory : {};
84581
+ const totalsByCategory = snapshot?.totalsByCategory && typeof snapshot.totalsByCategory === "object" ? Object.entries(snapshot.totalsByCategory).reduce((accumulator, [key, value]) => {
84582
+ const total = Number(value);
84583
+ accumulator[key] = Number.isFinite(total) ? Math.max(0, total) : 0;
84584
+ return accumulator;
84585
+ }, {}) : {};
84586
+ const snapshotCategoryId = typeof snapshot?.categoryId === "string" ? snapshot.categoryId : categoryId;
84587
+ const metadata = Array.isArray(snapshot?.metadata) ? snapshot.metadata : Array.isArray(metadataByCategory[snapshotCategoryId]) ? metadataByCategory[snapshotCategoryId] : [];
84588
+ if (controller.signal.aborted) {
84589
+ return true;
84590
+ }
84591
+ setInitialTimePrefetch({
84592
+ key: prefetchKey,
84593
+ categoryId: snapshotCategoryId,
84594
+ metadata,
84595
+ metadataByCategory,
84596
+ totalsByCategory,
84597
+ percentileClipsByCategory,
84598
+ firstVideo: snapshot?.firstVideo || null,
84599
+ total: typeof totalsByCategory[snapshotCategoryId] === "number" ? totalsByCategory[snapshotCategoryId] : metadata.length,
84600
+ loading: false,
84601
+ error: null
84602
+ });
84603
+ return true;
84604
+ } catch (error2) {
84605
+ if (error2.name === "AbortError") {
84606
+ return true;
84607
+ }
84608
+ console.warn("[WorkspaceDetailView] Hourly clips snapshot failed; falling back to category prefetch:", error2);
84609
+ return false;
84610
+ }
84611
+ };
84612
+ const fetchRegularCategory = async (prefetchCategoryId) => {
84613
+ const metadataResponse = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
84614
+ method: "POST",
84615
+ headers: {
84616
+ "Content-Type": "application/json"
84617
+ },
84618
+ body: JSON.stringify({
84619
+ action: "clip-metadata",
84620
+ workspaceId,
84621
+ date: resolvedDate,
84622
+ shift: resolvedShiftId,
84623
+ category: prefetchCategoryId,
84624
+ page: 1,
84625
+ limit: 500,
84626
+ knownTotal: null,
84627
+ startTime: hour.startTime,
84628
+ endTime: hour.endTime,
84629
+ timeFilterTimezone: effectiveTimezone,
84630
+ sort: prefetchCategoryId === "recent_flow_red_streak" ? "red_flow_output_shortfall_desc" : "latest"
84631
+ }),
84632
+ signal: controller.signal,
84633
+ redirectReason: "session_expired"
84634
+ });
84635
+ if (!metadataResponse.ok) {
84636
+ throw new Error(`Hourly clips metadata prefetch failed for ${prefetchCategoryId}: ${metadataResponse.status}`);
84637
+ }
84638
+ const metadataData = await metadataResponse.json();
84639
+ return {
84640
+ categoryId: prefetchCategoryId,
84641
+ clips: Array.isArray(metadataData?.clips) ? metadataData.clips : [],
84642
+ total: typeof metadataData?.total === "number" ? metadataData.total : 0
84643
+ };
84644
+ };
84645
+ const fetchPercentileCategory = async (prefetchCategoryId) => {
84646
+ const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
84647
+ method: "POST",
84648
+ headers: {
84649
+ "Content-Type": "application/json"
84650
+ },
84651
+ body: JSON.stringify({
84652
+ action: "percentile-clips",
84653
+ workspaceId,
84654
+ startDate: `${resolvedDate}T00:00:00Z`,
84655
+ endDate: `${resolvedDate}T23:59:59Z`,
84656
+ percentile: 10,
84657
+ shiftId: resolvedShiftId,
84658
+ limit: 500,
84659
+ startTime: hour.startTime,
84660
+ endTime: hour.endTime,
84661
+ timeFilterTimezone: effectiveTimezone,
84662
+ percentileAction: prefetchCategoryId
84663
+ }),
84664
+ signal: controller.signal,
84665
+ redirectReason: "session_expired"
84666
+ });
84667
+ if (!response.ok) {
84668
+ throw new Error(`Hourly percentile clips prefetch failed for ${prefetchCategoryId}: ${response.status}`);
84669
+ }
84670
+ const data = await response.json();
84671
+ return {
84672
+ categoryId: prefetchCategoryId,
84673
+ clips: Array.isArray(data?.clips) ? data.clips : [],
84674
+ total: typeof data?.total === "number" ? data.total : 0
84675
+ };
84676
+ };
84677
+ const runPrefetch = async () => {
84678
+ try {
84679
+ const snapshotLoaded = await fetchHourlySnapshot();
84680
+ if (snapshotLoaded) {
84681
+ return;
84682
+ }
84683
+ const cycleCompletionPromise = fetchRegularCategory("cycle_completion");
84684
+ let resolvedFirstVideo = null;
84685
+ const firstVideoPromise = cycleCompletionPromise.then(async (cycleResponse) => {
84686
+ const firstClipId = cycleResponse.clips[0]?.clipId || cycleResponse.clips[0]?.id || null;
84687
+ if (!firstClipId) {
84688
+ return null;
84689
+ }
84690
+ const video = await s3Service.getClipById(firstClipId);
84691
+ resolvedFirstVideo = video;
84692
+ if (!controller.signal.aborted) {
84693
+ setInitialTimePrefetch((prev) => prev?.key === prefetchKey ? {
84694
+ ...prev,
84695
+ firstVideo: video
84696
+ } : prev);
84697
+ }
84698
+ return video;
84699
+ }).catch((error2) => {
84700
+ if (error2.name !== "AbortError") {
84701
+ console.warn("[WorkspaceDetailView] Hourly first clip prefetch failed:", error2);
84702
+ }
84703
+ return null;
84704
+ });
84705
+ const percentileResponsesPromise = Promise.all(percentileCategoryIds.map(fetchPercentileCategory));
84706
+ const regularResponses = await Promise.all([
84707
+ cycleCompletionPromise,
84708
+ fetchRegularCategory("idle_time"),
84709
+ fetchRegularCategory("recent_flow_red_streak")
84710
+ ]);
84711
+ const percentileResponses = await percentileResponsesPromise;
84712
+ const metadataByCategory = regularResponses.reduce((accumulator, response) => {
84713
+ accumulator[response.categoryId] = response.clips;
84714
+ return accumulator;
84715
+ }, {});
84716
+ const percentileClipsByCategory = percentileResponses.reduce((accumulator, response) => {
84717
+ accumulator[response.categoryId] = response.clips;
84718
+ return accumulator;
84719
+ }, {});
84720
+ const totalsByCategory = [...regularResponses, ...percentileResponses].reduce((accumulator, response) => {
84721
+ accumulator[response.categoryId] = Math.max(0, Number(response.total || 0));
84722
+ return accumulator;
84723
+ }, {});
84724
+ const metadata = metadataByCategory[categoryId] || [];
84725
+ if (controller.signal.aborted) {
84726
+ return;
84727
+ }
84728
+ setInitialTimePrefetch({
84729
+ key: prefetchKey,
84730
+ categoryId,
84731
+ metadata,
84732
+ metadataByCategory,
84733
+ totalsByCategory,
84734
+ percentileClipsByCategory,
84735
+ firstVideo: resolvedFirstVideo,
84736
+ total: typeof totalsByCategory[categoryId] === "number" ? totalsByCategory[categoryId] : metadata.length,
84737
+ loading: false,
84738
+ error: null
84739
+ });
84740
+ void firstVideoPromise;
84741
+ } catch (error2) {
84742
+ if (error2.name === "AbortError") {
84743
+ return;
84744
+ }
84745
+ console.warn("[WorkspaceDetailView] Hourly clips prefetch failed:", error2);
84746
+ setInitialTimePrefetch((prev) => prev?.key === prefetchKey ? {
84747
+ ...prev,
84748
+ loading: false,
84749
+ error: error2 instanceof Error ? error2.message : "Hourly clips prefetch failed"
84750
+ } : prev);
84751
+ } finally {
84752
+ selectedHourClipPrefetchInFlightRef.current.delete(prefetchKey);
84753
+ }
84754
+ };
84755
+ void runPrefetch();
84756
+ }, [
84757
+ dashboardConfig,
84758
+ date,
84759
+ initialTimePrefetch,
84760
+ isFastSlowClipFiltersEnabled,
84761
+ isClipsEnabled,
84762
+ parsedShiftId,
84763
+ selectedShift,
84764
+ supabase,
84765
+ timezone,
84766
+ workspaceId
84767
+ ]);
82704
84768
  const handleOutputHourSelect = React125.useCallback((payload) => {
82705
- setSelectedHour({
84769
+ const hour = {
82706
84770
  source: "output",
82707
84771
  hourIndex: payload.hourIndex,
82708
84772
  timeRange: payload.timeRange,
82709
84773
  startTime: payload.startTime,
82710
84774
  endTime: payload.endTime,
84775
+ timezone: payload.timezone,
82711
84776
  status: payload.status,
82712
84777
  output: payload.output,
82713
84778
  target: payload.target
82714
- });
82715
- }, []);
84779
+ };
84780
+ setSelectedHour(hour);
84781
+ prefetchClipsForSelectedHour(hour);
84782
+ }, [prefetchClipsForSelectedHour]);
82716
84783
  const handleAiSummaryClick = React125.useCallback((payload) => {
82717
84784
  trackCoreEvent("Hourly Output Ask AI Clicked", buildHourlyOutputActionTrackingProps(payload));
82718
84785
  setAiSummaryHour(payload);
82719
84786
  }, [buildHourlyOutputActionTrackingProps]);
82720
84787
  const handleCycleHourSelect = React125.useCallback((payload) => {
82721
- setSelectedHour({
84788
+ const hour = {
82722
84789
  source: "cycle",
82723
84790
  hourIndex: payload.hourIndex,
82724
84791
  timeRange: payload.timeRange,
@@ -82728,17 +84795,23 @@ var WorkspaceDetailView = ({
82728
84795
  cycleTime: payload.cycleTime,
82729
84796
  idealCycleTime: payload.idealCycleTime,
82730
84797
  idleMinutes: payload.idleMinutes
82731
- });
82732
- }, []);
84798
+ };
84799
+ setSelectedHour(hour);
84800
+ prefetchClipsForSelectedHour(hour);
84801
+ }, [prefetchClipsForSelectedHour]);
82733
84802
  const handleOpenClipsForHour = React125.useCallback((hour) => {
84803
+ prefetchClipsForSelectedHour(hour);
82734
84804
  setPendingClipHourFilter({
82735
84805
  startTime: hour.startTime,
82736
84806
  endTime: hour.endTime,
82737
84807
  sourceLabel: hour.timeRange,
82738
- status: hour.status === "below_target" || hour.status === "above_standard" ? "below_target" : "met_target"
84808
+ status: hour.status === "below_target" || hour.status === "above_standard" ? "below_target" : "met_target",
84809
+ timezone: hour.timezone,
84810
+ categoryId: "cycle_completion",
84811
+ categoryIds: ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time", "recent_flow_red_streak"]
82739
84812
  });
82740
84813
  setActiveTab("bottlenecks");
82741
- }, []);
84814
+ }, [prefetchClipsForSelectedHour]);
82742
84815
  const handleWatchClipsFromChart = React125.useCallback((payload) => {
82743
84816
  trackCoreEvent("Hourly Output Watch Clips Clicked", buildHourlyOutputActionTrackingProps(payload));
82744
84817
  handleOpenClipsForHour({
@@ -82747,6 +84820,7 @@ var WorkspaceDetailView = ({
82747
84820
  timeRange: payload.timeRange,
82748
84821
  startTime: payload.startTime,
82749
84822
  endTime: payload.endTime,
84823
+ timezone: payload.timezone,
82750
84824
  status: payload.status,
82751
84825
  output: payload.output,
82752
84826
  target: payload.target
@@ -82769,11 +84843,6 @@ var WorkspaceDetailView = ({
82769
84843
  startDate: isFullRange ? void 0 : rangeStart,
82770
84844
  endDate: isFullRange ? void 0 : rangeEnd
82771
84845
  });
82772
- const {
82773
- isFastSlowClipFiltersEnabled,
82774
- isResolved: isFastSlowClipFiltersResolved
82775
- } = useCompanyFastSlowClipFiltersEnabled();
82776
- const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
82777
84846
  dashboardConfig?.supervisorConfig?.enabled || false;
82778
84847
  const routedLineId = lineId || selectedLineId;
82779
84848
  const latestCachedDetailedMetrics = React125.useMemo(() => {
@@ -84520,6 +86589,7 @@ var WorkspaceDetailView = ({
84520
86589
  workspaceMetrics: detailedWorkspaceMetrics || void 0,
84521
86590
  prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts : null,
84522
86591
  lowMomentsPrefetch,
86592
+ initialTimePrefetch,
84523
86593
  initialTimeFilter: pendingClipHourFilter,
84524
86594
  className: "h-[calc(100vh-10rem)]"
84525
86595
  }
@@ -94507,6 +96577,7 @@ exports.parseDateKeyToDate = parseDateKeyToDate;
94507
96577
  exports.parseS3Uri = parseS3Uri;
94508
96578
  exports.pickPreferredLineMetricsRow = pickPreferredLineMetricsRow;
94509
96579
  exports.preInitializeWorkspaceDisplayNames = preInitializeWorkspaceDisplayNames;
96580
+ exports.preInitializeWorkspaceDisplayNamesForLines = preInitializeWorkspaceDisplayNamesForLines;
94510
96581
  exports.preloadS3Video = preloadS3Video;
94511
96582
  exports.preloadS3VideoUrl = preloadS3VideoUrl;
94512
96583
  exports.preloadS3VideosUrl = preloadS3VideosUrl;
@@ -94657,3 +96728,5 @@ exports.withRegistry = withRegistry;
94657
96728
  exports.withTimezone = withTimezone;
94658
96729
  exports.workspaceHealthService = workspaceHealthService;
94659
96730
  exports.workspaceService = workspaceService;
96731
+ //# sourceMappingURL=index.js.map
96732
+ //# sourceMappingURL=index.js.map