@optifye/dashboard-core 6.12.51 → 6.12.53

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
+ }
12559
+ }
12560
+ }
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;
12259
12571
  }
12260
12572
  }
12261
- async sendEndSession(reason) {
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,
@@ -15677,6 +16005,10 @@ var useDashboardMetrics = ({
15677
16005
  }
15678
16006
  const isFactory = currentLineIdToUse === factoryViewId;
15679
16007
  const targetLineIds = isFactory ? targetFactoryLineIds : [currentLineIdToUse];
16008
+ if (isFactory && targetLineIds.length > 0 && !shiftGroupsKey) {
16009
+ logDebug("[useDashboardMetrics] Skipping fetch: factory shift groups are not ready");
16010
+ return;
16011
+ }
15680
16012
  const targetLineIdsKey = targetLineIds.slice().sort().join(",");
15681
16013
  const effectiveBlueComparisonLineIds = normalizedBlueComparisonLineIds.length ? normalizedBlueComparisonLineIds : targetLineIds;
15682
16014
  const blueComparisonLineIdsKey = effectiveBlueComparisonLineIds.slice().sort().join(",");
@@ -15741,7 +16073,7 @@ var useDashboardMetrics = ({
15741
16073
  const qaGreenStreakParams = qaGreenStreakParamString ? `&${qaGreenStreakParamString}` : "";
15742
16074
  const buildMetricsEndpoint = (params) => {
15743
16075
  const lineIdsParam = isFactory ? `line_ids=${params.groupLineIds.join(",")}` : `line_id=${params.groupLineIds[0]}`;
15744
- const blueComparisonParam = effectiveBlueComparisonLineIds.length ? `&blue_comparison_line_ids=${effectiveBlueComparisonLineIds.join(",")}` : "";
16076
+ const blueComparisonParam = normalizedBlueComparisonLineIds.length ? `&blue_comparison_line_ids=${normalizedBlueComparisonLineIds.join(",")}` : "";
15745
16077
  const selectorParam = normalizedSelectorLineIds.length ? `&selector_line_ids=${encodeURIComponent(normalizedSelectorLineIds.join(","))}` : "";
15746
16078
  return `/api/dashboard/metrics?${lineIdsParam}${blueComparisonParam}${selectorParam}&date=${params.date}&shift_id=${params.shiftId}&company_id=${companyId}${forceParam}${qaGreenStreakParams}`;
15747
16079
  };
@@ -17464,7 +17796,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17464
17796
  return Number.isFinite(parsed) ? parsed : DEFAULT_MAX_MANIFEST_AGE_MS;
17465
17797
  })();
17466
17798
  const manifestStaleThresholdMs = maxManifestAgeMs > 0 ? Math.min(maxManifestAgeMs, SEGMENT_MAX_AGE_MS) : SEGMENT_MAX_AGE_MS;
17467
- const debugLog = (...args) => {
17799
+ const debugLog2 = (...args) => {
17468
17800
  if (debugEnabled) {
17469
17801
  console.log(...args);
17470
17802
  }
@@ -17646,7 +17978,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17646
17978
  return;
17647
17979
  }
17648
17980
  } catch (error) {
17649
- debugLog("[HLS] Stale manifest poll failed", error);
17981
+ debugLog2("[HLS] Stale manifest poll failed", error);
17650
17982
  }
17651
17983
  staleManifestPollDelayRef.current = Math.min(
17652
17984
  staleManifestPollDelayRef.current + 5e3,
@@ -17996,7 +18328,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
17996
18328
  }
17997
18329
  return true;
17998
18330
  } catch (error) {
17999
- debugLog("[HLS] Manifest freshness check failed", error);
18331
+ debugLog2("[HLS] Manifest freshness check failed", error);
18000
18332
  {
18001
18333
  markStaleStream("manifest freshness check failed");
18002
18334
  return false;
@@ -18042,7 +18374,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18042
18374
  };
18043
18375
  const softRestart = (reason) => {
18044
18376
  if (staleManifestTriggeredRef.current) {
18045
- debugLog("[HLS] Skip soft restart while manifest is stale", reason);
18377
+ debugLog2("[HLS] Skip soft restart while manifest is stale", reason);
18046
18378
  return;
18047
18379
  }
18048
18380
  console.warn(`[HLS] Soft restart: ${reason}`);
@@ -18075,7 +18407,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18075
18407
  };
18076
18408
  const hardRestart = (reason) => {
18077
18409
  if (staleManifestTriggeredRef.current) {
18078
- debugLog("[HLS] Skip hard restart while manifest is stale", reason);
18410
+ debugLog2("[HLS] Skip hard restart while manifest is stale", reason);
18079
18411
  return;
18080
18412
  }
18081
18413
  console.warn(`[HLS] Hard restart: ${reason}`);
@@ -18329,7 +18661,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18329
18661
  hls.loadSource(resolvedHlsSrc);
18330
18662
  activeStreamUrlRef.current = resolvedHlsSrc;
18331
18663
  hls.on(Hls__default.default.Events.ERROR, (_, data) => {
18332
- debugLog("[HLS] Error", {
18664
+ debugLog2("[HLS] Error", {
18333
18665
  type: data.type,
18334
18666
  details: data.details,
18335
18667
  fatal: data.fatal,
@@ -18337,7 +18669,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18337
18669
  frag: data.frag?.sn
18338
18670
  });
18339
18671
  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");
18672
+ debugLog2("[HLS] Buffer stalled, waiting for next segment");
18341
18673
  attemptPlay();
18342
18674
  return;
18343
18675
  }
@@ -18481,7 +18813,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
18481
18813
  }
18482
18814
  }
18483
18815
  }
18484
- debugLog("[HLS] Level loaded", {
18816
+ debugLog2("[HLS] Level loaded", {
18485
18817
  targetduration: details.targetduration,
18486
18818
  edge: details.edge,
18487
18819
  fragments: data.details?.fragments?.length
@@ -18877,6 +19209,7 @@ var isInitializing = false;
18877
19209
  var initializedWithLineIds = [];
18878
19210
  var missingLineContextWarnings = /* @__PURE__ */ new Set();
18879
19211
  var lineLoadPromises = /* @__PURE__ */ new Map();
19212
+ var GLOBAL_CACHE_KEY = "global";
18880
19213
  var initializationPromise = null;
18881
19214
  var workspaceDisplayNamesListeners = /* @__PURE__ */ new Set();
18882
19215
  var notifyWorkspaceDisplayNamesListeners = (changedLineId) => {
@@ -18898,6 +19231,22 @@ var storeLineDisplayNames = (lineId, lineDisplayNamesMap) => {
18898
19231
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
18899
19232
  });
18900
19233
  };
19234
+ var storeGlobalDisplayNames = (displayNamesMap) => {
19235
+ runtimeWorkspaceDisplayNames[GLOBAL_CACHE_KEY] = {};
19236
+ displayNamesMap.forEach((displayName, workspaceId) => {
19237
+ runtimeWorkspaceDisplayNames[GLOBAL_CACHE_KEY][workspaceId] = displayName;
19238
+ });
19239
+ };
19240
+ var storeDisplayNamesByLine = (displayNamesByLine, requestedLineIds = []) => {
19241
+ displayNamesByLine.forEach((lineDisplayNamesMap, lineId) => {
19242
+ storeLineDisplayNames(lineId, lineDisplayNamesMap);
19243
+ });
19244
+ requestedLineIds.forEach((lineId) => {
19245
+ if (!runtimeWorkspaceDisplayNames[lineId]) {
19246
+ runtimeWorkspaceDisplayNames[lineId] = {};
19247
+ }
19248
+ });
19249
+ };
18901
19250
  var ensureLineWorkspaceDisplayNamesLoaded = async (lineId) => {
18902
19251
  if (!lineId || runtimeWorkspaceDisplayNames[lineId]) {
18903
19252
  return;
@@ -18981,7 +19330,11 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
18981
19330
  console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
18982
19331
  runtimeWorkspaceDisplayNames = {};
18983
19332
  lineLoadPromises.clear();
18984
- if (targetLineIds.length > 0) {
19333
+ if (targetLineIds.length > 1) {
19334
+ const displayNamesByLine = await workspaceService.getWorkspaceDisplayNamesByLine(void 0, targetLineIds);
19335
+ storeDisplayNamesByLine(displayNamesByLine, targetLineIds);
19336
+ console.log(`\u2705 Stored display names for ${displayNamesByLine.size} authorized lines`);
19337
+ } else if (targetLineIds.length === 1) {
18985
19338
  const results = await Promise.all(
18986
19339
  targetLineIds.map(async (lineId) => {
18987
19340
  console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
@@ -18996,10 +19349,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
18996
19349
  } else {
18997
19350
  console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
18998
19351
  const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
18999
- runtimeWorkspaceDisplayNames["global"] = {};
19000
- allWorkspacesMap.forEach((displayName, workspaceId) => {
19001
- runtimeWorkspaceDisplayNames["global"][workspaceId] = displayName;
19002
- });
19352
+ storeGlobalDisplayNames(allWorkspacesMap);
19003
19353
  }
19004
19354
  isInitialized = true;
19005
19355
  initializedWithLineIds = targetLineIds;
@@ -19015,6 +19365,37 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
19015
19365
  })();
19016
19366
  await initializationPromise;
19017
19367
  }
19368
+ var preInitializeWorkspaceDisplayNamesForLines = async (lineIds) => {
19369
+ const uniqueLineIds = Array.from(new Set((lineIds || []).filter(Boolean)));
19370
+ if (uniqueLineIds.length === 0) {
19371
+ await preInitializeWorkspaceDisplayNames();
19372
+ return;
19373
+ }
19374
+ if (uniqueLineIds.length === 1) {
19375
+ await preInitializeWorkspaceDisplayNames(uniqueLineIds[0]);
19376
+ return;
19377
+ }
19378
+ if (isInitialized && uniqueLineIds.every((lineId) => Boolean(runtimeWorkspaceDisplayNames[lineId]))) {
19379
+ return;
19380
+ }
19381
+ if (initializationPromise) {
19382
+ await initializationPromise;
19383
+ if (uniqueLineIds.every((lineId) => Boolean(runtimeWorkspaceDisplayNames[lineId]))) {
19384
+ return;
19385
+ }
19386
+ }
19387
+ isInitializing = true;
19388
+ initializationPromise = workspaceService.getWorkspaceDisplayNamesByLine(void 0, uniqueLineIds).then((displayNamesByLine) => {
19389
+ storeDisplayNamesByLine(displayNamesByLine, uniqueLineIds);
19390
+ initializedWithLineIds = uniqueLineIds;
19391
+ isInitialized = true;
19392
+ notifyWorkspaceDisplayNamesListeners();
19393
+ }).finally(() => {
19394
+ isInitializing = false;
19395
+ initializationPromise = null;
19396
+ });
19397
+ await initializationPromise;
19398
+ };
19018
19399
  var preInitializeWorkspaceDisplayNames = async (lineId) => {
19019
19400
  console.log("\u{1F504} preInitializeWorkspaceDisplayNames called for lineId:", lineId);
19020
19401
  if (isInitialized) {
@@ -38749,6 +39130,7 @@ var HourlyOutputChartComponent = ({
38749
39130
  timeRange: data2.timeRange,
38750
39131
  startTime,
38751
39132
  endTime,
39133
+ timezone,
38752
39134
  output: Math.round(data2.originalOutput || 0),
38753
39135
  target: data2.target,
38754
39136
  status: data2.status
@@ -38922,6 +39304,7 @@ var HourlyOutputChartComponent = ({
38922
39304
  timeRange: entry.timeRange,
38923
39305
  startTime,
38924
39306
  endTime,
39307
+ timezone,
38925
39308
  output: Math.round(entry.originalOutput || 0),
38926
39309
  target: entry.target,
38927
39310
  status: entry.status
@@ -42389,6 +42772,65 @@ var buildPrefetchedExplorerMetadata = (activeFilter, metadataCategoryId, categor
42389
42772
  };
42390
42773
  };
42391
42774
  var shouldDeferClipPlayerRender = (cropLoading, workspaceCrop) => cropLoading && workspaceCrop === null;
42775
+ var parseSortableCycleTime = (value) => {
42776
+ if (typeof value === "number" && Number.isFinite(value)) {
42777
+ return value;
42778
+ }
42779
+ if (typeof value === "string") {
42780
+ const parsed = Number(value);
42781
+ return Number.isFinite(parsed) ? parsed : null;
42782
+ }
42783
+ return null;
42784
+ };
42785
+ var readSortableCycleTime = (clip) => {
42786
+ const candidate = clip;
42787
+ return parseSortableCycleTime(candidate?.cycleTimeSeconds) ?? parseSortableCycleTime(candidate?.cycle_time_seconds) ?? parseSortableCycleTime(candidate?.duration) ?? parseSortableCycleTime(
42788
+ candidate?.original_task_metadata?.cycle_time
42789
+ ) ?? null;
42790
+ };
42791
+ var readSortableTimestamp = (clip) => {
42792
+ const candidate = clip;
42793
+ const timestamp = candidate?.creation_timestamp || candidate?.timestamp || candidate?.clip_timestamp || candidate?.clip_end_time || candidate?.clip_start_time;
42794
+ if (typeof timestamp !== "string") {
42795
+ return 0;
42796
+ }
42797
+ const parsed = new Date(timestamp).getTime();
42798
+ return Number.isFinite(parsed) ? parsed : 0;
42799
+ };
42800
+ var sortPercentileCycleClipsForDisplay = (categoryId, clips) => {
42801
+ if (categoryId !== "fast-cycles" && categoryId !== "slow-cycles") {
42802
+ return [...clips];
42803
+ }
42804
+ return [...clips].sort((left, right) => {
42805
+ const leftCycleTime = readSortableCycleTime(left);
42806
+ const rightCycleTime = readSortableCycleTime(right);
42807
+ const leftMissingCycleTime = leftCycleTime === null;
42808
+ const rightMissingCycleTime = rightCycleTime === null;
42809
+ if (leftMissingCycleTime !== rightMissingCycleTime) {
42810
+ return leftMissingCycleTime ? 1 : -1;
42811
+ }
42812
+ if (leftCycleTime !== null && rightCycleTime !== null && leftCycleTime !== rightCycleTime) {
42813
+ return categoryId === "fast-cycles" ? leftCycleTime - rightCycleTime : rightCycleTime - leftCycleTime;
42814
+ }
42815
+ return readSortableTimestamp(right) - readSortableTimestamp(left);
42816
+ });
42817
+ };
42818
+ var resolveInitialClipCategory = (categoryCandidates, clipTypes = [], counts = {}) => {
42819
+ const candidates = Array.from(
42820
+ new Set(
42821
+ categoryCandidates.filter((candidate) => Boolean(candidate)).map((candidate) => candidate.trim()).filter(Boolean)
42822
+ )
42823
+ );
42824
+ if (candidates.length === 0) {
42825
+ return null;
42826
+ }
42827
+ const availableCategories = /* @__PURE__ */ new Set();
42828
+ clipTypes.forEach((type) => {
42829
+ if (type?.type) availableCategories.add(String(type.type));
42830
+ if (type?.id) availableCategories.add(String(type.id));
42831
+ });
42832
+ return candidates.find((candidate) => (counts[candidate] || 0) > 0) || candidates.find((candidate) => availableCategories.has(candidate)) || null;
42833
+ };
42392
42834
  var getCategoryMetadataLoadPlanForFilterChange = ({
42393
42835
  activeFilter,
42394
42836
  currentClipId,
@@ -42400,6 +42842,12 @@ var getCategoryMetadataLoadPlanForFilterChange = ({
42400
42842
  if (activeFilter === "recent_flow_red_streak" && categoryTotal > 0) {
42401
42843
  return { shouldLoad: true, autoLoadFirstVideo: true };
42402
42844
  }
42845
+ if (activeFilter === "fast-cycles" || activeFilter === "slow-cycles") {
42846
+ return { shouldLoad: true, autoLoadFirstVideo: true };
42847
+ }
42848
+ if (categoryTotal > 0 && activeFilter === "cycle_completion") {
42849
+ return { shouldLoad: true, autoLoadFirstVideo: true };
42850
+ }
42403
42851
  return {
42404
42852
  shouldLoad: Boolean(currentClipId),
42405
42853
  autoLoadFirstVideo: false
@@ -46237,6 +46685,36 @@ var CLIP_METADATA_PAGE_SIZE = 50;
46237
46685
  var RECENT_FLOW_RED_STREAK_CLIP_TYPE2 = "recent_flow_red_streak";
46238
46686
  var RECENT_FLOW_RED_STREAK_DISPLAY_LABEL = "Low moments";
46239
46687
  var RECENT_FLOW_RED_STREAK_DISPLAY_SUBTITLE = "Moments of low efficiency";
46688
+ var REQUIRED_HOURLY_HANDOFF_CATEGORIES = {
46689
+ cycle_completion: {
46690
+ id: "cycle_completion",
46691
+ label: "Cycle Completion",
46692
+ description: "Successfully completed production cycles",
46693
+ color: "green",
46694
+ icon: "check-circle"
46695
+ },
46696
+ idle_time: {
46697
+ id: "idle_time",
46698
+ label: "Idle Time",
46699
+ description: "Idle periods",
46700
+ color: "purple",
46701
+ icon: "clock"
46702
+ },
46703
+ [RECENT_FLOW_RED_STREAK_CLIP_TYPE2]: {
46704
+ id: RECENT_FLOW_RED_STREAK_CLIP_TYPE2,
46705
+ label: RECENT_FLOW_RED_STREAK_DISPLAY_LABEL,
46706
+ description: RECENT_FLOW_RED_STREAK_DISPLAY_SUBTITLE,
46707
+ color: "red",
46708
+ icon: "alert-triangle"
46709
+ }
46710
+ };
46711
+ var REQUIRED_HOURLY_FILTER_CATEGORY_IDS = [
46712
+ RECENT_FLOW_RED_STREAK_CLIP_TYPE2,
46713
+ "cycle_completion",
46714
+ "fast-cycles",
46715
+ "slow-cycles",
46716
+ "idle_time"
46717
+ ];
46240
46718
  var parseCycleTime = (value) => {
46241
46719
  if (typeof value === "number" && Number.isFinite(value)) {
46242
46720
  return value;
@@ -46265,6 +46743,21 @@ var formatDurationLabel = (seconds) => {
46265
46743
  }
46266
46744
  return `${Math.round(roundedSeconds / 60)} min`;
46267
46745
  };
46746
+ var timeValueToMinutes = (value) => {
46747
+ const [hourValue, minuteValue] = value.substring(0, 5).split(":").map(Number);
46748
+ if (!Number.isInteger(hourValue) || !Number.isInteger(minuteValue) || hourValue < 0 || hourValue > 23 || minuteValue < 0 || minuteValue > 59) {
46749
+ return null;
46750
+ }
46751
+ return hourValue * 60 + minuteValue;
46752
+ };
46753
+ var isMinuteInTimeWindow = (minute, startValue, endValue) => {
46754
+ const startMinute = timeValueToMinutes(startValue);
46755
+ const endMinute = timeValueToMinutes(endValue);
46756
+ if (startMinute === null || endMinute === null) {
46757
+ return true;
46758
+ }
46759
+ return endMinute > startMinute ? minute >= startMinute && minute < endMinute : minute >= startMinute || minute < endMinute;
46760
+ };
46268
46761
  var sortRedFlowMetadata = (clips) => {
46269
46762
  return clips.slice().sort((left, right) => {
46270
46763
  const getOutputShortfall = (clip) => {
@@ -46289,6 +46782,27 @@ var sortRedFlowMetadata = (clips) => {
46289
46782
  return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
46290
46783
  });
46291
46784
  };
46785
+ var buildClipMetadataFromVideo = (video, index) => ({
46786
+ id: video.id,
46787
+ clipId: video.id,
46788
+ clip_timestamp: video.creation_timestamp || video.timestamp,
46789
+ description: video.description,
46790
+ severity: video.severity,
46791
+ category: video.type,
46792
+ duration: typeof video.duration === "number" ? video.duration : typeof video.cycle_time_seconds === "number" ? video.cycle_time_seconds : void 0,
46793
+ clip_start_time: video.clip_start_time,
46794
+ clip_end_time: video.clip_end_time,
46795
+ index,
46796
+ idle_start_time: video.idle_start_time,
46797
+ idle_end_time: video.idle_end_time,
46798
+ cycle_item_count: null,
46799
+ red_flow_timeline: video.red_flow_timeline,
46800
+ red_flow_severity_score: video.red_flow_severity_score,
46801
+ red_flow_output_shortfall_units: video.red_flow_output_shortfall_units,
46802
+ red_flow_worst_minute: video.red_flow_worst_minute,
46803
+ red_flow_explanation_summary: video.red_flow_explanation_summary,
46804
+ red_flow_explanation: video.red_flow_explanation
46805
+ });
46292
46806
  var getSeverityIcon = (severity, categoryId, cycleTimeSeconds, targetCycleTime, clipId) => {
46293
46807
  if (categoryId === "idle_time" || categoryId === "low_value" || categoryId === "longest-idles") {
46294
46808
  return null;
@@ -46367,6 +46881,8 @@ var FileManagerFilters = ({
46367
46881
  idleTimeVlmEnabled = false,
46368
46882
  showPercentileCycleFilters = true,
46369
46883
  prefetchedClipMetadata,
46884
+ prefetchedClipTotals,
46885
+ prefetchedPercentileClips,
46370
46886
  externallyManagedLoadingCategories,
46371
46887
  activeCategoryLoading,
46372
46888
  idleClipSort = "latest",
@@ -46374,6 +46890,7 @@ var FileManagerFilters = ({
46374
46890
  initialTimeFilter
46375
46891
  }) => {
46376
46892
  const [expandedNodes, setExpandedNodes] = React125.useState(/* @__PURE__ */ new Set());
46893
+ const [activeInitialTimeFilter, setActiveInitialTimeFilter] = React125.useState(initialTimeFilter ?? null);
46377
46894
  const [startTime, setStartTime] = React125.useState(initialTimeFilter?.startTime ?? "");
46378
46895
  const [endTime, setEndTime] = React125.useState(initialTimeFilter?.endTime ?? "");
46379
46896
  const [isTimeFilterActive, setIsTimeFilterActive] = React125.useState(
@@ -46388,14 +46905,93 @@ var FileManagerFilters = ({
46388
46905
  const [showIdleLabelFilterModal, setShowIdleLabelFilterModal] = React125.useState(false);
46389
46906
  const [isLoadingIdleReasonOptions, setIsLoadingIdleReasonOptions] = React125.useState(false);
46390
46907
  const timezone = useAppTimezone();
46908
+ const activeTimeFilterTimezone = activeInitialTimeFilter?.timezone || initialTimeFilter?.timezone || timezone;
46909
+ const activeTimeFilterKey = React125.useMemo(() => startTime && endTime && isTimeFilterActive ? `${startTime}-${endTime}-${activeTimeFilterTimezone}` : "none", [activeTimeFilterTimezone, endTime, isTimeFilterActive, startTime]);
46910
+ const initialTimeFilterCategoryIds = React125.useMemo(() => {
46911
+ if (!activeInitialTimeFilter) {
46912
+ return [];
46913
+ }
46914
+ return Array.from(new Set([
46915
+ activeInitialTimeFilter.categoryId,
46916
+ ...activeInitialTimeFilter.categoryIds || []
46917
+ ].filter((value) => typeof value === "string" && value.length > 0)));
46918
+ }, [activeInitialTimeFilter]);
46919
+ const requiredHourlyFilterCategoryIds = React125.useMemo(() => {
46920
+ if (initialTimeFilterCategoryIds.length > 0) {
46921
+ return initialTimeFilterCategoryIds;
46922
+ }
46923
+ if (!isTimeFilterActive || !startTime || !endTime) {
46924
+ return [];
46925
+ }
46926
+ return REQUIRED_HOURLY_FILTER_CATEGORY_IDS;
46927
+ }, [endTime, initialTimeFilterCategoryIds, isTimeFilterActive, startTime]);
46928
+ const categoriesForTree = React125.useMemo(() => {
46929
+ if (requiredHourlyFilterCategoryIds.length === 0) {
46930
+ return categories;
46931
+ }
46932
+ const existingCategoryIds = new Set(categories.map((category) => category.id));
46933
+ const mergedCategories = [...categories];
46934
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
46935
+ const fallbackCategory = REQUIRED_HOURLY_HANDOFF_CATEGORIES[categoryId];
46936
+ if (!fallbackCategory || existingCategoryIds.has(categoryId)) {
46937
+ return;
46938
+ }
46939
+ mergedCategories.push(fallbackCategory);
46940
+ existingCategoryIds.add(categoryId);
46941
+ });
46942
+ return mergedCategories;
46943
+ }, [categories, requiredHourlyFilterCategoryIds]);
46391
46944
  const supabase = useSupabase();
46392
46945
  const [clipMetadata, setClipMetadata] = React125.useState({});
46946
+ const [scopedCategoryTotals, setScopedCategoryTotals] = React125.useState({});
46393
46947
  const [loadingCategories, setLoadingCategories] = React125.useState(/* @__PURE__ */ new Set());
46948
+ const isCategoryScopedByTimeFilter = React125.useCallback((categoryId) => {
46949
+ if (!startTime || !endTime || !isTimeFilterActive) {
46950
+ return false;
46951
+ }
46952
+ if (!activeInitialTimeFilter) {
46953
+ return true;
46954
+ }
46955
+ if (activeFilter === categoryId || activeInitialTimeFilter.categoryId === categoryId) {
46956
+ return true;
46957
+ }
46958
+ return Boolean(activeInitialTimeFilter.categoryIds?.includes(categoryId));
46959
+ }, [
46960
+ activeFilter,
46961
+ activeInitialTimeFilter,
46962
+ endTime,
46963
+ isTimeFilterActive,
46964
+ startTime
46965
+ ]);
46966
+ const isRequiredHourlyFilterCategory = React125.useCallback((categoryId) => requiredHourlyFilterCategoryIds.includes(categoryId), [requiredHourlyFilterCategoryIds]);
46967
+ const manualHourlySnapshotKey = React125.useMemo(() => {
46968
+ if (activeInitialTimeFilter || !isTimeFilterActive || !startTime || !endTime || !workspaceId || !date || shift === void 0) {
46969
+ return null;
46970
+ }
46971
+ return `${workspaceId}:${date}:${shift}:${startTime}:${endTime}:${activeTimeFilterTimezone}`;
46972
+ }, [
46973
+ activeInitialTimeFilter,
46974
+ activeTimeFilterTimezone,
46975
+ date,
46976
+ endTime,
46977
+ isTimeFilterActive,
46978
+ shift,
46979
+ startTime,
46980
+ workspaceId
46981
+ ]);
46982
+ const manualHourlySnapshotAppliedKeyRef = React125.useRef(null);
46983
+ const manualHourlySnapshotInFlightKeyRef = React125.useRef(null);
46984
+ const [manualHourlySnapshotFailureKey, setManualHourlySnapshotFailureKey] = React125.useState(null);
46985
+ const isManualHourlySnapshotPending = Boolean(
46986
+ manualHourlySnapshotKey && manualHourlySnapshotAppliedKeyRef.current !== manualHourlySnapshotKey && manualHourlySnapshotFailureKey !== manualHourlySnapshotKey
46987
+ );
46394
46988
  const [categoryPages, setCategoryPages] = React125.useState({});
46395
46989
  const [categoryHasMore, setCategoryHasMore] = React125.useState({});
46396
46990
  const [localClipClassifications, setLocalClipClassifications] = React125.useState({});
46397
46991
  const clipMetadataRef = React125.useRef({});
46398
46992
  const inFlightMetadataRequestsRef = React125.useRef(/* @__PURE__ */ new Set());
46993
+ const inFlightPercentileClipRequestsRef = React125.useRef(/* @__PURE__ */ new Set());
46994
+ const autoSelectedScopedClipRef = React125.useRef(null);
46399
46995
  const previousIdleClipSortRef = React125.useRef(idleClipSort);
46400
46996
  const mergedClipClassifications = React125.useMemo(() => ({
46401
46997
  ...clipClassifications || {},
@@ -46408,12 +47004,14 @@ var FileManagerFilters = ({
46408
47004
  if (!initialTimeFilter?.startTime || !initialTimeFilter?.endTime) {
46409
47005
  return;
46410
47006
  }
47007
+ setActiveInitialTimeFilter(initialTimeFilter);
46411
47008
  setStartTime(initialTimeFilter.startTime);
46412
47009
  setEndTime(initialTimeFilter.endTime);
46413
47010
  setIsTimeFilterActive(true);
46414
47011
  setShowTimeFilterModal(false);
46415
47012
  setStartSearchTerm("");
46416
47013
  setEndSearchTerm("");
47014
+ setScopedCategoryTotals({});
46417
47015
  }, [initialTimeFilter?.startTime, initialTimeFilter?.endTime]);
46418
47016
  React125.useEffect(() => {
46419
47017
  if (previousIdleClipSortRef.current === idleClipSort) {
@@ -46445,18 +47043,66 @@ var FileManagerFilters = ({
46445
47043
  return next;
46446
47044
  });
46447
47045
  }, [idleClipSort]);
47046
+ React125.useEffect(() => {
47047
+ manualHourlySnapshotAppliedKeyRef.current = null;
47048
+ manualHourlySnapshotInFlightKeyRef.current = null;
47049
+ setManualHourlySnapshotFailureKey(null);
47050
+ setScopedCategoryTotals({});
47051
+ setClipMetadata({});
47052
+ setCategoryPages({});
47053
+ setCategoryHasMore({});
47054
+ setPercentileClips({});
47055
+ setPercentileCounts({
47056
+ "fast-cycles": null,
47057
+ "slow-cycles": null
47058
+ });
47059
+ }, [activeTimeFilterKey]);
46448
47060
  const isCategoryExternallyManaged = React125.useCallback((categoryId) => {
46449
47061
  if (!categoryId) {
46450
47062
  return false;
46451
47063
  }
46452
- if (prefetchedClipMetadata && Array.isArray(prefetchedClipMetadata[categoryId]) && prefetchedClipMetadata[categoryId].length > 0) {
47064
+ if (isManualHourlySnapshotPending && requiredHourlyFilterCategoryIds.includes(categoryId)) {
47065
+ return true;
47066
+ }
47067
+ if (prefetchedClipMetadata && Array.isArray(prefetchedClipMetadata[categoryId]) && (prefetchedClipMetadata[categoryId].length > 0 || typeof prefetchedClipTotals?.[categoryId] === "number")) {
47068
+ return true;
47069
+ }
47070
+ if (prefetchedPercentileClips && Array.isArray(prefetchedPercentileClips[categoryId]) && (prefetchedPercentileClips[categoryId].length > 0 || isCategoryScopedByTimeFilter(categoryId) && typeof prefetchedClipTotals?.[categoryId] === "number")) {
46453
47071
  return true;
46454
47072
  }
46455
47073
  if (externallyManagedLoadingCategories?.[categoryId]) {
46456
47074
  return true;
46457
47075
  }
46458
47076
  return false;
46459
- }, [prefetchedClipMetadata, externallyManagedLoadingCategories]);
47077
+ }, [
47078
+ externallyManagedLoadingCategories,
47079
+ isManualHourlySnapshotPending,
47080
+ isCategoryScopedByTimeFilter,
47081
+ prefetchedClipMetadata,
47082
+ prefetchedClipTotals,
47083
+ prefetchedPercentileClips,
47084
+ requiredHourlyFilterCategoryIds
47085
+ ]);
47086
+ const getAvailableClipMetadata = React125.useCallback((categoryId) => {
47087
+ const localClips = clipMetadata[categoryId];
47088
+ if (Array.isArray(localClips) && localClips.length > 0) {
47089
+ return localClips;
47090
+ }
47091
+ const prefetchedClips = prefetchedClipMetadata?.[categoryId];
47092
+ if (Array.isArray(prefetchedClips) && prefetchedClips.length > 0) {
47093
+ return prefetchedClips;
47094
+ }
47095
+ return [];
47096
+ }, [clipMetadata, prefetchedClipMetadata]);
47097
+ const hasKnownClipMetadata = React125.useCallback((categoryId) => {
47098
+ if (Array.isArray(clipMetadata[categoryId])) {
47099
+ return true;
47100
+ }
47101
+ if (Array.isArray(prefetchedClipMetadata?.[categoryId])) {
47102
+ return true;
47103
+ }
47104
+ return typeof prefetchedClipTotals?.[categoryId] === "number";
47105
+ }, [clipMetadata, prefetchedClipMetadata, prefetchedClipTotals]);
46460
47106
  React125.useEffect(() => {
46461
47107
  if (!prefetchedClipMetadata) {
46462
47108
  return;
@@ -46479,6 +47125,22 @@ var FileManagerFilters = ({
46479
47125
  });
46480
47126
  return changed ? next : prev;
46481
47127
  });
47128
+ setScopedCategoryTotals((prev) => {
47129
+ let changed = false;
47130
+ const next = { ...prev };
47131
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
47132
+ if (!Array.isArray(clips) || !isCategoryScopedByTimeFilter(categoryId)) {
47133
+ return;
47134
+ }
47135
+ const nextTotal = typeof prefetchedClipTotals?.[categoryId] === "number" ? Math.max(0, prefetchedClipTotals[categoryId]) : clips.length;
47136
+ if (next[categoryId] === nextTotal) {
47137
+ return;
47138
+ }
47139
+ next[categoryId] = nextTotal;
47140
+ changed = true;
47141
+ });
47142
+ return changed ? next : prev;
47143
+ });
46482
47144
  setCategoryPages((prev) => {
46483
47145
  let changed = false;
46484
47146
  const next = { ...prev };
@@ -46503,7 +47165,7 @@ var FileManagerFilters = ({
46503
47165
  return;
46504
47166
  }
46505
47167
  const knownTotal = typeof counts?.[categoryId] === "number" ? counts[categoryId] : null;
46506
- const inferredHasMore = knownTotal !== null ? clips.length < knownTotal : prev[categoryId];
47168
+ const inferredHasMore = isCategoryScopedByTimeFilter(categoryId) ? false : knownTotal !== null ? clips.length < knownTotal : prev[categoryId];
46507
47169
  if (typeof inferredHasMore !== "boolean" || next[categoryId] === inferredHasMore) {
46508
47170
  return;
46509
47171
  }
@@ -46512,8 +47174,68 @@ var FileManagerFilters = ({
46512
47174
  });
46513
47175
  return changed ? next : prev;
46514
47176
  });
46515
- }, [prefetchedClipMetadata, counts]);
47177
+ }, [prefetchedClipMetadata, prefetchedClipTotals, counts, isCategoryScopedByTimeFilter]);
47178
+ React125.useEffect(() => {
47179
+ if (!prefetchedPercentileClips) {
47180
+ return;
47181
+ }
47182
+ setPercentileClips((prev) => {
47183
+ let changed = false;
47184
+ const next = { ...prev };
47185
+ ["fast-cycles", "slow-cycles"].forEach((categoryId) => {
47186
+ const clips = prefetchedPercentileClips[categoryId];
47187
+ if (!Array.isArray(clips)) {
47188
+ return;
47189
+ }
47190
+ const previousClips = prev[categoryId] || [];
47191
+ const previousSignature = previousClips.map((clip) => clip.id).join("|");
47192
+ const nextSignature = clips.map((clip) => clip.id).join("|");
47193
+ if (previousSignature === nextSignature) {
47194
+ return;
47195
+ }
47196
+ next[categoryId] = clips;
47197
+ changed = true;
47198
+ });
47199
+ return changed ? next : prev;
47200
+ });
47201
+ setPercentileCounts((prev) => {
47202
+ let changed = false;
47203
+ const next = { ...prev };
47204
+ ["fast-cycles", "slow-cycles"].forEach((categoryId) => {
47205
+ const clips = prefetchedPercentileClips[categoryId];
47206
+ if (!Array.isArray(clips)) {
47207
+ return;
47208
+ }
47209
+ const nextTotal = typeof prefetchedClipTotals?.[categoryId] === "number" ? Math.max(0, prefetchedClipTotals[categoryId]) : clips.length;
47210
+ if (next[categoryId] === nextTotal) {
47211
+ return;
47212
+ }
47213
+ next[categoryId] = nextTotal;
47214
+ changed = true;
47215
+ });
47216
+ return changed ? next : prev;
47217
+ });
47218
+ }, [prefetchedClipTotals, prefetchedPercentileClips]);
46516
47219
  const { state: filterState } = useClipFilter();
47220
+ const shouldShowCategory = React125.useCallback((categoryId) => {
47221
+ switch (categoryId) {
47222
+ case "fast-cycles":
47223
+ return showPercentileCycleFilters && filterState.showFastCycles;
47224
+ case "slow-cycles":
47225
+ return showPercentileCycleFilters && filterState.showSlowCycles;
47226
+ case "longest-idles":
47227
+ return false;
47228
+ // filterState.showLongestIdles; // Temporarily disabled
47229
+ case "cycle_completion":
47230
+ return filterState.showCycleCompletion;
47231
+ case "idle_time":
47232
+ return filterState.showIdleTime;
47233
+ case "sop_deviations":
47234
+ return filterState.showSopDeviations;
47235
+ default:
47236
+ return true;
47237
+ }
47238
+ }, [filterState, showPercentileCycleFilters]);
46517
47239
  const [percentileCounts, setPercentileCounts] = React125.useState({
46518
47240
  "fast-cycles": null,
46519
47241
  "slow-cycles": null
@@ -46623,11 +47345,12 @@ var FileManagerFilters = ({
46623
47345
  return null;
46624
47346
  }
46625
47347
  }, [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]);
47348
+ 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
47349
  const fetchClipMetadataPage = React125.useCallback(async (categoryId, page = 1) => {
46628
47350
  if (!workspaceId || !date || shift === void 0) {
46629
47351
  throw new Error("Missing required params for clip metadata fetch");
46630
47352
  }
47353
+ const shouldScopeToTimeFilter = isCategoryScopedByTimeFilter(categoryId);
46631
47354
  const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
46632
47355
  method: "POST",
46633
47356
  headers: {
@@ -46641,9 +47364,12 @@ var FileManagerFilters = ({
46641
47364
  category: categoryId,
46642
47365
  page,
46643
47366
  limit: CLIP_METADATA_PAGE_SIZE,
46644
- knownTotal: typeof counts?.[categoryId] === "number" ? counts[categoryId] : null,
47367
+ knownTotal: shouldScopeToTimeFilter ? null : typeof counts?.[categoryId] === "number" ? counts[categoryId] : null,
46645
47368
  snapshotDateTime,
46646
47369
  snapshotClipId,
47370
+ startTime: shouldScopeToTimeFilter ? startTime : void 0,
47371
+ endTime: shouldScopeToTimeFilter ? endTime : void 0,
47372
+ timeFilterTimezone: shouldScopeToTimeFilter ? activeTimeFilterTimezone : void 0,
46647
47373
  sort: categoryId === RECENT_FLOW_RED_STREAK_CLIP_TYPE2 ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"
46648
47374
  }),
46649
47375
  redirectReason: "session_expired"
@@ -46652,7 +47378,20 @@ var FileManagerFilters = ({
46652
47378
  throw new Error(`API error: ${response.status}`);
46653
47379
  }
46654
47380
  return response.json();
46655
- }, [workspaceId, date, shift, counts, snapshotDateTime, snapshotClipId, idleClipSort, supabase]);
47381
+ }, [
47382
+ activeTimeFilterTimezone,
47383
+ endTime,
47384
+ isCategoryScopedByTimeFilter,
47385
+ startTime,
47386
+ workspaceId,
47387
+ date,
47388
+ shift,
47389
+ counts,
47390
+ snapshotDateTime,
47391
+ snapshotClipId,
47392
+ idleClipSort,
47393
+ supabase
47394
+ ]);
46656
47395
  const seedIdleClassifications = React125.useCallback(async (clips) => {
46657
47396
  if (!idleTimeVlmEnabled || clips.length === 0) {
46658
47397
  return;
@@ -46720,6 +47459,12 @@ var FileManagerFilters = ({
46720
47459
  ...prev,
46721
47460
  [categoryId]: page === 1 ? data.clips : [...prev[categoryId] || [], ...data.clips]
46722
47461
  }));
47462
+ if (isCategoryScopedByTimeFilter(categoryId)) {
47463
+ setScopedCategoryTotals((prev) => ({
47464
+ ...prev,
47465
+ [categoryId]: Math.max(0, Number(data.total || 0))
47466
+ }));
47467
+ }
46723
47468
  if (categoryId === "idle_time" && idleTimeVlmEnabled) {
46724
47469
  await seedIdleClassifications(data.clips || []);
46725
47470
  }
@@ -46736,7 +47481,94 @@ var FileManagerFilters = ({
46736
47481
  return newSet;
46737
47482
  });
46738
47483
  }
46739
- }, [workspaceId, date, shift, fetchClipMetadataPage, idleTimeVlmEnabled, seedIdleClassifications, getMetadataLoadingKey]);
47484
+ }, [workspaceId, date, shift, fetchClipMetadataPage, idleTimeVlmEnabled, seedIdleClassifications, getMetadataLoadingKey, isCategoryScopedByTimeFilter]);
47485
+ React125.useCallback(async (categoryId) => {
47486
+ if (!workspaceId || !date || shift === void 0) {
47487
+ console.warn("[FileManager] Missing required params for full clip metadata fetch");
47488
+ return;
47489
+ }
47490
+ const loadingKey = `${getMetadataLoadingKey(categoryId, 1)}-all`;
47491
+ if (inFlightMetadataRequestsRef.current.has(loadingKey)) {
47492
+ return;
47493
+ }
47494
+ inFlightMetadataRequestsRef.current.add(loadingKey);
47495
+ setLoadingCategories((prev) => /* @__PURE__ */ new Set([...prev, loadingKey]));
47496
+ try {
47497
+ let accumulatedClips = [];
47498
+ let currentPage = 0;
47499
+ let hasMore = true;
47500
+ while (hasMore) {
47501
+ const nextPage = currentPage + 1;
47502
+ const pageData = await fetchClipMetadataPage(categoryId, nextPage);
47503
+ const pageClips = pageData.clips || [];
47504
+ accumulatedClips = [...accumulatedClips, ...pageClips];
47505
+ currentPage = nextPage;
47506
+ hasMore = Boolean(pageData.hasMore && pageClips.length > 0);
47507
+ }
47508
+ const dedupedClips = accumulatedClips.filter((clip, index, arr) => {
47509
+ const clipKey = clip.clipId || clip.id;
47510
+ return arr.findIndex((item) => (item.clipId || item.id) === clipKey) === index;
47511
+ });
47512
+ setClipMetadata((prev) => ({
47513
+ ...prev,
47514
+ [categoryId]: dedupedClips
47515
+ }));
47516
+ setCategoryPages((prev) => ({ ...prev, [categoryId]: Math.max(currentPage, 1) }));
47517
+ setCategoryHasMore((prev) => ({ ...prev, [categoryId]: false }));
47518
+ if (categoryId === "idle_time" && idleTimeVlmEnabled) {
47519
+ await seedIdleClassifications(dedupedClips);
47520
+ }
47521
+ console.log(`[FileManager] Loaded all ${dedupedClips.length} clips for ${categoryId} due to chart time filter`);
47522
+ } catch (error) {
47523
+ console.error("[FileManager] Error fetching full clip metadata:", error);
47524
+ } finally {
47525
+ inFlightMetadataRequestsRef.current.delete(loadingKey);
47526
+ setLoadingCategories((prev) => {
47527
+ const newSet = new Set(prev);
47528
+ newSet.delete(loadingKey);
47529
+ return newSet;
47530
+ });
47531
+ }
47532
+ }, [
47533
+ workspaceId,
47534
+ date,
47535
+ shift,
47536
+ fetchClipMetadataPage,
47537
+ idleTimeVlmEnabled,
47538
+ seedIdleClassifications,
47539
+ getMetadataLoadingKey
47540
+ ]);
47541
+ const isInitialTimeFilterCategory = React125.useCallback((categoryId) => {
47542
+ if (!startTime || !endTime || !activeInitialTimeFilter) {
47543
+ return false;
47544
+ }
47545
+ if (activeFilter === categoryId) {
47546
+ return true;
47547
+ }
47548
+ if (activeInitialTimeFilter.categoryId === categoryId) {
47549
+ return true;
47550
+ }
47551
+ return Array.isArray(activeInitialTimeFilter.categoryIds) && activeInitialTimeFilter.categoryIds.includes(categoryId);
47552
+ }, [
47553
+ activeFilter,
47554
+ activeInitialTimeFilter,
47555
+ endTime,
47556
+ startTime
47557
+ ]);
47558
+ const hasCompleteMetadataForInitialTimeFilter = React125.useCallback((categoryId) => {
47559
+ if (!isInitialTimeFilterCategory(categoryId)) {
47560
+ return true;
47561
+ }
47562
+ const loadedClips = clipMetadataRef.current[categoryId] || [];
47563
+ const knownTotal = typeof scopedCategoryTotals[categoryId] === "number" ? scopedCategoryTotals[categoryId] : null;
47564
+ if (categoryHasMore[categoryId]) {
47565
+ return false;
47566
+ }
47567
+ if (knownTotal !== null && loadedClips.length < knownTotal) {
47568
+ return false;
47569
+ }
47570
+ return loadedClips.length > 0 || knownTotal === 0;
47571
+ }, [categoryHasMore, isInitialTimeFilterCategory, scopedCategoryTotals]);
46740
47572
  const ensureAllIdleTimeClipMetadataLoaded = React125.useCallback(async () => {
46741
47573
  if (!workspaceId || !date || shift === void 0) {
46742
47574
  return;
@@ -46802,6 +47634,12 @@ var FileManagerFilters = ({
46802
47634
  console.warn("[FileManager] Missing required params for percentile clips fetch");
46803
47635
  return;
46804
47636
  }
47637
+ const shouldScopeToTimeFilter = isCategoryScopedByTimeFilter(type);
47638
+ const requestKey = `${type}:${date}:${shift}:${filterState.percentile}:${shouldScopeToTimeFilter ? activeTimeFilterKey : "unscoped"}`;
47639
+ if (inFlightPercentileClipRequestsRef.current.has(requestKey)) {
47640
+ return;
47641
+ }
47642
+ inFlightPercentileClipRequestsRef.current.add(requestKey);
46805
47643
  try {
46806
47644
  const startDate = `${date}T00:00:00Z`;
46807
47645
  const endDate = `${date}T23:59:59Z`;
@@ -46817,7 +47655,10 @@ var FileManagerFilters = ({
46817
47655
  endDate,
46818
47656
  percentile: filterState.percentile,
46819
47657
  shiftId: shift,
46820
- limit: 50,
47658
+ limit: shouldScopeToTimeFilter ? 500 : 100,
47659
+ startTime: shouldScopeToTimeFilter ? startTime : void 0,
47660
+ endTime: shouldScopeToTimeFilter ? endTime : void 0,
47661
+ timeFilterTimezone: shouldScopeToTimeFilter ? activeTimeFilterTimezone : void 0,
46821
47662
  // The actual percentile action (fast-cycles, slow-cycles, idle-times)
46822
47663
  percentileAction: type
46823
47664
  }),
@@ -46832,7 +47673,7 @@ var FileManagerFilters = ({
46832
47673
  [type]: data.clips || []
46833
47674
  }));
46834
47675
  setPercentileCounts((prev) => {
46835
- if (typeof prev[type] === "number") {
47676
+ if (!shouldScopeToTimeFilter && typeof prev[type] === "number") {
46836
47677
  return prev;
46837
47678
  }
46838
47679
  return {
@@ -46843,8 +47684,206 @@ var FileManagerFilters = ({
46843
47684
  console.log(`[FileManager] Loaded ${data.clips?.length || 0} ${type} clips (percentile: ${filterState.percentile}%)`);
46844
47685
  } catch (error) {
46845
47686
  console.error(`[FileManager] Error fetching ${type} clips:`, error);
47687
+ } finally {
47688
+ inFlightPercentileClipRequestsRef.current.delete(requestKey);
47689
+ }
47690
+ }, [
47691
+ activeTimeFilterTimezone,
47692
+ activeTimeFilterKey,
47693
+ endTime,
47694
+ isCategoryScopedByTimeFilter,
47695
+ startTime,
47696
+ workspaceId,
47697
+ date,
47698
+ shift,
47699
+ filterState.percentile,
47700
+ showPercentileCycleFilters,
47701
+ supabase
47702
+ ]);
47703
+ React125.useEffect(() => {
47704
+ if (!manualHourlySnapshotKey) {
47705
+ return;
47706
+ }
47707
+ if (manualHourlySnapshotAppliedKeyRef.current === manualHourlySnapshotKey) {
47708
+ return;
47709
+ }
47710
+ if (manualHourlySnapshotInFlightKeyRef.current === manualHourlySnapshotKey) {
47711
+ return;
47712
+ }
47713
+ manualHourlySnapshotInFlightKeyRef.current = manualHourlySnapshotKey;
47714
+ setManualHourlySnapshotFailureKey((prev) => prev === manualHourlySnapshotKey ? null : prev);
47715
+ const applyHourlySnapshot = async () => {
47716
+ try {
47717
+ const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
47718
+ method: "POST",
47719
+ headers: {
47720
+ "Content-Type": "application/json"
47721
+ },
47722
+ body: JSON.stringify({
47723
+ action: "hourly-snapshot",
47724
+ workspaceId,
47725
+ date,
47726
+ shift,
47727
+ startTime,
47728
+ endTime,
47729
+ timeFilterTimezone: activeTimeFilterTimezone
47730
+ }),
47731
+ redirectReason: "session_expired"
47732
+ });
47733
+ if (!response.ok) {
47734
+ throw new Error(`API error: ${response.status}`);
47735
+ }
47736
+ const snapshot = await response.json();
47737
+ const metadataByCategory = snapshot?.metadataByCategory && typeof snapshot.metadataByCategory === "object" ? snapshot.metadataByCategory : {};
47738
+ const percentileClipsByCategory = snapshot?.percentileClipsByCategory && typeof snapshot.percentileClipsByCategory === "object" ? snapshot.percentileClipsByCategory : {};
47739
+ const rawTotalsByCategory = snapshot?.totalsByCategory && typeof snapshot.totalsByCategory === "object" ? snapshot.totalsByCategory : {};
47740
+ const totalsByCategory = requiredHourlyFilterCategoryIds.reduce((accumulator, categoryId) => {
47741
+ const rawTotal = rawTotalsByCategory[categoryId];
47742
+ if (typeof rawTotal === "number" && Number.isFinite(rawTotal)) {
47743
+ accumulator[categoryId] = Math.max(0, rawTotal);
47744
+ return accumulator;
47745
+ }
47746
+ if (categoryId === "fast-cycles" || categoryId === "slow-cycles") {
47747
+ accumulator[categoryId] = Array.isArray(percentileClipsByCategory[categoryId]) ? percentileClipsByCategory[categoryId].length : 0;
47748
+ return accumulator;
47749
+ }
47750
+ accumulator[categoryId] = Array.isArray(metadataByCategory[categoryId]) ? metadataByCategory[categoryId].length : 0;
47751
+ return accumulator;
47752
+ }, {});
47753
+ setClipMetadata((prev) => {
47754
+ const next = { ...prev };
47755
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
47756
+ if (categoryId === "fast-cycles" || categoryId === "slow-cycles") {
47757
+ return;
47758
+ }
47759
+ next[categoryId] = Array.isArray(metadataByCategory[categoryId]) ? metadataByCategory[categoryId] : [];
47760
+ });
47761
+ return next;
47762
+ });
47763
+ setScopedCategoryTotals((prev) => ({
47764
+ ...prev,
47765
+ ...totalsByCategory
47766
+ }));
47767
+ if (showPercentileCycleFilters) {
47768
+ setPercentileClips((prev) => ({
47769
+ ...prev,
47770
+ "fast-cycles": Array.isArray(percentileClipsByCategory["fast-cycles"]) ? percentileClipsByCategory["fast-cycles"] : [],
47771
+ "slow-cycles": Array.isArray(percentileClipsByCategory["slow-cycles"]) ? percentileClipsByCategory["slow-cycles"] : []
47772
+ }));
47773
+ setPercentileCounts((prev) => ({
47774
+ ...prev,
47775
+ "fast-cycles": totalsByCategory["fast-cycles"] ?? 0,
47776
+ "slow-cycles": totalsByCategory["slow-cycles"] ?? 0
47777
+ }));
47778
+ }
47779
+ setCategoryPages((prev) => {
47780
+ const next = { ...prev };
47781
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
47782
+ next[categoryId] = 1;
47783
+ });
47784
+ return next;
47785
+ });
47786
+ setCategoryHasMore((prev) => {
47787
+ const next = { ...prev };
47788
+ requiredHourlyFilterCategoryIds.forEach((categoryId) => {
47789
+ next[categoryId] = false;
47790
+ });
47791
+ return next;
47792
+ });
47793
+ const idleClips = metadataByCategory.idle_time;
47794
+ if (Array.isArray(idleClips) && idleClips.length > 0) {
47795
+ await seedIdleClassifications(idleClips);
47796
+ }
47797
+ manualHourlySnapshotAppliedKeyRef.current = manualHourlySnapshotKey;
47798
+ setManualHourlySnapshotFailureKey((prev) => prev === manualHourlySnapshotKey ? null : prev);
47799
+ } catch (error) {
47800
+ console.error("[FileManager] Error fetching manual hourly snapshot:", error);
47801
+ setManualHourlySnapshotFailureKey(manualHourlySnapshotKey);
47802
+ } finally {
47803
+ if (manualHourlySnapshotInFlightKeyRef.current === manualHourlySnapshotKey) {
47804
+ manualHourlySnapshotInFlightKeyRef.current = null;
47805
+ }
47806
+ }
47807
+ };
47808
+ void applyHourlySnapshot();
47809
+ }, [
47810
+ activeTimeFilterTimezone,
47811
+ date,
47812
+ endTime,
47813
+ manualHourlySnapshotKey,
47814
+ requiredHourlyFilterCategoryIds,
47815
+ seedIdleClassifications,
47816
+ shift,
47817
+ showPercentileCycleFilters,
47818
+ startTime,
47819
+ supabase,
47820
+ workspaceId
47821
+ ]);
47822
+ React125.useEffect(() => {
47823
+ if (!startTime || !endTime || !activeInitialTimeFilter || initialTimeFilterCategoryIds.length === 0) {
47824
+ return;
46846
47825
  }
46847
- }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase]);
47826
+ initialTimeFilterCategoryIds.forEach((categoryId) => {
47827
+ if (categoryId === "fast-cycles" || categoryId === "slow-cycles") {
47828
+ const hasScopedPercentileResult = typeof percentileCounts[categoryId] === "number";
47829
+ if (!isCategoryExternallyManaged(categoryId) && !hasScopedPercentileResult && showPercentileCycleFilters) {
47830
+ fetchPercentileClips(categoryId);
47831
+ }
47832
+ return;
47833
+ }
47834
+ if (isCategoryExternallyManaged(categoryId)) {
47835
+ return;
47836
+ }
47837
+ if (!hasCompleteMetadataForInitialTimeFilter(categoryId)) {
47838
+ fetchClipMetadata(categoryId, 1);
47839
+ }
47840
+ });
47841
+ }, [
47842
+ activeInitialTimeFilter,
47843
+ endTime,
47844
+ fetchClipMetadata,
47845
+ fetchPercentileClips,
47846
+ hasCompleteMetadataForInitialTimeFilter,
47847
+ initialTimeFilterCategoryIds,
47848
+ isCategoryExternallyManaged,
47849
+ percentileCounts,
47850
+ showPercentileCycleFilters,
47851
+ startTime
47852
+ ]);
47853
+ React125.useEffect(() => {
47854
+ if (!startTime || !endTime || !isTimeFilterActive || activeInitialTimeFilter) {
47855
+ return;
47856
+ }
47857
+ categoriesForTree.forEach((category) => {
47858
+ if (!shouldShowCategory(category.id) || isCategoryExternallyManaged(category.id)) {
47859
+ return;
47860
+ }
47861
+ if (typeof scopedCategoryTotals[category.id] !== "number") {
47862
+ fetchClipMetadata(category.id, 1);
47863
+ }
47864
+ });
47865
+ if (showPercentileCycleFilters) {
47866
+ if (!isCategoryExternallyManaged("fast-cycles") && typeof percentileCounts["fast-cycles"] !== "number") {
47867
+ fetchPercentileClips("fast-cycles");
47868
+ }
47869
+ if (!isCategoryExternallyManaged("slow-cycles") && typeof percentileCounts["slow-cycles"] !== "number") {
47870
+ fetchPercentileClips("slow-cycles");
47871
+ }
47872
+ }
47873
+ }, [
47874
+ activeInitialTimeFilter,
47875
+ categoriesForTree,
47876
+ endTime,
47877
+ fetchClipMetadata,
47878
+ fetchPercentileClips,
47879
+ isCategoryExternallyManaged,
47880
+ isTimeFilterActive,
47881
+ percentileCounts,
47882
+ scopedCategoryTotals,
47883
+ shouldShowCategory,
47884
+ showPercentileCycleFilters,
47885
+ startTime
47886
+ ]);
46848
47887
  const percentileCountsKey = React125.useMemo(() => {
46849
47888
  if (!workspaceId || !date || shift === void 0) {
46850
47889
  return null;
@@ -46867,12 +47906,15 @@ var FileManagerFilters = ({
46867
47906
  }
46868
47907
  percentileCountsKeyRef.current = percentileCountsKey;
46869
47908
  percentilePrefetchRef.current = { key: null, types: /* @__PURE__ */ new Set() };
47909
+ if (prefetchedPercentileClips && (Array.isArray(prefetchedPercentileClips["fast-cycles"]) || Array.isArray(prefetchedPercentileClips["slow-cycles"]))) {
47910
+ return;
47911
+ }
46870
47912
  setPercentileCounts({
46871
47913
  "fast-cycles": null,
46872
47914
  "slow-cycles": null
46873
47915
  });
46874
47916
  setPercentileClips({});
46875
- }, [showPercentileCycleFilters, percentileCountsKey]);
47917
+ }, [prefetchedPercentileClips, showPercentileCycleFilters, percentileCountsKey]);
46876
47918
  React125.useEffect(() => {
46877
47919
  if (!showPercentileCycleFilters) {
46878
47920
  return;
@@ -46928,11 +47970,20 @@ var FileManagerFilters = ({
46928
47970
  const data = await response.json();
46929
47971
  const fastCycles = data?.counts?.["fast-cycles"];
46930
47972
  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
- }));
47973
+ if (typeof fastCycles === "number" || typeof slowCycles === "number") {
47974
+ setPercentileCounts((prev) => {
47975
+ const nextFastCycles = typeof fastCycles === "number" ? fastCycles : prev["fast-cycles"];
47976
+ const nextSlowCycles = typeof slowCycles === "number" ? slowCycles : prev["slow-cycles"];
47977
+ if (prev["fast-cycles"] === nextFastCycles && prev["slow-cycles"] === nextSlowCycles) {
47978
+ return prev;
47979
+ }
47980
+ return {
47981
+ ...prev,
47982
+ "fast-cycles": nextFastCycles,
47983
+ "slow-cycles": nextSlowCycles
47984
+ };
47985
+ });
47986
+ }
46936
47987
  if (options?.prefetchClips) {
46937
47988
  if (percentilePrefetchRef.current.key !== requestKey) {
46938
47989
  percentilePrefetchRef.current = { key: requestKey, types: /* @__PURE__ */ new Set() };
@@ -46951,37 +48002,30 @@ var FileManagerFilters = ({
46951
48002
  }
46952
48003
  }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase, percentileCounts, percentileClips, fetchPercentileClips]);
46953
48004
  React125.useEffect(() => {
46954
- if (!showPercentileCycleFilters || !isReady || !percentileCountsKey) {
48005
+ if (!showPercentileCycleFilters || !isReady || !percentileCountsKey || isTimeFilterActive) {
46955
48006
  return;
46956
48007
  }
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;
48008
+ fetchPercentileCounts({ prefetchClips: true });
48009
+ }, [showPercentileCycleFilters, isReady, percentileCountsKey, fetchPercentileCounts, isTimeFilterActive]);
48010
+ React125.useEffect(() => {
48011
+ if (!isReady || isTimeFilterActive || activeFilter !== RECENT_FLOW_RED_STREAK_CLIP_TYPE2) {
48012
+ return;
46983
48013
  }
46984
- }, [filterState, showPercentileCycleFilters]);
48014
+ ["cycle_completion", "idle_time"].forEach((categoryId) => {
48015
+ if ((counts?.[categoryId] || 0) <= 0 || hasKnownClipMetadata(categoryId) || isCategoryExternallyManaged(categoryId)) {
48016
+ return;
48017
+ }
48018
+ fetchClipMetadata(categoryId, 1);
48019
+ });
48020
+ }, [
48021
+ activeFilter,
48022
+ counts,
48023
+ fetchClipMetadata,
48024
+ hasKnownClipMetadata,
48025
+ isCategoryExternallyManaged,
48026
+ isReady,
48027
+ isTimeFilterActive
48028
+ ]);
46985
48029
  const getPercentileIcon = React125.useCallback((type, isExpanded, colorClasses) => {
46986
48030
  const iconMap = {
46987
48031
  "fast-cycles": { icon: lucideReact.TrendingUp, color: "text-green-600" },
@@ -47010,12 +48054,29 @@ var FileManagerFilters = ({
47010
48054
  newExpanded.add(activeFilter);
47011
48055
  return newExpanded;
47012
48056
  });
47013
- const category = categories.find((cat) => cat.id === activeFilter);
47014
- if (category && !isCategoryExternallyManaged(activeFilter) && !clipMetadataRef.current[activeFilter]) {
47015
- fetchClipMetadata(activeFilter, 1);
48057
+ const category = categoriesForTree.find((cat) => cat.id === activeFilter);
48058
+ if (category) {
48059
+ if (isInitialTimeFilterCategory(activeFilter) && !isCategoryExternallyManaged(activeFilter) && !hasCompleteMetadataForInitialTimeFilter(activeFilter)) {
48060
+ fetchClipMetadata(activeFilter, 1);
48061
+ } else if (!isCategoryExternallyManaged(activeFilter) && !clipMetadataRef.current[activeFilter]) {
48062
+ fetchClipMetadata(activeFilter, 1);
48063
+ }
47016
48064
  }
47017
48065
  }
47018
48066
  }, [activeFilter]);
48067
+ React125.useEffect(() => {
48068
+ const requestedCategory = activeFilter;
48069
+ if (!requestedCategory || !isInitialTimeFilterCategory(requestedCategory) || isCategoryExternallyManaged(requestedCategory) || hasCompleteMetadataForInitialTimeFilter(requestedCategory)) {
48070
+ return;
48071
+ }
48072
+ fetchClipMetadata(requestedCategory, 1);
48073
+ }, [
48074
+ activeFilter,
48075
+ fetchClipMetadata,
48076
+ hasCompleteMetadataForInitialTimeFilter,
48077
+ isCategoryExternallyManaged,
48078
+ isInitialTimeFilterCategory
48079
+ ]);
47019
48080
  React125.useEffect(() => {
47020
48081
  const handleEscape = (e) => {
47021
48082
  if (e.key === "Escape") {
@@ -47079,25 +48140,34 @@ var FileManagerFilters = ({
47079
48140
  }
47080
48141
  try {
47081
48142
  const clipDate = new Date(clipTimestamp);
47082
- const clipTimeStr = clipDate.toLocaleTimeString("en-US", {
48143
+ const clipParts = new Intl.DateTimeFormat("en-US", {
47083
48144
  hour12: false,
47084
48145
  hour: "2-digit",
47085
48146
  minute: "2-digit",
47086
- timeZone: timezone
47087
- });
47088
- return clipTimeStr >= startTime && clipTimeStr <= endTime;
48147
+ timeZone: activeTimeFilterTimezone
48148
+ }).formatToParts(clipDate);
48149
+ const hourValue = clipParts.find((part) => part.type === "hour")?.value;
48150
+ const minuteValue = clipParts.find((part) => part.type === "minute")?.value;
48151
+ const clipMinute = timeValueToMinutes(`${hourValue}:${minuteValue}`);
48152
+ return clipMinute === null ? false : isMinuteInTimeWindow(clipMinute, startTime, endTime);
47089
48153
  } catch (error) {
47090
48154
  console.error("[FileManager] Error parsing clip timestamp:", error);
47091
48155
  return true;
47092
48156
  }
47093
- }, [isTimeFilterActive, startTime, endTime, timezone]);
48157
+ }, [isTimeFilterActive, startTime, endTime, activeTimeFilterTimezone]);
47094
48158
  const filterTree = React125.useMemo(() => {
47095
48159
  const tree = [];
47096
48160
  const regularCategoryNodes = [];
47097
- categories.forEach((category) => {
48161
+ categoriesForTree.forEach((category) => {
48162
+ if (category.id === "fast-cycles" || category.id === "slow-cycles") {
48163
+ return;
48164
+ }
47098
48165
  const categoryCount = counts?.[category.id] || 0;
47099
- const categoryClips = clipMetadata[category.id] || [];
47100
- let filteredClips = categoryClips.filter((clip) => isClipInTimeRange(clip.clip_timestamp));
48166
+ const categoryMetadataClips = getAvailableClipMetadata(category.id);
48167
+ const categoryVideoFallbackClips = categoryMetadataClips.length === 0 && isInitialTimeFilterCategory(category.id) ? videos.filter((video) => video.type === category.id).map(buildClipMetadataFromVideo).filter((clip) => isClipInTimeRange(clip.clip_timestamp)) : [];
48168
+ const categoryClips = categoryMetadataClips.length > 0 ? categoryMetadataClips : categoryVideoFallbackClips;
48169
+ const timeFilteredClips = categoryClips.filter((clip) => isClipInTimeRange(clip.clip_timestamp));
48170
+ let filteredClips = timeFilteredClips;
47101
48171
  if (category.id === RECENT_FLOW_RED_STREAK_CLIP_TYPE2) {
47102
48172
  filteredClips = sortRedFlowMetadata(filteredClips);
47103
48173
  }
@@ -47107,9 +48177,22 @@ var FileManagerFilters = ({
47107
48177
  return classification?.label === idleLabelFilter;
47108
48178
  });
47109
48179
  }
47110
- const displayCount = isTimeFilterActive || category.id === "idle_time" && idleLabelFilter ? filteredClips.length : categoryCount;
48180
+ const scopedTotal = typeof scopedCategoryTotals[category.id] === "number" ? scopedCategoryTotals[category.id] : null;
48181
+ const isScopedByTimeFilter = isCategoryScopedByTimeFilter(category.id);
48182
+ const scopedPageLoaded = isScopedByTimeFilter && typeof scopedCategoryTotals[category.id] === "number";
48183
+ const scopedResponseHadOutOfHourClips = categoryClips.length > filteredClips.length;
48184
+ const shouldTrustScopedTotal = !scopedResponseHadOutOfHourClips || Boolean(categoryHasMore[category.id]);
48185
+ const isCategoryMetadataLoading = Array.from(loadingCategories).some((key) => key.startsWith(`${category.id}-`));
48186
+ const isScopedTotalPending = isTimeFilterActive && isScopedByTimeFilter && scopedTotal === null;
48187
+ const displayCount = isScopedTotalPending ? null : isTimeFilterActive && isScopedByTimeFilter && scopedTotal !== null ? shouldTrustScopedTotal ? scopedTotal : filteredClips.length : isTimeFilterActive || category.id === "idle_time" && idleLabelFilter ? filteredClips.length : categoryCount;
48188
+ const shouldShowScopedEmptyCategory = Boolean(
48189
+ scopedPageLoaded && !isCategoryMetadataLoading && !categoryHasMore[category.id] && filteredClips.length === 0 && (isRequiredHourlyFilterCategory(category.id) || activeFilter === category.id)
48190
+ );
48191
+ const shouldShowScopedLoadingCategory = Boolean(
48192
+ isScopedTotalPending && (isRequiredHourlyFilterCategory(category.id) || !activeInitialTimeFilter || activeFilter === category.id)
48193
+ );
47111
48194
  const shouldShowEmptyIdleTime = category.id === "idle_time" && idleLabelFilter;
47112
- if ((displayCount > 0 || shouldShowEmptyIdleTime) && shouldShowCategory(category.id)) {
48195
+ if ((typeof displayCount === "number" && displayCount > 0 || shouldShowEmptyIdleTime || isCategoryMetadataLoading || shouldShowScopedEmptyCategory || shouldShowScopedLoadingCategory) && shouldShowCategory(category.id)) {
47113
48196
  const colorClasses = getColorClasses(category.color);
47114
48197
  const clipNodes = filteredClips.map((clip, index) => {
47115
48198
  const cycleTime = extractCycleTimeSeconds(clip);
@@ -47119,7 +48202,7 @@ var FileManagerFilters = ({
47119
48202
  const baseTimeLabel = formatClipExplorerTimeLabel({
47120
48203
  categoryId: category.id,
47121
48204
  clipTimestamp: clip.clip_timestamp,
47122
- timezone,
48205
+ timezone: activeTimeFilterTimezone,
47123
48206
  durationSeconds: idleDuration ?? clip.duration,
47124
48207
  idleStartTime: clip.idle_start_time,
47125
48208
  idleEndTime: clip.idle_end_time,
@@ -47157,6 +48240,7 @@ var FileManagerFilters = ({
47157
48240
  type: "category",
47158
48241
  count: displayCount,
47159
48242
  // Use filtered count when time filter is active
48243
+ countLoading: isScopedTotalPending || isCategoryMetadataLoading && scopedTotal === null,
47160
48244
  children: clipNodes,
47161
48245
  // Use clip nodes from metadata
47162
48246
  icon: expandedNodes.has(category.id) ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: `h-4 w-4 ${colorClasses.text}` }) : getCategoryIcon(category.icon, colorClasses),
@@ -47164,10 +48248,18 @@ var FileManagerFilters = ({
47164
48248
  });
47165
48249
  }
47166
48250
  });
47167
- const filteredFastCycles = (percentileClips["fast-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""));
47168
- const filteredSlowCycles = (percentileClips["slow-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""));
48251
+ const filteredFastCycles = sortPercentileCycleClipsForDisplay(
48252
+ "fast-cycles",
48253
+ (percentileClips["fast-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""))
48254
+ );
48255
+ const filteredSlowCycles = sortPercentileCycleClipsForDisplay(
48256
+ "slow-cycles",
48257
+ (percentileClips["slow-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""))
48258
+ );
47169
48259
  const fastCount = typeof percentileCounts["fast-cycles"] === "number" ? percentileCounts["fast-cycles"] : null;
47170
48260
  const slowCount = typeof percentileCounts["slow-cycles"] === "number" ? percentileCounts["slow-cycles"] : null;
48261
+ const isFastCountPending = showPercentileCycleFilters && fastCount === null;
48262
+ const isSlowCountPending = showPercentileCycleFilters && slowCount === null;
47171
48263
  const percentileCategories = showPercentileCycleFilters ? [
47172
48264
  {
47173
48265
  id: "fast-cycles",
@@ -47176,6 +48268,7 @@ var FileManagerFilters = ({
47176
48268
  description: "Top 10% fastest performance",
47177
48269
  type: "percentile-category",
47178
48270
  count: isTimeFilterActive ? fastCount === null && filteredFastCycles.length === 0 ? null : filteredFastCycles.length : fastCount,
48271
+ countLoading: isFastCountPending,
47179
48272
  icon: getPercentileIcon("fast-cycles", expandedNodes.has("fast-cycles"), { text: "text-green-600" }),
47180
48273
  color: "green",
47181
48274
  percentileType: "fast-cycles",
@@ -47187,7 +48280,7 @@ var FileManagerFilters = ({
47187
48280
  label: `${formatClipExplorerTimeLabel({
47188
48281
  categoryId: "fast-cycles",
47189
48282
  clipTimestamp: clip.creation_timestamp || clip.timestamp || "",
47190
- timezone,
48283
+ timezone: activeTimeFilterTimezone,
47191
48284
  durationSeconds: cycleTime
47192
48285
  })}${clip.cycle_time_seconds ? ` - (${clip.cycle_time_seconds.toFixed(1)}s)` : ""}`,
47193
48286
  type: "video",
@@ -47207,6 +48300,7 @@ var FileManagerFilters = ({
47207
48300
  description: "Bottom 10% slowest performance",
47208
48301
  type: "percentile-category",
47209
48302
  count: isTimeFilterActive ? slowCount === null && filteredSlowCycles.length === 0 ? null : filteredSlowCycles.length : slowCount,
48303
+ countLoading: isSlowCountPending,
47210
48304
  icon: getPercentileIcon("slow-cycles", expandedNodes.has("slow-cycles"), { text: "text-red-600" }),
47211
48305
  color: "red",
47212
48306
  percentileType: "slow-cycles",
@@ -47218,7 +48312,7 @@ var FileManagerFilters = ({
47218
48312
  label: `${formatClipExplorerTimeLabel({
47219
48313
  categoryId: "slow-cycles",
47220
48314
  clipTimestamp: clip.creation_timestamp || clip.timestamp || "",
47221
- timezone,
48315
+ timezone: activeTimeFilterTimezone,
47222
48316
  durationSeconds: cycleTime
47223
48317
  })}${clip.cycle_time_seconds ? ` - (${clip.cycle_time_seconds.toFixed(1)}s)` : ""}`,
47224
48318
  type: "video",
@@ -47249,7 +48343,7 @@ var FileManagerFilters = ({
47249
48343
  hour12: true,
47250
48344
  hour: 'numeric',
47251
48345
  minute: '2-digit',
47252
- timeZone: timezone
48346
+ timeZone: activeTimeFilterTimezone
47253
48347
  });
47254
48348
 
47255
48349
  return {
@@ -47268,7 +48362,11 @@ var FileManagerFilters = ({
47268
48362
  const orderedIds = [RECENT_FLOW_RED_STREAK_CLIP_TYPE2, "cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
47269
48363
  orderedIds.forEach((orderedId) => {
47270
48364
  const percentileCategory = percentileCategories.find((cat) => cat.id === orderedId);
47271
- const shouldIncludePercentile = percentileCategory ? typeof percentileCategory.count === "number" && percentileCategory.count > 0 : false;
48365
+ const shouldIncludePercentile = percentileCategory ? typeof percentileCategory.count === "number" || Boolean(
48366
+ isTimeFilterActive && isRequiredHourlyFilterCategory(orderedId) && typeof percentileCategory.count === "number" && percentileCategory.count === 0
48367
+ ) || Boolean(
48368
+ percentileCategory.countLoading && (isRequiredHourlyFilterCategory(orderedId) || !activeInitialTimeFilter || activeFilter === orderedId)
48369
+ ) : false;
47272
48370
  if (percentileCategory && shouldIncludePercentile && shouldShowCategory(orderedId)) {
47273
48371
  tree.push(percentileCategory);
47274
48372
  }
@@ -47283,23 +48381,263 @@ var FileManagerFilters = ({
47283
48381
  }
47284
48382
  });
47285
48383
  percentileCategories.forEach((category) => {
47286
- const shouldIncludePercentile = typeof category.count === "number" && category.count > 0;
48384
+ const shouldIncludePercentile = typeof category.count === "number" || Boolean(
48385
+ isTimeFilterActive && isRequiredHourlyFilterCategory(category.id) && typeof category.count === "number" && category.count === 0
48386
+ ) || Boolean(
48387
+ category.countLoading && (isRequiredHourlyFilterCategory(category.id) || !activeInitialTimeFilter || activeFilter === category.id)
48388
+ );
47287
48389
  if (!orderedIds.includes(category.id) && shouldIncludePercentile && shouldShowCategory(category.id)) {
47288
48390
  tree.push(category);
47289
48391
  }
47290
48392
  });
47291
48393
  return tree;
47292
- }, [categories, expandedNodes, counts, clipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive, showPercentileCycleFilters]);
48394
+ }, [categoriesForTree, expandedNodes, counts, getAvailableClipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive, showPercentileCycleFilters, loadingCategories, activeTimeFilterTimezone, isInitialTimeFilterCategory, isRequiredHourlyFilterCategory, hasCompleteMetadataForInitialTimeFilter, scopedCategoryTotals, isCategoryScopedByTimeFilter, categoryHasMore, activeFilter]);
48395
+ const chartHandoffVideoFallbackTree = React125.useMemo(() => {
48396
+ if (!startTime || !endTime || !activeInitialTimeFilter || !activeFilter) {
48397
+ return [];
48398
+ }
48399
+ const fallbackCategory = categoriesForTree.find((category) => category.id === activeFilter) || categoriesForTree.find((category) => videos.some((video) => video.type === category.id));
48400
+ if (!fallbackCategory) {
48401
+ return [];
48402
+ }
48403
+ const fallbackVideos = videos.filter((video) => video.type === fallbackCategory.id);
48404
+ const sourceVideos = fallbackVideos.map(buildClipMetadataFromVideo).filter((clip) => isClipInTimeRange(clip.clip_timestamp));
48405
+ if (sourceVideos.length === 0) {
48406
+ return [];
48407
+ }
48408
+ const colorClasses = getColorClasses(fallbackCategory.color);
48409
+ const clipNodes = sourceVideos.map((clip, index) => {
48410
+ const cycleTime = extractCycleTimeSeconds(clip);
48411
+ const baseTimeLabel = formatClipExplorerTimeLabel({
48412
+ categoryId: fallbackCategory.id,
48413
+ clipTimestamp: clip.clip_timestamp,
48414
+ timezone: activeTimeFilterTimezone,
48415
+ durationSeconds: clip.duration,
48416
+ idleStartTime: clip.idle_start_time,
48417
+ idleEndTime: clip.idle_end_time,
48418
+ clipStartTime: clip.clip_start_time,
48419
+ clipEndTime: clip.clip_end_time
48420
+ });
48421
+ 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)` : ""}`;
48422
+ return {
48423
+ id: clip.id,
48424
+ label: displayLabel,
48425
+ type: "video",
48426
+ icon: getSeverityIcon(clip.severity, fallbackCategory.id, cycleTime, resolvedTargetCycleTime, clip.clipId),
48427
+ timestamp: clip.clip_timestamp,
48428
+ severity: clip.severity,
48429
+ clipId: clip.clipId,
48430
+ categoryId: fallbackCategory.id,
48431
+ clipPosition: index + 1,
48432
+ cycleTimeSeconds: cycleTime,
48433
+ duration: clip.duration,
48434
+ cycleItemCount: null,
48435
+ redFlowSeverityScore: clip.red_flow_severity_score ?? null,
48436
+ redFlowExplanationSummary: clip.red_flow_explanation_summary ?? clip.red_flow_explanation?.summary ?? null
48437
+ };
48438
+ });
48439
+ return [{
48440
+ id: activeFilter,
48441
+ label: fallbackCategory.id === RECENT_FLOW_RED_STREAK_CLIP_TYPE2 ? RECENT_FLOW_RED_STREAK_DISPLAY_LABEL : fallbackCategory.label,
48442
+ subtitle: fallbackCategory.subtitle || fallbackCategory.description,
48443
+ description: fallbackCategory.description,
48444
+ type: "category",
48445
+ count: clipNodes.length,
48446
+ children: clipNodes,
48447
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: `h-4 w-4 ${colorClasses.text}` }),
48448
+ color: fallbackCategory.color
48449
+ }];
48450
+ }, [
48451
+ activeFilter,
48452
+ activeInitialTimeFilter,
48453
+ activeTimeFilterTimezone,
48454
+ categoriesForTree,
48455
+ counts,
48456
+ currentVideoId,
48457
+ endTime,
48458
+ getDisplayValue,
48459
+ isClipInTimeRange,
48460
+ resolvedTargetCycleTime,
48461
+ startTime,
48462
+ videos
48463
+ ]);
48464
+ const displayedFilterTree = React125.useMemo(() => {
48465
+ if (chartHandoffVideoFallbackTree.length === 0) {
48466
+ return filterTree;
48467
+ }
48468
+ if (filterTree.length === 0) {
48469
+ return chartHandoffVideoFallbackTree;
48470
+ }
48471
+ const fallbackNode = chartHandoffVideoFallbackTree[0];
48472
+ const activeNode = filterTree.find((node) => node.id === activeFilter);
48473
+ const activeNodeHasChildren = (activeNode?.children?.length || 0) > 0;
48474
+ const shouldReplaceActiveNode = Boolean(
48475
+ activeFilter && fallbackNode && activeNode && !activeNodeHasChildren && isInitialTimeFilterCategory(activeFilter)
48476
+ );
48477
+ if (!shouldReplaceActiveNode) {
48478
+ return filterTree;
48479
+ }
48480
+ return filterTree.map((node) => node.id === activeFilter ? fallbackNode : node);
48481
+ }, [activeFilter, chartHandoffVideoFallbackTree, filterTree, isInitialTimeFilterCategory]);
48482
+ const getSelectionContextForNodes = React125.useCallback((categoryId, clipNodes) => {
48483
+ const normalizeSeverity = (severity) => severity === "low" || severity === "medium" || severity === "high" ? severity : "medium";
48484
+ const scopedNodes = clipNodes.filter((node) => node.type === "video" && Boolean(node.clipId || node.id));
48485
+ const scopedNodeClips = scopedNodes.map((node, index) => ({
48486
+ id: node.clipId || node.id,
48487
+ clipId: node.clipId || node.id,
48488
+ clip_timestamp: node.timestamp || "",
48489
+ creation_timestamp: node.timestamp || "",
48490
+ description: node.label,
48491
+ severity: normalizeSeverity(node.severity),
48492
+ category: categoryId,
48493
+ duration: node.duration,
48494
+ cycle_time_seconds: node.cycleTimeSeconds,
48495
+ index
48496
+ }));
48497
+ const categoryClips = getAvailableClipMetadata(categoryId);
48498
+ const contextClips = isCategoryScopedByTimeFilter(categoryId) && scopedNodeClips.length > 0 ? scopedNodeClips : categoryClips.length > 0 ? categoryClips : scopedNodeClips;
48499
+ const scopedTotal = scopedCategoryTotals[categoryId];
48500
+ const total = typeof scopedTotal === "number" ? scopedTotal : counts?.[categoryId];
48501
+ return contextClips.length ? { clips: contextClips, total } : void 0;
48502
+ }, [counts, getAvailableClipMetadata, isCategoryScopedByTimeFilter, scopedCategoryTotals]);
48503
+ React125.useEffect(() => {
48504
+ if (!activeInitialTimeFilter || !startTime || !endTime || currentVideoId || !onClipSelect) {
48505
+ return;
48506
+ }
48507
+ const categoryPriority = Array.from(new Set([
48508
+ activeInitialTimeFilter.categoryId,
48509
+ activeFilter,
48510
+ ...activeInitialTimeFilter.categoryIds || []
48511
+ ].filter((value) => typeof value === "string" && value.length > 0)));
48512
+ const preferredCategoryId = categoryPriority[0];
48513
+ const orderedCategoryNodes = displayedFilterTree.filter((categoryNode) => isInitialTimeFilterCategory(categoryNode.id)).sort((left, right) => {
48514
+ const leftIndex = categoryPriority.indexOf(left.id);
48515
+ const rightIndex = categoryPriority.indexOf(right.id);
48516
+ const leftPriority = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
48517
+ const rightPriority = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
48518
+ return leftPriority - rightPriority;
48519
+ });
48520
+ const preferredCategoryNode = preferredCategoryId ? orderedCategoryNodes.find((categoryNode) => categoryNode.id === preferredCategoryId) : void 0;
48521
+ if (preferredCategoryId && !preferredCategoryNode) {
48522
+ const preferredKnownTotal = preferredCategoryId === "fast-cycles" || preferredCategoryId === "slow-cycles" ? percentileCounts[preferredCategoryId] : scopedCategoryTotals[preferredCategoryId];
48523
+ if (preferredKnownTotal !== 0) {
48524
+ return;
48525
+ }
48526
+ }
48527
+ const preferredFirstClipNode = preferredCategoryNode?.children?.find((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48528
+ if (preferredCategoryId && preferredCategoryNode && !preferredFirstClipNode) {
48529
+ const preferredKnownTotal = preferredCategoryId === "fast-cycles" || preferredCategoryId === "slow-cycles" ? percentileCounts[preferredCategoryId] : scopedCategoryTotals[preferredCategoryId];
48530
+ if (preferredKnownTotal !== 0) {
48531
+ return;
48532
+ }
48533
+ }
48534
+ for (const categoryNode of orderedCategoryNodes) {
48535
+ const firstClipNode = categoryNode.children?.find((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48536
+ if (!firstClipNode?.clipId || !firstClipNode.categoryId) {
48537
+ continue;
48538
+ }
48539
+ const selectionKey = `${activeTimeFilterKey}:${firstClipNode.categoryId}:${firstClipNode.clipId}:${currentVideoId || "none"}`;
48540
+ if (autoSelectedScopedClipRef.current === selectionKey) {
48541
+ return;
48542
+ }
48543
+ autoSelectedScopedClipRef.current = selectionKey;
48544
+ const selectionContext = getSelectionContextForNodes(firstClipNode.categoryId, categoryNode.children || []);
48545
+ onClipSelect(
48546
+ firstClipNode.categoryId,
48547
+ firstClipNode.clipId,
48548
+ firstClipNode.clipPosition,
48549
+ selectionContext
48550
+ );
48551
+ return;
48552
+ }
48553
+ }, [
48554
+ activeInitialTimeFilter,
48555
+ activeFilter,
48556
+ activeTimeFilterKey,
48557
+ currentVideoId,
48558
+ displayedFilterTree,
48559
+ endTime,
48560
+ getSelectionContextForNodes,
48561
+ isInitialTimeFilterCategory,
48562
+ onClipSelect,
48563
+ percentileCounts,
48564
+ scopedCategoryTotals,
48565
+ startTime
48566
+ ]);
48567
+ const isChartHandoffLoading = React125.useMemo(() => {
48568
+ if (!startTime || !endTime || !activeInitialTimeFilter || !activeFilter || !isInitialTimeFilterCategory(activeFilter)) {
48569
+ return false;
48570
+ }
48571
+ const pageOneLoadingKey = getMetadataLoadingKey(activeFilter, 1);
48572
+ return Boolean(
48573
+ activeCategoryLoading || Array.from(loadingCategories).some((key) => key === pageOneLoadingKey || key.startsWith(`${pageOneLoadingKey}-all`))
48574
+ );
48575
+ }, [
48576
+ activeCategoryLoading,
48577
+ activeFilter,
48578
+ activeInitialTimeFilter,
48579
+ endTime,
48580
+ getMetadataLoadingKey,
48581
+ isInitialTimeFilterCategory,
48582
+ loadingCategories,
48583
+ startTime
48584
+ ]);
48585
+ React125.useEffect(() => {
48586
+ if (!isTimeFilterActive || !startTime || !endTime || !activeFilter || activeFilter === "all" || !onClipSelect) {
48587
+ return;
48588
+ }
48589
+ const activeCategoryNode = displayedFilterTree.find((categoryNode) => categoryNode.id === activeFilter);
48590
+ const activeClipNodes = (activeCategoryNode?.children || []).filter((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48591
+ if (activeClipNodes.length === 0) {
48592
+ return;
48593
+ }
48594
+ const currentClipIsVisible = Boolean(
48595
+ currentVideoId && activeClipNodes.some((child) => (child.clipId || child.id) === currentVideoId)
48596
+ );
48597
+ if (currentClipIsVisible) {
48598
+ return;
48599
+ }
48600
+ const firstClipNode = activeClipNodes[0];
48601
+ if (!firstClipNode?.clipId || !firstClipNode.categoryId) {
48602
+ return;
48603
+ }
48604
+ const selectionKey = `${activeTimeFilterKey}:${firstClipNode.categoryId}:${firstClipNode.clipId}:${currentVideoId || "none"}`;
48605
+ if (autoSelectedScopedClipRef.current === selectionKey) {
48606
+ return;
48607
+ }
48608
+ autoSelectedScopedClipRef.current = selectionKey;
48609
+ const selectionContext = getSelectionContextForNodes(firstClipNode.categoryId, activeClipNodes);
48610
+ onClipSelect(
48611
+ firstClipNode.categoryId,
48612
+ firstClipNode.clipId,
48613
+ firstClipNode.clipPosition,
48614
+ selectionContext
48615
+ );
48616
+ }, [
48617
+ activeFilter,
48618
+ activeTimeFilterKey,
48619
+ currentVideoId,
48620
+ displayedFilterTree,
48621
+ endTime,
48622
+ getSelectionContextForNodes,
48623
+ isTimeFilterActive,
48624
+ onClipSelect,
48625
+ startTime
48626
+ ]);
47293
48627
  const toggleExpanded = (nodeId) => {
47294
48628
  const newExpanded = new Set(expandedNodes);
47295
48629
  if (newExpanded.has(nodeId)) {
47296
48630
  newExpanded.delete(nodeId);
47297
48631
  } else {
47298
48632
  newExpanded.add(nodeId);
47299
- const category = categories.find((cat) => cat.id === nodeId);
47300
- if (category && !clipMetadata[nodeId] && !isCategoryExternallyManaged(nodeId)) {
48633
+ const category = categoriesForTree.find((cat) => cat.id === nodeId);
48634
+ if (category) {
47301
48635
  console.log(`[FileManager] Fetching clips for expanded category: ${nodeId}`);
47302
- fetchClipMetadata(nodeId, 1);
48636
+ if (isInitialTimeFilterCategory(nodeId) && !hasCompleteMetadataForInitialTimeFilter(nodeId)) {
48637
+ fetchClipMetadata(nodeId, 1);
48638
+ } else if (!hasKnownClipMetadata(nodeId) && !isCategoryExternallyManaged(nodeId)) {
48639
+ fetchClipMetadata(nodeId, 1);
48640
+ }
47303
48641
  }
47304
48642
  if (!isCategoryExternallyManaged(nodeId) && showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
47305
48643
  fetchPercentileClips("fast-cycles");
@@ -47317,10 +48655,14 @@ var FileManagerFilters = ({
47317
48655
  newExpanded.delete(node.id);
47318
48656
  } else {
47319
48657
  newExpanded.add(node.id);
47320
- const category = categories.find((cat) => cat.id === node.id);
47321
- if (category && !clipMetadata[node.id] && !isCategoryExternallyManaged(node.id)) {
48658
+ const category = categoriesForTree.find((cat) => cat.id === node.id);
48659
+ if (category) {
47322
48660
  console.log(`[FileManager] Fetching clips for expanded category: ${node.id}`);
47323
- fetchClipMetadata(node.id, 1);
48661
+ if (isInitialTimeFilterCategory(node.id) && !hasCompleteMetadataForInitialTimeFilter(node.id)) {
48662
+ fetchClipMetadata(node.id, 1);
48663
+ } else if (!hasKnownClipMetadata(node.id) && !isCategoryExternallyManaged(node.id)) {
48664
+ fetchClipMetadata(node.id, 1);
48665
+ }
47324
48666
  }
47325
48667
  if (!isCategoryExternallyManaged(node.id) && showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
47326
48668
  fetchPercentileClips("fast-cycles");
@@ -47330,7 +48672,21 @@ var FileManagerFilters = ({
47330
48672
  }
47331
48673
  }
47332
48674
  setExpandedNodes(newExpanded);
47333
- onFilterChange(node.id);
48675
+ if (node.id !== activeFilter) {
48676
+ onFilterChange(node.id);
48677
+ }
48678
+ if (isCategoryScopedByTimeFilter(node.id) && onClipSelect && node.children?.length) {
48679
+ const firstClipNode = node.children.find((child) => child.type === "video" && Boolean(child.clipId) && Boolean(child.categoryId));
48680
+ if (firstClipNode?.clipId && firstClipNode.categoryId) {
48681
+ const selectionContext = getSelectionContextForNodes(firstClipNode.categoryId, node.children);
48682
+ onClipSelect(
48683
+ firstClipNode.categoryId,
48684
+ firstClipNode.clipId,
48685
+ firstClipNode.clipPosition,
48686
+ selectionContext
48687
+ );
48688
+ }
48689
+ }
47334
48690
  if (node.id === "fast-cycles") {
47335
48691
  trackCoreEvent("Fast Clips Clicked", {
47336
48692
  workspaceId,
@@ -47368,8 +48724,11 @@ var FileManagerFilters = ({
47368
48724
  } else if (node.type === "video") {
47369
48725
  if (onClipSelect && node.categoryId !== void 0 && node.clipId !== void 0) {
47370
48726
  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;
48727
+ const categoryNode = displayedFilterTree.find((candidate) => candidate.id === node.categoryId);
48728
+ const selectionContext = getSelectionContextForNodes(
48729
+ node.categoryId,
48730
+ categoryNode?.children || [node]
48731
+ );
47373
48732
  onClipSelect(node.categoryId, node.clipId, node.clipPosition, selectionContext);
47374
48733
  } else {
47375
48734
  const videoIndex = videos.findIndex((v) => v.id === node.id);
@@ -47382,24 +48741,40 @@ var FileManagerFilters = ({
47382
48741
  const renderNode = (node, depth = 0) => {
47383
48742
  const isExpanded = expandedNodes.has(node.id);
47384
48743
  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;
48744
+ const nodeClipId = node.clipId || node.id;
48745
+ const isCurrentVideo = Boolean(
48746
+ node.type === "video" && currentVideoId && currentVideoId === nodeClipId
48747
+ );
48748
+ const isCountUnknown = Boolean(
48749
+ (node.type === "category" || node.type === "percentile-category") && (node.count === null || node.countLoading)
48750
+ );
48751
+ const hasRenderedChildren = (node.children?.length || 0) > 0;
47389
48752
  const loadedPage = categoryPages[node.id] || 0;
47390
48753
  const pageOneLoadingKey = getMetadataLoadingKey(node.id, 1);
47391
48754
  const nextMetadataPage = loadedPage + 1;
47392
48755
  const nextLoadMorePage = (categoryPages[node.id] || 1) + 1;
47393
48756
  const isPageOneLoading = loadingCategories.has(pageOneLoadingKey);
48757
+ const isFullCategoryLoading = Array.from(loadingCategories).some((key) => key.startsWith(`${pageOneLoadingKey}-all`));
47394
48758
  const isLoadMoreLoading = loadedPage > 0 && loadingCategories.has(getMetadataLoadingKey(node.id, nextMetadataPage));
48759
+ const isNodeLoading = Boolean(
48760
+ isPageOneLoading || isFullCategoryLoading || activeCategoryLoading && node.id === activeFilter
48761
+ );
48762
+ const totalForLoadMore = isCategoryScopedByTimeFilter(node.id) && typeof scopedCategoryTotals[node.id] === "number" ? scopedCategoryTotals[node.id] : counts?.[node.id] || 0;
47395
48763
  const showInitialLoadingState = Boolean(
47396
- isExpanded && !hasLoadedChildren && (node.type === "category" || node.type === "percentile-category") && (isPageOneLoading || activeCategoryLoading && node.id === activeFilter)
48764
+ isExpanded && !hasRenderedChildren && (node.type === "category" || node.type === "percentile-category") && isNodeLoading
48765
+ );
48766
+ const showScopedEmptyState = Boolean(
48767
+ isExpanded && !hasRenderedChildren && !isNodeLoading && !categoryHasMore[node.id] && isCategoryScopedByTimeFilter(node.id) && (node.type === "category" || node.type === "percentile-category")
47397
48768
  );
48769
+ const hasChildren = isCountUnknown || (node.count || 0) > 0 || isNodeLoading || showScopedEmptyState;
47398
48770
  const colorClasses = node.color ? getColorClasses(node.color) : null;
47399
48771
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "select-none animate-in", children: [
47400
48772
  /* @__PURE__ */ jsxRuntime.jsxs(
47401
48773
  "div",
47402
48774
  {
48775
+ "aria-current": isCurrentVideo ? "true" : void 0,
48776
+ "data-qa-clips-row-clip-id": node.type === "video" ? nodeClipId : void 0,
48777
+ "data-qa-clips-row-category-id": node.type === "video" ? node.categoryId : void 0,
47403
48778
  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
48779
  onClick: () => handleNodeClick(node),
47405
48780
  children: [
@@ -47435,7 +48810,7 @@ var FileManagerFilters = ({
47435
48810
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex items-center justify-between", children: [
47436
48811
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
47437
48812
  /* @__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 }),
48813
+ 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
48814
  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
48815
  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
48816
  // Show root cause label for idle time clips (text only, icon is on the left)
@@ -47471,7 +48846,7 @@ var FileManagerFilters = ({
47471
48846
  })()
47472
48847
  ) })
47473
48848
  ] }),
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 }) })
48849
+ 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
48850
  ] })
47476
48851
  ]
47477
48852
  }
@@ -47482,6 +48857,7 @@ var FileManagerFilters = ({
47482
48857
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
47483
48858
  "Loading clips..."
47484
48859
  ] }) }),
48860
+ showScopedEmptyState && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 px-3 text-center text-sm text-slate-500", children: "No clips found" }),
47485
48861
  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
48862
  /* @__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
48863
  "Loading more clips..."
@@ -47496,7 +48872,7 @@ var FileManagerFilters = ({
47496
48872
  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
48873
  children: [
47498
48874
  "Load more clips (",
47499
- (counts?.[node.id] || 0) - (clipMetadata[node.id]?.length || 0),
48875
+ Math.max(0, totalForLoadMore - getAvailableClipMetadata(node.id).length),
47500
48876
  " remaining)"
47501
48877
  ]
47502
48878
  }
@@ -47559,6 +48935,8 @@ var FileManagerFilters = ({
47559
48935
  e.stopPropagation();
47560
48936
  setStartTime("");
47561
48937
  setEndTime("");
48938
+ setActiveInitialTimeFilter(null);
48939
+ setScopedCategoryTotals({});
47562
48940
  setIsTimeFilterActive(false);
47563
48941
  },
47564
48942
  className: "rounded-full p-0.5 transition-colors hover:bg-blue-100",
@@ -47692,6 +49070,8 @@ var FileManagerFilters = ({
47692
49070
  onClick: () => {
47693
49071
  setStartTime("");
47694
49072
  setEndTime("");
49073
+ setActiveInitialTimeFilter(null);
49074
+ setScopedCategoryTotals({});
47695
49075
  setStartSearchTerm("");
47696
49076
  setEndSearchTerm("");
47697
49077
  setIsTimeFilterActive(false);
@@ -47780,8 +49160,13 @@ var FileManagerFilters = ({
47780
49160
  )
47781
49161
  ] }),
47782
49162
  /* @__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: [
49163
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: displayedFilterTree.map((node) => renderNode(node)) }),
49164
+ displayedFilterTree.length === 0 && isChartHandoffLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
49165
+ /* @__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" }) }),
49166
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "Loading clips..." }),
49167
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500", children: "Finding clips from the selected hour" })
49168
+ ] }),
49169
+ displayedFilterTree.length === 0 && !isChartHandoffLoading && (startTime || endTime) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
47785
49170
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-12 w-12 mx-auto" }) }),
47786
49171
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clips found" }),
47787
49172
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500 mb-4", children: "No clips match the selected time range" }),
@@ -47791,6 +49176,8 @@ var FileManagerFilters = ({
47791
49176
  onClick: () => {
47792
49177
  setStartTime("");
47793
49178
  setEndTime("");
49179
+ setActiveInitialTimeFilter(null);
49180
+ setScopedCategoryTotals({});
47794
49181
  setIsTimeFilterActive(false);
47795
49182
  },
47796
49183
  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 +49185,12 @@ var FileManagerFilters = ({
47798
49185
  }
47799
49186
  )
47800
49187
  ] }),
47801
- filterTree.length === 0 && !startTime && !endTime && categories.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
49188
+ displayedFilterTree.length === 0 && !startTime && !endTime && categories.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
47802
49189
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.HelpCircle, { className: "h-12 w-12 mx-auto" }) }),
47803
49190
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clip types available" }),
47804
49191
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500", children: "Loading clip categories..." })
47805
49192
  ] }),
47806
- filterTree.length === 0 && !startTime && !endTime && categories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
49193
+ displayedFilterTree.length === 0 && !startTime && !endTime && categories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
47807
49194
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-12 w-12 mx-auto" }) }),
47808
49195
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clips available" }),
47809
49196
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-slate-500", children: "No clips found for the selected time period" })
@@ -48210,6 +49597,65 @@ function useClipsRealtimeUpdates({
48210
49597
  }
48211
49598
  var LOW_EFFICIENCY_CATEGORY_ID = "recent_flow_red_streak";
48212
49599
  var LOW_EFFICIENCY_AI_SUMMARY_ENABLED = process.env.NEXT_PUBLIC_LOW_EFFICIENCY_AI_SUMMARY_ENABLED === "true";
49600
+ var timeValueToMinutes2 = (value) => {
49601
+ const [hourValue, minuteValue] = value.substring(0, 5).split(":").map(Number);
49602
+ if (!Number.isInteger(hourValue) || !Number.isInteger(minuteValue) || hourValue < 0 || hourValue > 23 || minuteValue < 0 || minuteValue > 59) {
49603
+ return null;
49604
+ }
49605
+ return hourValue * 60 + minuteValue;
49606
+ };
49607
+ var isMinuteInTimeWindow2 = (minute, startValue, endValue) => {
49608
+ const startMinute = timeValueToMinutes2(startValue);
49609
+ const endMinute = timeValueToMinutes2(endValue);
49610
+ if (startMinute === null || endMinute === null) {
49611
+ return true;
49612
+ }
49613
+ return endMinute > startMinute ? minute >= startMinute && minute < endMinute : minute >= startMinute || minute < endMinute;
49614
+ };
49615
+ var CHART_HANDOFF_CATEGORY_FALLBACKS = {
49616
+ cycle_completion: {
49617
+ label: "Cycle Completion",
49618
+ description: "Successfully completed production cycles",
49619
+ color: "blue",
49620
+ icon: "check-circle"
49621
+ },
49622
+ "fast-cycles": {
49623
+ label: "Fast Cycles",
49624
+ description: "Fastest cycle clips",
49625
+ color: "green",
49626
+ icon: "trending-up"
49627
+ },
49628
+ "slow-cycles": {
49629
+ label: "Slow Cycles",
49630
+ description: "Slowest cycle clips",
49631
+ color: "red",
49632
+ icon: "trending-down"
49633
+ },
49634
+ idle_time: {
49635
+ label: "Idle Time",
49636
+ description: "Low value activities and idle moments",
49637
+ color: "amber",
49638
+ icon: "clock"
49639
+ },
49640
+ recent_flow_red_streak: {
49641
+ label: "Low moments",
49642
+ description: "Moments of low efficiency",
49643
+ color: "rose",
49644
+ icon: "alert-triangle"
49645
+ },
49646
+ worst_cycle_time: {
49647
+ label: "Slow Cycles",
49648
+ description: "Slowest cycle clips",
49649
+ color: "red",
49650
+ icon: "trending-down"
49651
+ },
49652
+ best_cycle_time: {
49653
+ label: "Fast Cycles",
49654
+ description: "Fastest cycle clips",
49655
+ color: "green",
49656
+ icon: "trending-up"
49657
+ }
49658
+ };
48213
49659
  var parseFiniteNumber2 = (value) => {
48214
49660
  if (typeof value === "number" && Number.isFinite(value)) {
48215
49661
  return value;
@@ -48257,6 +49703,7 @@ var BottlenecksContent = ({
48257
49703
  ticketId,
48258
49704
  prefetchedPercentileCounts,
48259
49705
  lowMomentsPrefetch,
49706
+ initialTimePrefetch,
48260
49707
  initialTimeFilter
48261
49708
  }) => {
48262
49709
  console.log("\u{1F3AB} [BottlenecksContent] Rendered with ticketId:", ticketId || "NONE", "workspaceId:", workspaceId, "date:", date, "shift:", shift);
@@ -48469,6 +49916,108 @@ var BottlenecksContent = ({
48469
49916
  enabled: isEffectiveShiftReady
48470
49917
  });
48471
49918
  const isLowMomentsCategoryAvailable = React125.useMemo(() => Array.isArray(clipTypes) && clipTypes.some((type) => type?.type === LOW_EFFICIENCY_CATEGORY_ID || type?.id === LOW_EFFICIENCY_CATEGORY_ID), [clipTypes]);
49919
+ const requestedInitialCategoryCandidates = React125.useMemo(() => {
49920
+ const requestedCategories = [
49921
+ ...Array.isArray(initialTimeFilter?.categoryIds) ? initialTimeFilter.categoryIds : [],
49922
+ initialTimeFilter?.categoryId
49923
+ ];
49924
+ return Array.from(
49925
+ new Set(
49926
+ requestedCategories.filter((category) => Boolean(category)).map((category) => category.trim()).filter(Boolean)
49927
+ )
49928
+ );
49929
+ }, [initialTimeFilter?.categoryId, initialTimeFilter?.categoryIds]);
49930
+ const hasInitialTimeHandoff = Boolean(initialTimeFilter?.startTime && initialTimeFilter?.endTime);
49931
+ const initialTimeHandoffCategorySet = React125.useMemo(() => new Set(requestedInitialCategoryCandidates), [requestedInitialCategoryCandidates]);
49932
+ const isInitialTimeHandoffCategory = React125.useCallback((categoryId) => Boolean(
49933
+ hasInitialTimeHandoff && categoryId && (initialTimeHandoffCategorySet.size === 0 || initialTimeHandoffCategorySet.has(categoryId))
49934
+ ), [hasInitialTimeHandoff, initialTimeHandoffCategorySet]);
49935
+ const isTimestampInInitialTimeHandoff = React125.useCallback((timestamp) => {
49936
+ if (!hasInitialTimeHandoff || !initialTimeFilter?.startTime || !initialTimeFilter?.endTime || !timestamp) {
49937
+ return true;
49938
+ }
49939
+ try {
49940
+ const clipDate = new Date(timestamp);
49941
+ if (Number.isNaN(clipDate.getTime())) {
49942
+ return false;
49943
+ }
49944
+ const clipParts = new Intl.DateTimeFormat("en-US", {
49945
+ hour12: false,
49946
+ hour: "2-digit",
49947
+ minute: "2-digit",
49948
+ timeZone: initialTimeFilter.timezone || timezone
49949
+ }).formatToParts(clipDate);
49950
+ const hourValue = clipParts.find((part) => part.type === "hour")?.value;
49951
+ const minuteValue = clipParts.find((part) => part.type === "minute")?.value;
49952
+ const clipMinute = timeValueToMinutes2(`${hourValue}:${minuteValue}`);
49953
+ return clipMinute === null ? false : isMinuteInTimeWindow2(clipMinute, initialTimeFilter.startTime, initialTimeFilter.endTime);
49954
+ } catch {
49955
+ return false;
49956
+ }
49957
+ }, [
49958
+ hasInitialTimeHandoff,
49959
+ initialTimeFilter?.endTime,
49960
+ initialTimeFilter?.startTime,
49961
+ initialTimeFilter?.timezone,
49962
+ timezone
49963
+ ]);
49964
+ const resolvedInitialCategory = React125.useMemo(() => resolveInitialClipCategory(requestedInitialCategoryCandidates, clipTypes, dynamicCounts), [clipTypes, dynamicCounts, requestedInitialCategoryCandidates]);
49965
+ const initialTimeFilterKey = React125.useMemo(() => hasInitialTimeHandoff ? [
49966
+ initialTimeFilter?.startTime,
49967
+ initialTimeFilter?.endTime,
49968
+ initialTimeFilter?.timezone || timezone,
49969
+ initialTimeFilter?.categoryId || "",
49970
+ ...initialTimeFilter?.categoryIds || []
49971
+ ].join("|") : "", [
49972
+ hasInitialTimeHandoff,
49973
+ initialTimeFilter?.categoryId,
49974
+ initialTimeFilter?.categoryIds,
49975
+ initialTimeFilter?.endTime,
49976
+ initialTimeFilter?.startTime,
49977
+ initialTimeFilter?.timezone,
49978
+ timezone
49979
+ ]);
49980
+ const preferredInitialTimeCategory = resolvedInitialCategory || requestedInitialCategoryCandidates[0] || "";
49981
+ React125.useEffect(() => {
49982
+ if (!initialTimeFilterKey) {
49983
+ return;
49984
+ }
49985
+ setPendingVideo(null);
49986
+ setAllVideos([]);
49987
+ setCurrentIndex(0);
49988
+ setCurrentClipId(null);
49989
+ currentClipIdRef.current = null;
49990
+ setCurrentMetadataIndex(0);
49991
+ currentMetadataIndexRef.current = 0;
49992
+ setCurrentPosition(0);
49993
+ currentPositionRef.current = 0;
49994
+ setCurrentTotal(0);
49995
+ currentTotalRef.current = 0;
49996
+ setCategoryMetadata([]);
49997
+ categoryMetadataRef.current = [];
49998
+ setCategoryMetadataCategoryId(null);
49999
+ setCategoryMetadataSort(null);
50000
+ clearRetryTimeout();
50001
+ navigationLockRef.current = false;
50002
+ loadingCategoryRef.current = null;
50003
+ if (loadingTimeoutRef.current) {
50004
+ clearTimeout(loadingTimeoutRef.current);
50005
+ loadingTimeoutRef.current = null;
50006
+ }
50007
+ setIsTransitioning(false);
50008
+ setIsNavigating(false);
50009
+ setIsCategoryLoading(false);
50010
+ if (preferredInitialTimeCategory) {
50011
+ setInitialFilter(preferredInitialTimeCategory);
50012
+ updateActiveFilter(preferredInitialTimeCategory);
50013
+ previousFilterRef.current = "";
50014
+ }
50015
+ }, [
50016
+ clearRetryTimeout,
50017
+ initialTimeFilterKey,
50018
+ preferredInitialTimeCategory,
50019
+ updateActiveFilter
50020
+ ]);
48472
50021
  console.log("[BottlenecksContent] Clip types data:", {
48473
50022
  clipTypes,
48474
50023
  clipTypesLength: clipTypes?.length,
@@ -48516,6 +50065,15 @@ var BottlenecksContent = ({
48516
50065
  }
48517
50066
  });
48518
50067
  React125.useEffect(() => {
50068
+ if (resolvedInitialCategory) {
50069
+ if (initialFilter !== resolvedInitialCategory) {
50070
+ setInitialFilter(resolvedInitialCategory);
50071
+ }
50072
+ if (activeFilterRef.current !== resolvedInitialCategory) {
50073
+ updateActiveFilter(resolvedInitialCategory);
50074
+ }
50075
+ return;
50076
+ }
48519
50077
  if (initialFilter) {
48520
50078
  return;
48521
50079
  }
@@ -48532,7 +50090,15 @@ var BottlenecksContent = ({
48532
50090
  activeFilterRef.current = defaultCategory;
48533
50091
  return;
48534
50092
  }
48535
- }, [clipTypes, dynamicCounts, defaultCategory, initialFilter, isLowMomentsCategoryAvailable]);
50093
+ }, [
50094
+ clipTypes,
50095
+ dynamicCounts,
50096
+ defaultCategory,
50097
+ initialFilter,
50098
+ isLowMomentsCategoryAvailable,
50099
+ resolvedInitialCategory,
50100
+ updateActiveFilter
50101
+ ]);
48536
50102
  const mergedCounts = React125.useMemo(() => {
48537
50103
  return { ...dynamicCounts };
48538
50104
  }, [dynamicCounts]);
@@ -48581,6 +50147,9 @@ var BottlenecksContent = ({
48581
50147
  const loadFirstVideoForCategory = React125.useCallback(async (category) => {
48582
50148
  if (!workspaceId || !s3ClipsService || !isMountedRef.current || !isEffectiveShiftReady) return;
48583
50149
  const targetCategory = category || activeFilterRef.current;
50150
+ if (targetCategory === "fast-cycles" || targetCategory === "slow-cycles") {
50151
+ return;
50152
+ }
48584
50153
  const operationKey = `loadFirstVideo:${targetCategory}:${effectiveDateString}:${effectiveShiftId}`;
48585
50154
  if (loadingCategoryRef.current === targetCategory || fetchInProgressRef.current.has(operationKey)) {
48586
50155
  console.log(`[BottlenecksContent] Load first video already in progress for ${targetCategory}`);
@@ -49026,8 +50595,9 @@ var BottlenecksContent = ({
49026
50595
  });
49027
50596
  }
49028
50597
  setVisibleCategoryMetadata(categoryId, cachedMetadata);
49029
- if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
49030
- const firstClipMeta = cachedMetadata[0];
50598
+ const cachedAutoloadCandidates = isInitialTimeHandoffCategory(categoryId) ? cachedMetadata.filter((clip) => isTimestampInInitialTimeHandoff(clip.clip_timestamp || clip.creation_timestamp || clip.timestamp)) : cachedMetadata;
50599
+ if (autoLoadFirstVideo && cachedAutoloadCandidates.length > 0 && s3ClipsService) {
50600
+ const firstClipMeta = cachedAutoloadCandidates[0];
49031
50601
  const firstClipId = firstClipMeta.clipId || firstClipMeta.id;
49032
50602
  const prefetchedFirstVideo = matchingLowMomentsPrefetch?.firstVideo ?? null;
49033
50603
  try {
@@ -49066,7 +50636,10 @@ var BottlenecksContent = ({
49066
50636
  endDate: `${resolvedDate}T23:59:59Z`,
49067
50637
  percentile: 10,
49068
50638
  shiftId: effectiveShiftId,
49069
- limit: 100
50639
+ limit: isInitialTimeHandoffCategory(categoryId) ? 500 : 100,
50640
+ startTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.startTime : void 0,
50641
+ endTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.endTime : void 0,
50642
+ timeFilterTimezone: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.timezone || timezone : void 0
49070
50643
  }),
49071
50644
  redirectReason: "session_expired"
49072
50645
  });
@@ -49083,10 +50656,13 @@ var BottlenecksContent = ({
49083
50656
  shift: effectiveShiftId,
49084
50657
  category: categoryId,
49085
50658
  page: 1,
49086
- limit: 100,
49087
- knownTotal: mergedCounts[categoryId] ?? null,
50659
+ limit: isInitialTimeHandoffCategory(categoryId) ? 500 : 100,
50660
+ knownTotal: isInitialTimeHandoffCategory(categoryId) ? null : mergedCounts[categoryId] ?? null,
49088
50661
  snapshotDateTime,
49089
50662
  snapshotClipId,
50663
+ startTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.startTime : void 0,
50664
+ endTime: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.endTime : void 0,
50665
+ timeFilterTimezone: isInitialTimeHandoffCategory(categoryId) ? initialTimeFilter?.timezone || timezone : void 0,
49090
50666
  sort: categoryId === LOW_EFFICIENCY_CATEGORY_ID ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"
49091
50667
  }),
49092
50668
  redirectReason: "session_expired"
@@ -49099,7 +50675,8 @@ var BottlenecksContent = ({
49099
50675
  if (categoryData.clips && isMountedRef.current) {
49100
50676
  let metadataClips;
49101
50677
  if (isPercentileCategory(categoryId)) {
49102
- metadataClips = categoryData.clips.map((clip, index) => ({
50678
+ const sortedPercentileClips = sortPercentileCycleClipsForDisplay(categoryId, categoryData.clips);
50679
+ metadataClips = sortedPercentileClips.map((clip, index) => ({
49103
50680
  id: clip.id,
49104
50681
  clipId: clip.id,
49105
50682
  clip_timestamp: clip.creation_timestamp || clip.timestamp,
@@ -49146,8 +50723,9 @@ var BottlenecksContent = ({
49146
50723
  }));
49147
50724
  setVisibleCategoryMetadata(categoryId, metadataClips);
49148
50725
  console.log(`[BottlenecksContent] Loaded metadata for ${categoryId}: ${metadataClips.length} clips`);
49149
- if (autoLoadFirstVideo && metadataClips.length > 0 && s3ClipsService) {
49150
- const firstClipMeta = metadataClips[0];
50726
+ const autoloadCandidates = isInitialTimeHandoffCategory(categoryId) ? metadataClips.filter((clip) => isTimestampInInitialTimeHandoff(clip.clip_timestamp || clip.creation_timestamp || clip.timestamp)) : metadataClips;
50727
+ if (autoLoadFirstVideo && autoloadCandidates.length > 0 && s3ClipsService) {
50728
+ const firstClipMeta = autoloadCandidates[0];
49151
50729
  const firstClipId = firstClipMeta.clipId || firstClipMeta.id;
49152
50730
  try {
49153
50731
  const video = await s3ClipsService.getClipById(firstClipId);
@@ -49181,7 +50759,7 @@ var BottlenecksContent = ({
49181
50759
  setIsCategoryLoading(false);
49182
50760
  }
49183
50761
  }
49184
- }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, idleClipSort, supabase, setVisibleCategoryMetadata, lowMomentsPrefetch, applyPrefetchedFirstVideo, applyMetadataSnapshot, isLowMomentsCategoryAvailable]);
50762
+ }, [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
50763
  React125.useEffect(() => {
49186
50764
  if (activeFilter !== LOW_EFFICIENCY_CATEGORY_ID || !isLowMomentsCategoryAvailable) {
49187
50765
  return;
@@ -49213,6 +50791,37 @@ var BottlenecksContent = ({
49213
50791
  applyMetadataSnapshot,
49214
50792
  isLowMomentsCategoryAvailable
49215
50793
  ]);
50794
+ React125.useEffect(() => {
50795
+ if (!initialTimeFilter?.startTime || !initialTimeFilter?.endTime || !initialTimePrefetch || initialTimePrefetch.loading || initialTimePrefetch.categoryId !== activeFilter) {
50796
+ return;
50797
+ }
50798
+ if (initialTimePrefetch.metadata.length > 0) {
50799
+ applyMetadataSnapshot(
50800
+ initialTimePrefetch.categoryId,
50801
+ initialTimePrefetch.metadata,
50802
+ initialTimePrefetch.total
50803
+ );
50804
+ }
50805
+ if (initialTimePrefetch.firstVideo && isTimestampInInitialTimeHandoff(
50806
+ initialTimePrefetch.firstVideo.creation_timestamp || initialTimePrefetch.firstVideo.timestamp
50807
+ )) {
50808
+ applyPrefetchedFirstVideo(initialTimePrefetch.firstVideo);
50809
+ if (isMountedRef.current) {
50810
+ setIsCategoryLoading(false);
50811
+ setIsInitialLoading(false);
50812
+ setIsTransitioning(false);
50813
+ setIsNavigating(false);
50814
+ }
50815
+ }
50816
+ }, [
50817
+ activeFilter,
50818
+ applyMetadataSnapshot,
50819
+ applyPrefetchedFirstVideo,
50820
+ initialTimeFilter?.endTime,
50821
+ initialTimeFilter?.startTime,
50822
+ initialTimePrefetch,
50823
+ isTimestampInInitialTimeHandoff
50824
+ ]);
49216
50825
  React125.useEffect(() => {
49217
50826
  if (previousIdleClipSortRef.current === idleClipSort) {
49218
50827
  return;
@@ -49252,7 +50861,7 @@ var BottlenecksContent = ({
49252
50861
  currentTotalRef.current = total;
49253
50862
  setCurrentTotal(total);
49254
50863
  previousFilterRef.current = activeFilter;
49255
- const metadataLoadPlan = getCategoryMetadataLoadPlanForFilterChange({
50864
+ const metadataLoadPlan = isInitialTimeHandoffCategory(activeFilter) ? { shouldLoad: false, autoLoadFirstVideo: false } : getCategoryMetadataLoadPlanForFilterChange({
49256
50865
  activeFilter,
49257
50866
  currentClipId,
49258
50867
  categoryTotal: total
@@ -49276,7 +50885,7 @@ var BottlenecksContent = ({
49276
50885
  }
49277
50886
  }
49278
50887
  }
49279
- }, [activeFilter, allVideos, mergedCounts, currentClipId, loadCategoryMetadata]);
50888
+ }, [activeFilter, allVideos, mergedCounts, currentClipId, loadCategoryMetadata, isInitialTimeHandoffCategory]);
49280
50889
  React125.useEffect(() => {
49281
50890
  if (!currentClipId || activeFilter === "all") {
49282
50891
  return;
@@ -49304,10 +50913,19 @@ var BottlenecksContent = ({
49304
50913
  console.warn("[BottlenecksContent] Error disposing player:", e);
49305
50914
  }
49306
50915
  }
49307
- loadingTimeoutRef.current = setTimeout(() => {
50916
+ if (loadingTimeoutRef.current) {
50917
+ clearTimeout(loadingTimeoutRef.current);
50918
+ loadingTimeoutRef.current = null;
50919
+ }
50920
+ const loadingTimeout = setTimeout(() => {
50921
+ if (loadingTimeoutRef.current !== loadingTimeout) {
50922
+ return;
50923
+ }
50924
+ loadingTimeoutRef.current = null;
49308
50925
  console.warn("[BottlenecksContent] Loading timeout - clearing stuck loading state");
49309
50926
  clearLoadingState();
49310
50927
  }, 2e3);
50928
+ loadingTimeoutRef.current = loadingTimeout;
49311
50929
  if (activeFilterRef.current !== categoryId) {
49312
50930
  updateActiveFilter(categoryId);
49313
50931
  }
@@ -49327,17 +50945,20 @@ var BottlenecksContent = ({
49327
50945
  setCurrentClipId(clipId);
49328
50946
  setAllVideos([video]);
49329
50947
  setCurrentIndex(0);
50948
+ clearLoadingState();
49330
50949
  } else {
49331
50950
  throw new Error(`Failed to load video data for clip ${clipId}`);
49332
50951
  }
49333
50952
  await metadataPromise;
49334
50953
  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) {
50954
+ const getMetadataClipId = (clip) => clip?.clipId || clip?.id;
50955
+ const metadataHasClip = (clips) => clips.some((clip) => getMetadataClipId(clip) === clipId);
50956
+ const fallbackHasClip = fallbackMetadata?.some((clip) => getMetadataClipId(clip) === clipId);
50957
+ if ((metadataArray.length === 0 || !metadataHasClip(metadataArray)) && fallbackHasClip) {
49337
50958
  applyMetadataSnapshot(categoryId, fallbackMetadata, fallbackTotal);
49338
50959
  metadataArray = fallbackMetadata;
49339
50960
  }
49340
- if (metadataArray.length === 0 || !metadataArray.some((clip) => clip.clipId === clipId)) {
50961
+ if (metadataArray.length === 0 || !metadataHasClip(metadataArray)) {
49341
50962
  if (!fallbackHasClip) {
49342
50963
  console.warn(`[BottlenecksContent] Clip ${clipId} not found in metadata for ${categoryId} (cache hit: ${metadataArray.length > 0}) - forcing refresh`);
49343
50964
  await loadCategoryMetadata(categoryId, false, true);
@@ -49356,7 +50977,7 @@ var BottlenecksContent = ({
49356
50977
  }
49357
50978
  return;
49358
50979
  }
49359
- const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
50980
+ const clickedClipIndex = metadataArray.findIndex((clip) => getMetadataClipId(clip) === clipId);
49360
50981
  if (clickedClipIndex === -1) {
49361
50982
  console.warn(`[BottlenecksContent] Clip ${clipId} not found after metadata refresh`);
49362
50983
  if (!shouldUseMetadataNavigation(categoryId)) {
@@ -49744,6 +51365,32 @@ var BottlenecksContent = ({
49744
51365
  }
49745
51366
  return filteredVideos[currentIndex];
49746
51367
  }, [filteredVideos, currentIndex]);
51368
+ const currentVideoMatchesInitialTimeHandoff = React125.useMemo(() => {
51369
+ if (!hasInitialTimeHandoff) {
51370
+ return true;
51371
+ }
51372
+ if (!currentVideo) {
51373
+ return false;
51374
+ }
51375
+ if (!isInitialTimeHandoffCategory(activeFilter) || !isInitialTimeHandoffCategory(currentVideo.type)) {
51376
+ return false;
51377
+ }
51378
+ return isTimestampInInitialTimeHandoff(
51379
+ currentVideo.creation_timestamp || currentVideo.timestamp || currentVideo.clip_end_time || currentVideo.clip_start_time
51380
+ );
51381
+ }, [
51382
+ activeFilter,
51383
+ currentVideo,
51384
+ hasInitialTimeHandoff,
51385
+ isInitialTimeHandoffCategory,
51386
+ isTimestampInInitialTimeHandoff
51387
+ ]);
51388
+ const shouldHoldInitialTimeHandoffVideo = Boolean(
51389
+ hasInitialTimeHandoff && !currentVideoMatchesInitialTimeHandoff && (isCategoryLoading || filteredVideos.length > 0 || (mergedCounts[activeFilter] || 0) > 0)
51390
+ );
51391
+ const canRenderCurrentVideo = Boolean(
51392
+ filteredVideos.length > 0 && currentVideo && !isFullscreen && !shouldHoldInitialTimeHandoffVideo
51393
+ );
49747
51394
  const currentLowEfficiencyClipId = currentVideo?.id || currentClipId || null;
49748
51395
  const isCurrentLowEfficiencyClip = Boolean(
49749
51396
  currentVideo?.type === "recent_flow_red_streak" || currentVideo?.red_flow_timeline
@@ -49910,11 +51557,111 @@ var BottlenecksContent = ({
49910
51557
  }
49911
51558
  return currentPosition;
49912
51559
  }, [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(
51560
+ const initialTimePrefetchedMetadata = React125.useMemo(() => {
51561
+ if (!initialTimeFilter?.startTime || !initialTimeFilter?.endTime || !initialTimePrefetch || initialTimePrefetch.loading) {
51562
+ return void 0;
51563
+ }
51564
+ if (initialTimePrefetch.metadataByCategory) {
51565
+ return initialTimePrefetch.metadataByCategory;
51566
+ }
51567
+ if (initialTimePrefetch.categoryId === activeFilter && initialTimePrefetch.metadata.length > 0) {
51568
+ return { [activeFilter]: initialTimePrefetch.metadata };
51569
+ }
51570
+ return void 0;
51571
+ }, [
49914
51572
  activeFilter,
51573
+ initialTimePrefetch,
51574
+ initialTimeFilter?.endTime,
51575
+ initialTimeFilter?.startTime
51576
+ ]);
51577
+ const prefetchedExplorerMetadata = React125.useMemo(() => {
51578
+ if (initialTimePrefetchedMetadata) {
51579
+ return initialTimePrefetchedMetadata;
51580
+ }
51581
+ if (activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && !lowMomentsPrefetch.loading && lowMomentsPrefetch.metadata.length > 0) {
51582
+ return { [LOW_EFFICIENCY_CATEGORY_ID]: lowMomentsPrefetch.metadata };
51583
+ }
51584
+ if (activeFilter === "idle_time" && categoryMetadataSort !== idleClipSort) {
51585
+ return void 0;
51586
+ }
51587
+ const metadataSnapshot = buildPrefetchedExplorerMetadata(
51588
+ activeFilter,
51589
+ categoryMetadataCategoryId,
51590
+ categoryMetadata
51591
+ );
51592
+ if (metadataSnapshot) {
51593
+ return metadataSnapshot;
51594
+ }
51595
+ if (initialTimeFilter?.startTime && initialTimeFilter?.endTime && currentVideo && currentVideo.type === activeFilter) {
51596
+ return {
51597
+ [activeFilter]: [{
51598
+ id: currentVideo.id,
51599
+ clipId: currentVideo.id,
51600
+ clip_timestamp: currentVideo.creation_timestamp || currentVideo.timestamp,
51601
+ description: currentVideo.description,
51602
+ severity: currentVideo.severity,
51603
+ category: activeFilter,
51604
+ duration: typeof currentVideo.duration === "number" ? currentVideo.duration : typeof currentVideo.cycle_time_seconds === "number" ? currentVideo.cycle_time_seconds : void 0,
51605
+ clip_start_time: currentVideo.clip_start_time,
51606
+ clip_end_time: currentVideo.clip_end_time,
51607
+ index: 0,
51608
+ idle_start_time: currentVideo.idle_start_time,
51609
+ idle_end_time: currentVideo.idle_end_time,
51610
+ cycle_item_count: null,
51611
+ red_flow_timeline: currentVideo.red_flow_timeline,
51612
+ red_flow_severity_score: currentVideo.red_flow_severity_score,
51613
+ red_flow_output_shortfall_units: currentVideo.red_flow_output_shortfall_units,
51614
+ red_flow_worst_minute: currentVideo.red_flow_worst_minute,
51615
+ red_flow_explanation_summary: currentVideo.red_flow_explanation_summary,
51616
+ red_flow_explanation: currentVideo.red_flow_explanation
51617
+ }]
51618
+ };
51619
+ }
51620
+ return void 0;
51621
+ }, [
51622
+ activeFilter,
51623
+ categoryMetadata,
49915
51624
  categoryMetadataCategoryId,
49916
- categoryMetadata
49917
- ), [activeFilter, categoryMetadata, categoryMetadataCategoryId, categoryMetadataSort, idleClipSort, lowMomentsPrefetch, workspaceId, effectiveDateString, effectiveShiftId, isLowMomentsCategoryAvailable]);
51625
+ categoryMetadataSort,
51626
+ currentVideo,
51627
+ idleClipSort,
51628
+ initialTimePrefetchedMetadata,
51629
+ initialTimePrefetch,
51630
+ initialTimeFilter?.endTime,
51631
+ initialTimeFilter?.startTime,
51632
+ lowMomentsPrefetch,
51633
+ workspaceId,
51634
+ effectiveDateString,
51635
+ effectiveShiftId,
51636
+ isLowMomentsCategoryAvailable
51637
+ ]);
51638
+ const externallyManagedLoadingCategories = React125.useMemo(() => {
51639
+ const managedCategories = {
51640
+ recent_flow_red_streak: Boolean(
51641
+ activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && lowMomentsPrefetch.loading
51642
+ )
51643
+ };
51644
+ const isInitialTimeHandoffPending = Boolean(
51645
+ initialTimeFilter?.startTime && initialTimeFilter?.endTime && requestedInitialCategoryCandidates.length > 0 && (!initialTimePrefetch || initialTimePrefetch.loading)
51646
+ );
51647
+ if (isInitialTimeHandoffPending) {
51648
+ requestedInitialCategoryCandidates.forEach((categoryId) => {
51649
+ managedCategories[categoryId] = true;
51650
+ });
51651
+ }
51652
+ return managedCategories;
51653
+ }, [
51654
+ activeFilter,
51655
+ effectiveDateString,
51656
+ effectiveShiftId,
51657
+ initialTimeFilter?.endTime,
51658
+ initialTimeFilter?.startTime,
51659
+ initialTimePrefetch,
51660
+ isLowMomentsCategoryAvailable,
51661
+ lowMomentsPrefetch,
51662
+ requestedInitialCategoryCandidates,
51663
+ workspaceId
51664
+ ]);
49918
51665
  const classificationClipIds = React125.useMemo(() => {
49919
51666
  if (!idleTimeVlmEnabled) {
49920
51667
  return [];
@@ -50324,7 +52071,7 @@ var BottlenecksContent = ({
50324
52071
  /* @__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
52072
  ] });
50326
52073
  }
50327
- if (clipTypesLoading && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
52074
+ if (!hasInitialTimeHandoff && clipTypesLoading && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
50328
52075
  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
52076
  }
50330
52077
  if (error && error.type === "fatal" && !hasInitialLoad || clipTypesError) {
@@ -50334,7 +52081,30 @@ var BottlenecksContent = ({
50334
52081
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: error?.message || clipTypesError })
50335
52082
  ] });
50336
52083
  }
50337
- const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
52084
+ const categoriesToShow = (() => {
52085
+ const categories = clipTypes.length > 0 ? [...clipTypes] : [];
52086
+ const existingTypes = new Set(categories.map((category) => category.type));
52087
+ requestedInitialCategoryCandidates.forEach((categoryType) => {
52088
+ if (existingTypes.has(categoryType) || !hasInitialTimeHandoff && (mergedCounts[categoryType] || 0) <= 0) {
52089
+ return;
52090
+ }
52091
+ const fallback = CHART_HANDOFF_CATEGORY_FALLBACKS[categoryType];
52092
+ if (!fallback) {
52093
+ return;
52094
+ }
52095
+ categories.push({
52096
+ id: categoryType,
52097
+ type: categoryType,
52098
+ label: fallback.label,
52099
+ description: fallback.description,
52100
+ color: fallback.color,
52101
+ icon: fallback.icon,
52102
+ sort_order: 50
52103
+ });
52104
+ existingTypes.add(categoryType);
52105
+ });
52106
+ return categories;
52107
+ })();
50338
52108
  console.log("[BottlenecksContent] Categories to show:", {
50339
52109
  categoriesToShow,
50340
52110
  categoriesToShowLength: categoriesToShow?.length,
@@ -50353,190 +52123,204 @@ var BottlenecksContent = ({
50353
52123
  }
50354
52124
  ),
50355
52125
  /* @__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,
52126
+ /* @__PURE__ */ jsxRuntime.jsx(
52127
+ "div",
52128
+ {
52129
+ className: "min-w-0 w-full lg:flex-[3] lg:h-full",
52130
+ "data-qa-clips-active-filter": activeFilter || "",
52131
+ "data-qa-clips-current-video-type": currentVideo?.type || "",
52132
+ "data-qa-clips-current-video-id": currentVideo?.id || "",
52133
+ "data-qa-clips-current-clip-id": currentClipId || "",
52134
+ "data-qa-clips-filtered-count": filteredVideos.length,
52135
+ "data-qa-clips-all-count": allVideos.length,
52136
+ "data-qa-clips-current-video-matches-hour": String(currentVideoMatchesInitialTimeHandoff),
52137
+ "data-qa-clips-hold-hour-video": String(shouldHoldInitialTimeHandoffVideo),
52138
+ 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: [
52139
+ /* @__PURE__ */ jsxRuntime.jsx(
52140
+ "div",
50367
52141
  {
50368
- ref: videoRef,
50369
- src: currentVideo.src,
50370
- poster: "",
50371
52142
  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();
52143
+ style: {
52144
+ opacity: isTransitioning ? 0 : 1,
52145
+ transition: "opacity 0.1s ease-in-out"
50428
52146
  },
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"
52147
+ children: !shouldDeferPlayerRenderForCrop && /* @__PURE__ */ jsxRuntime.jsx(
52148
+ CroppedVideoPlayer,
52149
+ {
52150
+ ref: videoRef,
52151
+ src: currentVideo.src,
52152
+ poster: "",
52153
+ className: "w-full h-full",
52154
+ crop: workspaceCrop?.crop,
52155
+ autoplay: true,
52156
+ playsInline: true,
52157
+ loop: false,
52158
+ externalLoadingControl: true,
52159
+ onReady: handleVideoReady,
52160
+ onPlay: handleVideoPlay,
52161
+ onPause: handleVideoPause,
52162
+ onTimeUpdate: handleTimeUpdate,
52163
+ onDurationChange: handleDurationChange,
52164
+ onEnded: handleVideoEnded,
52165
+ onError: handleVideoError,
52166
+ onLoadedData: handleLoadedData,
52167
+ onPlaying: handleVideoPlaying,
52168
+ onLoadingChange: handleVideoLoadingChange,
52169
+ onShare: handleShareClip,
52170
+ isShareLoading,
52171
+ isShareCopied,
52172
+ timelineAnnotations: currentVideo.red_flow_timeline,
52173
+ timelineExplanation: currentVideo.red_flow_explanation,
52174
+ timelineTimezone: timezone,
52175
+ options: videoPlayerOptions
52176
+ },
52177
+ `${currentVideo.id}-${playerInstanceNonce}-inline`
52178
+ )
50431
52179
  }
50432
52180
  ),
50433
- error.canRetry && /* @__PURE__ */ jsxRuntime.jsx(
50434
- "button",
52181
+ currentVideo.type === "recent_flow_red_streak" && !shouldDeferPlayerRenderForCrop ? /* @__PURE__ */ jsxRuntime.jsx(
52182
+ RedFlowDiagnosticOverlay,
50435
52183
  {
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"
52184
+ timeline: currentVideo.red_flow_timeline,
52185
+ explanation: currentVideo.red_flow_explanation,
52186
+ aiSummary: currentLowEfficiencyAiSummary,
52187
+ aiSummaryLoading: isCurrentLowEfficiencyAiSummaryLoading,
52188
+ aiSummaryError: currentLowEfficiencyAiSummaryError,
52189
+ className: "right-4 top-4"
50442
52190
  }
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
- ] }) }) });
52191
+ ) : null,
52192
+ 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..." }) }),
52193
+ !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..." }) }),
52194
+ 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: [
52195
+ /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md" }),
52196
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white text-sm mt-4 font-medium", children: error.message })
52197
+ ] }) }),
52198
+ 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: [
52199
+ /* @__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" }) }),
52200
+ /* @__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" }),
52201
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-300 mb-6", children: error.message }),
52202
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 justify-center", children: [
52203
+ error.canSkip && /* @__PURE__ */ jsxRuntime.jsx(
52204
+ "button",
52205
+ {
52206
+ onClick: () => {
52207
+ setError(null);
52208
+ videoRetryCountRef.current = 0;
52209
+ handleNext();
52210
+ },
52211
+ className: "px-5 py-2.5 bg-blue-600 hover:bg-blue-700 rounded-md text-sm font-medium transition-colors",
52212
+ children: "Skip to Next Clip"
52213
+ }
52214
+ ),
52215
+ error.canRetry && /* @__PURE__ */ jsxRuntime.jsx(
52216
+ "button",
52217
+ {
52218
+ onClick: () => {
52219
+ videoRetryCountRef.current = 0;
52220
+ restartCurrentClipPlayback();
52221
+ },
52222
+ className: "px-5 py-2.5 bg-gray-600 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors",
52223
+ children: "Retry"
52224
+ }
52225
+ )
52226
+ ] })
52227
+ ] }) }),
52228
+ (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" ? (
52229
+ // Show full colored badge for idle time
52230
+ (() => {
52231
+ const classification = getIdleTimeClassification(currentVideo);
52232
+ const confidence = getIdleTimeConfidence(currentVideo);
52233
+ const config = getRootCauseConfig(classification);
52234
+ if (!config) return null;
52235
+ const IconComponent = config.Icon;
52236
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52237
+ 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: [
52238
+ /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: `h-3.5 w-3.5 flex-shrink-0 ${config.iconColor}`, strokeWidth: 2.5 }),
52239
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `truncate font-medium text-xs ${config.color}`, children: config.displayName || "Analyzing..." })
52240
+ ] }) }),
52241
+ idleTimeVlmEnabled && confidence !== null && (() => {
52242
+ const confidencePercent = confidence * 100;
52243
+ let confidenceLabel = "Low";
52244
+ let confidenceColor = "text-red-500";
52245
+ if (confidencePercent > 95) {
52246
+ confidenceLabel = "High";
52247
+ confidenceColor = "text-green-500";
52248
+ } else if (confidencePercent >= 91) {
52249
+ confidenceLabel = "Medium";
52250
+ confidenceColor = "text-yellow-500";
52251
+ }
52252
+ 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: [
52253
+ "AI Confidence: ",
52254
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: confidenceColor, children: confidenceLabel })
52255
+ ] }) }) });
52256
+ })()
52257
+ ] });
50474
52258
  })()
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: [
52259
+ ) : /* @__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: [
52260
+ /* @__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` }),
52261
+ (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: [
52262
+ "Cycle time: ",
52263
+ currentVideo.cycle_time_seconds.toFixed(1),
52264
+ "s"
52265
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52266
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
52267
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
52268
+ ] })
52269
+ ] }) }) : (
52270
+ /* Right side display for other video types */
52271
+ currentVideo.type === "idle_time" ? (
52272
+ // Show full colored badge for idle time
52273
+ (() => {
52274
+ const classification = getIdleTimeClassification(currentVideo);
52275
+ const confidence = getIdleTimeConfidence(currentVideo);
52276
+ const config = getRootCauseConfig(classification);
52277
+ if (!config) return null;
52278
+ const IconComponent = config.Icon;
52279
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52280
+ 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: [
52281
+ "AI Confidence: ",
52282
+ (confidence * 100).toFixed(0),
52283
+ "%"
52284
+ ] }) }) }),
52285
+ 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: [
52286
+ /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: `h-3.5 w-3.5 flex-shrink-0 ${config.iconColor}`, strokeWidth: 2.5 }),
52287
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `truncate font-medium text-xs ${config.color}`, children: config.displayName || "Analyzing..." })
52288
+ ] }) })
52289
+ ] });
52290
+ })()
52291
+ ) : /* @__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: [
52292
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${getSeverityColor(currentVideo.severity)} mr-2 animate-pulse` }),
52293
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
52294
+ currentVideo.type !== "recent_flow_red_streak" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
52295
+ ] }) })
52296
+ )
52297
+ ] }) }) }) : 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..." }) }) }) }) : (
52298
+ /* Priority 5: Show "no clips found" only if we have counts and there are truly no clips for workspace */
52299
+ 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
52300
  /* @__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." })
52301
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Clips Found" }),
52302
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There were no video clips found for this workspace today." })
50533
52303
  ] }) }) : (
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..." }) }) }) })
52304
+ /* Priority 5.5: Show "no folder selected" if activeFilter is empty */
52305
+ 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: [
52306
+ /* @__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" }) }),
52307
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Folder Selected" }),
52308
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "Please select a folder to view clips." })
52309
+ ] }) }) : (
52310
+ /* Priority 6: Show "no matching clips" only if we have data loaded and specifically no clips for this filter */
52311
+ 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: [
52312
+ /* @__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" }) }),
52313
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Matching Clips" }),
52314
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There are no clips matching the selected filter." })
52315
+ ] }) }) : (
52316
+ /* Priority 7: Default loading state for any other case */
52317
+ /* @__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..." }) }) }) })
52318
+ )
52319
+ )
50536
52320
  )
50537
- )
50538
- )
50539
- ) }) }),
52321
+ ) })
52322
+ }
52323
+ ),
50540
52324
  /* @__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
52325
  /* Triage Mode - Direct tile view for cycle completions and idle time */
50542
52326
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg shadow-sm h-full overflow-hidden flex flex-col", children: [
@@ -50671,7 +52455,7 @@ var BottlenecksContent = ({
50671
52455
  })),
50672
52456
  videos: allVideos || [],
50673
52457
  activeFilter,
50674
- currentVideoId: currentVideo?.id,
52458
+ currentVideoId: currentVideo?.id || currentClipId || void 0,
50675
52459
  counts: mergedCounts,
50676
52460
  isReady: hasInitialLoad,
50677
52461
  prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts || void 0 : void 0,
@@ -50685,11 +52469,9 @@ var BottlenecksContent = ({
50685
52469
  idleTimeVlmEnabled,
50686
52470
  showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
50687
52471
  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
- },
52472
+ prefetchedClipTotals: initialTimePrefetch?.loading ? void 0 : initialTimePrefetch?.totalsByCategory,
52473
+ prefetchedPercentileClips: initialTimePrefetch?.loading ? void 0 : initialTimePrefetch?.percentileClipsByCategory,
52474
+ externallyManagedLoadingCategories,
50693
52475
  activeCategoryLoading: isCategoryLoading,
50694
52476
  idleClipSort,
50695
52477
  onIdleClipSortChange: setIdleClipSort,
@@ -68482,17 +70264,7 @@ var setSessionSeenValue = (key) => {
68482
70264
  };
68483
70265
  var buildAllGreenCelebrationSeenKey = (identity) => `${ALL_GREEN_CELEBRATION_SEEN_PREFIX}${identity}`;
68484
70266
  var buildAllGreenMilestoneSeenKey = (identity, milestoneSeconds) => `${ALL_GREEN_MILESTONE_SEEN_PREFIX}${identity}:${milestoneSeconds}`;
68485
- var LINE_SELECTOR_INDICATOR_VERSION = "incident_exclamation_v1";
68486
- var LineSelectorIncidentIcon = ({ lineId }) => /* @__PURE__ */ jsxRuntime.jsx(
68487
- "span",
68488
- {
68489
- "data-testid": `line-selector-incident-icon-${lineId}`,
68490
- "aria-label": "Line needs attention",
68491
- role: "img",
68492
- className: "inline-flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full border border-rose-200 bg-white text-[13px] font-semibold leading-none text-rose-600 shadow-[0_1px_2px_rgba(15,23,42,0.06)]",
68493
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "-mt-px", children: "!" })
68494
- }
68495
- );
70267
+ var LINE_SELECTOR_INDICATOR_VERSION = "line_signal_dots_v1";
68496
70268
  var LoadingPageCmp = LoadingPage_default;
68497
70269
  var LoadingOverlayCmp = LoadingOverlay_default;
68498
70270
  function HomeView({
@@ -68832,8 +70604,8 @@ function HomeView({
68832
70604
  const currentIsCurrentScopeResolved = isBootstrapMonitorMode ? bootstrapMonitor.isCurrentScopeResolved : legacyIsCurrentScopeResolved;
68833
70605
  const currentMetricsError = isBootstrapMonitorMode ? bootstrapMonitor.error : legacyMetricsError;
68834
70606
  const currentRefetchMetrics = isBootstrapMonitorMode ? bootstrapMonitor.refetch : refetchLegacyMetrics;
68835
- const lineSelectorIndicatorByLine = React125.useMemo(() => {
68836
- const indicatorByLine = /* @__PURE__ */ new Map();
70607
+ const lineSelectorSignalStatusByLine = React125.useMemo(() => {
70608
+ const statusByLine = /* @__PURE__ */ new Map();
68837
70609
  const legend = currentEfficiencyLegend || DEFAULT_EFFICIENCY_LEGEND;
68838
70610
  const addRows = (rows) => {
68839
70611
  (rows || []).forEach((row) => {
@@ -68841,40 +70613,36 @@ function HomeView({
68841
70613
  if (!lineId) {
68842
70614
  return;
68843
70615
  }
68844
- if (row?.red_flow_incident?.active === true) {
68845
- indicatorByLine.set(lineId, "incident");
68846
- return;
68847
- }
68848
- if (indicatorByLine.get(lineId) === "incident") {
68849
- return;
68850
- }
68851
70616
  const status = getKpiSignalStatus(row?.line_signal, legend);
68852
- if (status === "attention") {
68853
- indicatorByLine.set(lineId, "red");
70617
+ if (status) {
70618
+ statusByLine.set(lineId, status);
68854
70619
  }
68855
70620
  });
68856
70621
  };
68857
70622
  addRows(currentSelectorLineMetrics);
68858
70623
  addRows(currentLineMetrics);
68859
- return indicatorByLine;
70624
+ return statusByLine;
68860
70625
  }, [currentEfficiencyLegend, currentLineMetrics, currentSelectorLineMetrics]);
68861
- const lineSelectorIndicatorStats = React125.useMemo(() => {
68862
- let incidentLineCount = 0;
68863
- let redFlowLineCount = 0;
70626
+ const lineSelectorSignalDotStats = React125.useMemo(() => {
70627
+ let stableSignalLineCount = 0;
70628
+ let warningSignalLineCount = 0;
70629
+ let attentionSignalLineCount = 0;
68864
70630
  visibleLineIds.forEach((lineId) => {
68865
- const indicator = lineSelectorIndicatorByLine.get(lineId);
68866
- if (indicator === "incident") {
68867
- incidentLineCount += 1;
68868
- } else if (indicator === "red") {
68869
- redFlowLineCount += 1;
70631
+ const status = lineSelectorSignalStatusByLine.get(lineId);
70632
+ if (status === "stable") {
70633
+ stableSignalLineCount += 1;
70634
+ } else if (status === "warning") {
70635
+ warningSignalLineCount += 1;
70636
+ } else if (status === "attention") {
70637
+ attentionSignalLineCount += 1;
68870
70638
  }
68871
70639
  });
68872
70640
  return {
68873
- incidentLineCount,
68874
- redFlowLineCount,
68875
- hasAnyIncident: incidentLineCount > 0
70641
+ stableSignalLineCount,
70642
+ warningSignalLineCount,
70643
+ attentionSignalLineCount
68876
70644
  };
68877
- }, [lineSelectorIndicatorByLine, visibleLineIds]);
70645
+ }, [lineSelectorSignalStatusByLine, visibleLineIds]);
68878
70646
  const metricsDisplayNames = React125.useMemo(() => {
68879
70647
  const nextDisplayNames = {};
68880
70648
  currentWorkspaceMetrics.forEach((workspace) => {
@@ -69731,12 +71499,13 @@ function HomeView({
69731
71499
  new_line_ids: normalizedLineIds,
69732
71500
  selected_line_count: normalizedLineIds.length,
69733
71501
  selection_mode: isAllLinesSelection(normalizedLineIds) ? "all" : normalizedLineIds.length === 1 ? "single" : "custom",
69734
- incident_line_count: lineSelectorIndicatorStats.incidentLineCount,
69735
- red_flow_line_count: lineSelectorIndicatorStats.redFlowLineCount,
71502
+ stable_signal_line_count: lineSelectorSignalDotStats.stableSignalLineCount,
71503
+ warning_signal_line_count: lineSelectorSignalDotStats.warningSignalLineCount,
71504
+ attention_signal_line_count: lineSelectorSignalDotStats.attentionSignalLineCount,
69736
71505
  selector_indicator_version: LINE_SELECTOR_INDICATOR_VERSION,
69737
71506
  line_name: getLineSelectionLabel(normalizedLineIds)
69738
71507
  });
69739
- }, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, lineSelectorIndicatorStats, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
71508
+ }, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, lineSelectorSignalDotStats, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
69740
71509
  React125.useCallback(() => {
69741
71510
  updateSelectedLineIds(visibleLineIds);
69742
71511
  }, [updateSelectedLineIds, visibleLineIds]);
@@ -69795,8 +71564,9 @@ function HomeView({
69795
71564
  selected_line_ids: selectedLineIds,
69796
71565
  selected_line_count: selectedLineIds.length,
69797
71566
  is_all_lines: isAllLinesSelection(selectedLineIds),
69798
- incident_line_count: lineSelectorIndicatorStats.incidentLineCount,
69799
- red_flow_line_count: lineSelectorIndicatorStats.redFlowLineCount,
71567
+ stable_signal_line_count: lineSelectorSignalDotStats.stableSignalLineCount,
71568
+ warning_signal_line_count: lineSelectorSignalDotStats.warningSignalLineCount,
71569
+ attention_signal_line_count: lineSelectorSignalDotStats.attentionSignalLineCount,
69800
71570
  selector_indicator_version: LINE_SELECTOR_INDICATOR_VERSION
69801
71571
  });
69802
71572
  }
@@ -69838,7 +71608,8 @@ function HomeView({
69838
71608
  ] }),
69839
71609
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 space-y-0.5 overflow-y-auto pr-1", children: visibleLineIds.map((lineId) => {
69840
71610
  const isChecked = pendingSelectedLineIds.includes(lineId);
69841
- const selectorIndicator = lineSelectorIndicatorByLine.get(lineId);
71611
+ const signalStatus = lineSelectorSignalStatusByLine.get(lineId);
71612
+ const signalDotClass = signalStatus === "stable" ? "bg-green-500" : signalStatus === "warning" ? "bg-yellow-400" : signalStatus === "attention" ? "bg-red-500" : "";
69842
71613
  const lineLabel = mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}`;
69843
71614
  return /* @__PURE__ */ jsxRuntime.jsxs(
69844
71615
  "label",
@@ -69866,11 +71637,11 @@ function HomeView({
69866
71637
  }
69867
71638
  ),
69868
71639
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: lineLabel }),
69869
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center", children: selectorIndicator === "incident" ? /* @__PURE__ */ jsxRuntime.jsx(LineSelectorIncidentIcon, { lineId }) : selectorIndicator === "red" && !lineSelectorIndicatorStats.hasAnyIncident ? /* @__PURE__ */ jsxRuntime.jsx(
71640
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-2.5 w-2.5 flex-shrink-0 items-center justify-center", children: signalStatus ? /* @__PURE__ */ jsxRuntime.jsx(
69870
71641
  "span",
69871
71642
  {
69872
71643
  "data-testid": `line-selector-signal-dot-${lineId}`,
69873
- className: "h-2 w-2 rounded-full bg-red-500",
71644
+ className: `h-2 w-2 rounded-full ${signalDotClass}`,
69874
71645
  "aria-hidden": "true"
69875
71646
  }
69876
71647
  ) : null })
@@ -69902,8 +71673,8 @@ function HomeView({
69902
71673
  mergedLineNames,
69903
71674
  selectedLineIds,
69904
71675
  pendingSelectedLineIds,
69905
- lineSelectorIndicatorByLine,
69906
- lineSelectorIndicatorStats,
71676
+ lineSelectorSignalStatusByLine,
71677
+ lineSelectorSignalDotStats,
69907
71678
  displayMode,
69908
71679
  slideshowActiveLineId,
69909
71680
  visibleLineIds,
@@ -70503,9 +72274,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
70503
72274
  await preInitializeWorkspaceDisplayNames(selectedLineId);
70504
72275
  }
70505
72276
  } else if (lineIdArray.length > 0) {
70506
- await Promise.all(
70507
- lineIdArray.map((lineId) => preInitializeWorkspaceDisplayNames(lineId))
70508
- );
72277
+ await preInitializeWorkspaceDisplayNamesForLines(lineIdArray);
70509
72278
  } else {
70510
72279
  await preInitializeWorkspaceDisplayNames();
70511
72280
  }
@@ -80969,7 +82738,6 @@ var TargetsViewUI = ({
80969
82738
  skuRequired = false,
80970
82739
  onUpdateWorkspaceSelectedSku
80971
82740
  }) => {
80972
- const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
80973
82741
  const mobileMenuContext = useMobileMenu();
80974
82742
  useHideMobileHeader(!!mobileMenuContext);
80975
82743
  if (isLoading) {
@@ -81151,7 +82919,7 @@ var TargetsViewUI = ({
81151
82919
  ] })
81152
82920
  ] }) }),
81153
82921
  /* @__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);
82922
+ const formattedName = workspace.displayName || formatWorkspaceName(workspace.name, lineId);
81155
82923
  const realSkuOptions = (workspace.skuRows || []).filter((r2) => !r2.is_dummy);
81156
82924
  const showSkuDropdown = !!onUpdateWorkspaceSelectedSku && realSkuOptions.length >= 1;
81157
82925
  const isPlanLocked = !!workspace.productionPlanLock?.locked;
@@ -81702,6 +83470,7 @@ var TargetsView = ({
81702
83470
  return {
81703
83471
  id: ws.id,
81704
83472
  name: ws.workspace_id,
83473
+ displayName: ws.display_name || ws.workspace_id,
81705
83474
  targetPPH: selectedRow?.pph_threshold ?? (threshold?.pph_threshold ? Math.round(threshold.pph_threshold) : ""),
81706
83475
  targetCycleTime: selectedRow?.ideal_cycle_time ?? (threshold?.ideal_cycle_time ?? ""),
81707
83476
  targetDayOutput: selectedRow?.total_day_output ?? (threshold?.total_day_output ?? ""),
@@ -81760,6 +83529,7 @@ var TargetsView = ({
81760
83529
  return {
81761
83530
  id: ws.id,
81762
83531
  name: ws.workspace_id,
83532
+ displayName: ws.display_name || ws.workspace_id,
81763
83533
  targetPPH: "",
81764
83534
  targetCycleTime: "",
81765
83535
  targetDayOutput: "",
@@ -82240,15 +84010,31 @@ var TargetsView = ({
82240
84010
  const handleUpdateWorkspaceDisplayName = React125.useCallback(async (workspaceId, displayName) => {
82241
84011
  try {
82242
84012
  const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
84013
+ const nextDisplayName = updated?.display_name || displayName;
84014
+ setAllShiftsData((prev) => {
84015
+ const next = { ...prev };
84016
+ Object.entries(prev).forEach(([shiftId, shiftData]) => {
84017
+ const numericShiftId = Number(shiftId);
84018
+ const updatedShiftData = {};
84019
+ Object.entries(shiftData).forEach(([lineId, line]) => {
84020
+ updatedShiftData[lineId] = {
84021
+ ...line,
84022
+ workspaces: line.workspaces.map(
84023
+ (ws) => ws.id === workspaceId ? { ...ws, displayName: nextDisplayName } : ws
84024
+ )
84025
+ };
84026
+ });
84027
+ next[numericShiftId] = updatedShiftData;
84028
+ });
84029
+ return next;
84030
+ });
82243
84031
  if (updated?.line_id && updated?.workspace_id) {
82244
84032
  upsertWorkspaceDisplayNameInCache({
82245
84033
  lineId: updated.line_id,
82246
84034
  workspaceId: updated.workspace_id,
82247
- displayName: updated?.display_name || displayName,
84035
+ displayName: nextDisplayName,
82248
84036
  enabled: updated?.enable
82249
84037
  });
82250
- } else {
82251
- await forceRefreshWorkspaceDisplayNames();
82252
84038
  }
82253
84039
  sonner.toast.success("Workspace name updated successfully");
82254
84040
  } catch (error) {
@@ -82292,9 +84078,8 @@ var TargetsView = ({
82292
84078
  }
82293
84079
  );
82294
84080
  };
82295
- var TargetsViewWithDisplayNames = withAllWorkspaceDisplayNames(TargetsView);
82296
- var TargetsView_default = TargetsViewWithDisplayNames;
82297
- var AuthenticatedTargetsView = withAuth(React125__namespace.default.memo(TargetsViewWithDisplayNames));
84081
+ var TargetsView_default = TargetsView;
84082
+ var AuthenticatedTargetsView = withAuth(React125__namespace.default.memo(TargetsView));
82298
84083
  function useTimezone(options = {}) {
82299
84084
  const dashboardConfig = useDashboardConfig();
82300
84085
  const workspaceConfig = useWorkspaceConfig();
@@ -82474,17 +84259,22 @@ var WorkspaceHourSummaryPanel = ({
82474
84259
  return;
82475
84260
  }
82476
84261
  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);
84262
+ const summaryTimer = window.setTimeout(() => {
84263
+ void summarize({
84264
+ workspaceId,
84265
+ companyId,
84266
+ date,
84267
+ shiftId,
84268
+ hourIndex: selectedHour.hourIndex,
84269
+ hourStart: selectedHour.startTime,
84270
+ hourEnd: selectedHour.endTime,
84271
+ forceRefresh: false,
84272
+ selectionSource: selectedHour.source ?? "unknown"
84273
+ }).catch(() => void 0);
84274
+ }, 1500);
84275
+ return () => {
84276
+ window.clearTimeout(summaryTimer);
84277
+ };
82488
84278
  }, [canSummarize, companyId, date, selectedHour, selectedKey, shiftId, summarize, workspaceId]);
82489
84279
  if (!selectedHour) {
82490
84280
  return null;
@@ -82668,10 +84458,18 @@ var WorkspaceDetailView = ({
82668
84458
  setSelectedHour(null);
82669
84459
  }, [workspaceId, date, shift]);
82670
84460
  const dashboardConfig = useDashboardConfig();
84461
+ const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
82671
84462
  const { legend: efficiencyLegend } = useEfficiencyLegend();
82672
84463
  const prewarmedClipsRef = React125.useRef(/* @__PURE__ */ new Set());
82673
84464
  const prewarmInFlightRef = React125.useRef(/* @__PURE__ */ new Set());
82674
84465
  const [lowMomentsPrefetch, setLowMomentsPrefetch] = React125.useState(null);
84466
+ const selectedHourClipPrefetchInFlightRef = React125.useRef(/* @__PURE__ */ new Set());
84467
+ const selectedHourClipPrefetchAbortRef = React125.useRef(null);
84468
+ const [initialTimePrefetch, setInitialTimePrefetch] = React125.useState(null);
84469
+ const {
84470
+ isFastSlowClipFiltersEnabled,
84471
+ isResolved: isFastSlowClipFiltersResolved
84472
+ } = useCompanyFastSlowClipFiltersEnabled();
82675
84473
  const [aiSummaryHour, setAiSummaryHour] = React125.useState(null);
82676
84474
  const buildHourlyOutputActionTrackingProps = React125.useCallback((payload) => ({
82677
84475
  workspace_id: workspaceId,
@@ -82701,24 +84499,286 @@ var WorkspaceDetailView = ({
82701
84499
  timezone,
82702
84500
  workspaceId
82703
84501
  ]);
84502
+ const prefetchClipsForSelectedHour = React125.useCallback((hour) => {
84503
+ if (!isClipsEnabled || !dashboardConfig?.s3Config || !workspaceId || !supabase) {
84504
+ return;
84505
+ }
84506
+ const resolvedDate = date || getOperationalDate(timezone);
84507
+ const resolvedShiftId = parsedShiftId ?? selectedShift;
84508
+ if (!resolvedDate || resolvedShiftId === null || resolvedShiftId === void 0) {
84509
+ return;
84510
+ }
84511
+ const categoryId = "cycle_completion";
84512
+ const regularCategoryIds = ["cycle_completion", "idle_time", "recent_flow_red_streak"];
84513
+ const percentileCategoryIds = isFastSlowClipFiltersEnabled ? ["fast-cycles", "slow-cycles"] : [];
84514
+ const allPrefetchCategoryIds = [...regularCategoryIds, ...percentileCategoryIds];
84515
+ const effectiveTimezone = hour.timezone || timezone;
84516
+ const prefetchKey = [
84517
+ workspaceId,
84518
+ resolvedDate,
84519
+ resolvedShiftId,
84520
+ hour.startTime,
84521
+ hour.endTime,
84522
+ effectiveTimezone || "",
84523
+ allPrefetchCategoryIds.join(",")
84524
+ ].join("|");
84525
+ if (initialTimePrefetch?.key === prefetchKey && !initialTimePrefetch.loading) {
84526
+ return;
84527
+ }
84528
+ if (selectedHourClipPrefetchInFlightRef.current.has(prefetchKey)) {
84529
+ return;
84530
+ }
84531
+ selectedHourClipPrefetchAbortRef.current?.abort();
84532
+ const controller = new AbortController();
84533
+ selectedHourClipPrefetchAbortRef.current = controller;
84534
+ selectedHourClipPrefetchInFlightRef.current.add(prefetchKey);
84535
+ const existingSnapshot = initialTimePrefetch?.key === prefetchKey ? initialTimePrefetch : null;
84536
+ setInitialTimePrefetch({
84537
+ key: prefetchKey,
84538
+ categoryId,
84539
+ metadata: existingSnapshot?.metadata || [],
84540
+ metadataByCategory: existingSnapshot?.metadataByCategory || {},
84541
+ totalsByCategory: existingSnapshot?.totalsByCategory || {},
84542
+ percentileClipsByCategory: existingSnapshot?.percentileClipsByCategory || {},
84543
+ firstVideo: existingSnapshot?.firstVideo || null,
84544
+ total: existingSnapshot?.total || 0,
84545
+ loading: true,
84546
+ error: null
84547
+ });
84548
+ const s3Service = videoPrefetchManager.getS3Service(dashboardConfig);
84549
+ const fetchHourlySnapshot = async () => {
84550
+ try {
84551
+ const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
84552
+ method: "POST",
84553
+ headers: {
84554
+ "Content-Type": "application/json"
84555
+ },
84556
+ body: JSON.stringify({
84557
+ action: "hourly-snapshot",
84558
+ workspaceId,
84559
+ date: resolvedDate,
84560
+ shift: resolvedShiftId,
84561
+ startTime: hour.startTime,
84562
+ endTime: hour.endTime,
84563
+ timeFilterTimezone: effectiveTimezone
84564
+ }),
84565
+ signal: controller.signal,
84566
+ redirectReason: "session_expired"
84567
+ });
84568
+ if (!response.ok) {
84569
+ return false;
84570
+ }
84571
+ const snapshot = await response.json();
84572
+ const metadataByCategory = snapshot?.metadataByCategory && typeof snapshot.metadataByCategory === "object" ? snapshot.metadataByCategory : {};
84573
+ const percentileClipsByCategory = snapshot?.percentileClipsByCategory && typeof snapshot.percentileClipsByCategory === "object" ? snapshot.percentileClipsByCategory : {};
84574
+ const totalsByCategory = snapshot?.totalsByCategory && typeof snapshot.totalsByCategory === "object" ? Object.entries(snapshot.totalsByCategory).reduce((accumulator, [key, value]) => {
84575
+ const total = Number(value);
84576
+ accumulator[key] = Number.isFinite(total) ? Math.max(0, total) : 0;
84577
+ return accumulator;
84578
+ }, {}) : {};
84579
+ const snapshotCategoryId = typeof snapshot?.categoryId === "string" ? snapshot.categoryId : categoryId;
84580
+ const metadata = Array.isArray(snapshot?.metadata) ? snapshot.metadata : Array.isArray(metadataByCategory[snapshotCategoryId]) ? metadataByCategory[snapshotCategoryId] : [];
84581
+ if (controller.signal.aborted) {
84582
+ return true;
84583
+ }
84584
+ setInitialTimePrefetch({
84585
+ key: prefetchKey,
84586
+ categoryId: snapshotCategoryId,
84587
+ metadata,
84588
+ metadataByCategory,
84589
+ totalsByCategory,
84590
+ percentileClipsByCategory,
84591
+ firstVideo: snapshot?.firstVideo || null,
84592
+ total: typeof totalsByCategory[snapshotCategoryId] === "number" ? totalsByCategory[snapshotCategoryId] : metadata.length,
84593
+ loading: false,
84594
+ error: null
84595
+ });
84596
+ return true;
84597
+ } catch (error2) {
84598
+ if (error2.name === "AbortError") {
84599
+ return true;
84600
+ }
84601
+ console.warn("[WorkspaceDetailView] Hourly clips snapshot failed; falling back to category prefetch:", error2);
84602
+ return false;
84603
+ }
84604
+ };
84605
+ const fetchRegularCategory = async (prefetchCategoryId) => {
84606
+ const metadataResponse = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
84607
+ method: "POST",
84608
+ headers: {
84609
+ "Content-Type": "application/json"
84610
+ },
84611
+ body: JSON.stringify({
84612
+ action: "clip-metadata",
84613
+ workspaceId,
84614
+ date: resolvedDate,
84615
+ shift: resolvedShiftId,
84616
+ category: prefetchCategoryId,
84617
+ page: 1,
84618
+ limit: 500,
84619
+ knownTotal: null,
84620
+ startTime: hour.startTime,
84621
+ endTime: hour.endTime,
84622
+ timeFilterTimezone: effectiveTimezone,
84623
+ sort: prefetchCategoryId === "recent_flow_red_streak" ? "red_flow_output_shortfall_desc" : "latest"
84624
+ }),
84625
+ signal: controller.signal,
84626
+ redirectReason: "session_expired"
84627
+ });
84628
+ if (!metadataResponse.ok) {
84629
+ throw new Error(`Hourly clips metadata prefetch failed for ${prefetchCategoryId}: ${metadataResponse.status}`);
84630
+ }
84631
+ const metadataData = await metadataResponse.json();
84632
+ return {
84633
+ categoryId: prefetchCategoryId,
84634
+ clips: Array.isArray(metadataData?.clips) ? metadataData.clips : [],
84635
+ total: typeof metadataData?.total === "number" ? metadataData.total : 0
84636
+ };
84637
+ };
84638
+ const fetchPercentileCategory = async (prefetchCategoryId) => {
84639
+ const response = await fetchWithSupabaseAuth(supabase, "/api/clips/supabase", {
84640
+ method: "POST",
84641
+ headers: {
84642
+ "Content-Type": "application/json"
84643
+ },
84644
+ body: JSON.stringify({
84645
+ action: "percentile-clips",
84646
+ workspaceId,
84647
+ startDate: `${resolvedDate}T00:00:00Z`,
84648
+ endDate: `${resolvedDate}T23:59:59Z`,
84649
+ percentile: 10,
84650
+ shiftId: resolvedShiftId,
84651
+ limit: 500,
84652
+ startTime: hour.startTime,
84653
+ endTime: hour.endTime,
84654
+ timeFilterTimezone: effectiveTimezone,
84655
+ percentileAction: prefetchCategoryId
84656
+ }),
84657
+ signal: controller.signal,
84658
+ redirectReason: "session_expired"
84659
+ });
84660
+ if (!response.ok) {
84661
+ throw new Error(`Hourly percentile clips prefetch failed for ${prefetchCategoryId}: ${response.status}`);
84662
+ }
84663
+ const data = await response.json();
84664
+ return {
84665
+ categoryId: prefetchCategoryId,
84666
+ clips: Array.isArray(data?.clips) ? data.clips : [],
84667
+ total: typeof data?.total === "number" ? data.total : 0
84668
+ };
84669
+ };
84670
+ const runPrefetch = async () => {
84671
+ try {
84672
+ const snapshotLoaded = await fetchHourlySnapshot();
84673
+ if (snapshotLoaded) {
84674
+ return;
84675
+ }
84676
+ const cycleCompletionPromise = fetchRegularCategory("cycle_completion");
84677
+ let resolvedFirstVideo = null;
84678
+ const firstVideoPromise = cycleCompletionPromise.then(async (cycleResponse) => {
84679
+ const firstClipId = cycleResponse.clips[0]?.clipId || cycleResponse.clips[0]?.id || null;
84680
+ if (!firstClipId) {
84681
+ return null;
84682
+ }
84683
+ const video = await s3Service.getClipById(firstClipId);
84684
+ resolvedFirstVideo = video;
84685
+ if (!controller.signal.aborted) {
84686
+ setInitialTimePrefetch((prev) => prev?.key === prefetchKey ? {
84687
+ ...prev,
84688
+ firstVideo: video
84689
+ } : prev);
84690
+ }
84691
+ return video;
84692
+ }).catch((error2) => {
84693
+ if (error2.name !== "AbortError") {
84694
+ console.warn("[WorkspaceDetailView] Hourly first clip prefetch failed:", error2);
84695
+ }
84696
+ return null;
84697
+ });
84698
+ const percentileResponsesPromise = Promise.all(percentileCategoryIds.map(fetchPercentileCategory));
84699
+ const regularResponses = await Promise.all([
84700
+ cycleCompletionPromise,
84701
+ fetchRegularCategory("idle_time"),
84702
+ fetchRegularCategory("recent_flow_red_streak")
84703
+ ]);
84704
+ const percentileResponses = await percentileResponsesPromise;
84705
+ const metadataByCategory = regularResponses.reduce((accumulator, response) => {
84706
+ accumulator[response.categoryId] = response.clips;
84707
+ return accumulator;
84708
+ }, {});
84709
+ const percentileClipsByCategory = percentileResponses.reduce((accumulator, response) => {
84710
+ accumulator[response.categoryId] = response.clips;
84711
+ return accumulator;
84712
+ }, {});
84713
+ const totalsByCategory = [...regularResponses, ...percentileResponses].reduce((accumulator, response) => {
84714
+ accumulator[response.categoryId] = Math.max(0, Number(response.total || 0));
84715
+ return accumulator;
84716
+ }, {});
84717
+ const metadata = metadataByCategory[categoryId] || [];
84718
+ if (controller.signal.aborted) {
84719
+ return;
84720
+ }
84721
+ setInitialTimePrefetch({
84722
+ key: prefetchKey,
84723
+ categoryId,
84724
+ metadata,
84725
+ metadataByCategory,
84726
+ totalsByCategory,
84727
+ percentileClipsByCategory,
84728
+ firstVideo: resolvedFirstVideo,
84729
+ total: typeof totalsByCategory[categoryId] === "number" ? totalsByCategory[categoryId] : metadata.length,
84730
+ loading: false,
84731
+ error: null
84732
+ });
84733
+ void firstVideoPromise;
84734
+ } catch (error2) {
84735
+ if (error2.name === "AbortError") {
84736
+ return;
84737
+ }
84738
+ console.warn("[WorkspaceDetailView] Hourly clips prefetch failed:", error2);
84739
+ setInitialTimePrefetch((prev) => prev?.key === prefetchKey ? {
84740
+ ...prev,
84741
+ loading: false,
84742
+ error: error2 instanceof Error ? error2.message : "Hourly clips prefetch failed"
84743
+ } : prev);
84744
+ } finally {
84745
+ selectedHourClipPrefetchInFlightRef.current.delete(prefetchKey);
84746
+ }
84747
+ };
84748
+ void runPrefetch();
84749
+ }, [
84750
+ dashboardConfig,
84751
+ date,
84752
+ initialTimePrefetch,
84753
+ isFastSlowClipFiltersEnabled,
84754
+ isClipsEnabled,
84755
+ parsedShiftId,
84756
+ selectedShift,
84757
+ supabase,
84758
+ timezone,
84759
+ workspaceId
84760
+ ]);
82704
84761
  const handleOutputHourSelect = React125.useCallback((payload) => {
82705
- setSelectedHour({
84762
+ const hour = {
82706
84763
  source: "output",
82707
84764
  hourIndex: payload.hourIndex,
82708
84765
  timeRange: payload.timeRange,
82709
84766
  startTime: payload.startTime,
82710
84767
  endTime: payload.endTime,
84768
+ timezone: payload.timezone,
82711
84769
  status: payload.status,
82712
84770
  output: payload.output,
82713
84771
  target: payload.target
82714
- });
82715
- }, []);
84772
+ };
84773
+ setSelectedHour(hour);
84774
+ prefetchClipsForSelectedHour(hour);
84775
+ }, [prefetchClipsForSelectedHour]);
82716
84776
  const handleAiSummaryClick = React125.useCallback((payload) => {
82717
84777
  trackCoreEvent("Hourly Output Ask AI Clicked", buildHourlyOutputActionTrackingProps(payload));
82718
84778
  setAiSummaryHour(payload);
82719
84779
  }, [buildHourlyOutputActionTrackingProps]);
82720
84780
  const handleCycleHourSelect = React125.useCallback((payload) => {
82721
- setSelectedHour({
84781
+ const hour = {
82722
84782
  source: "cycle",
82723
84783
  hourIndex: payload.hourIndex,
82724
84784
  timeRange: payload.timeRange,
@@ -82728,17 +84788,23 @@ var WorkspaceDetailView = ({
82728
84788
  cycleTime: payload.cycleTime,
82729
84789
  idealCycleTime: payload.idealCycleTime,
82730
84790
  idleMinutes: payload.idleMinutes
82731
- });
82732
- }, []);
84791
+ };
84792
+ setSelectedHour(hour);
84793
+ prefetchClipsForSelectedHour(hour);
84794
+ }, [prefetchClipsForSelectedHour]);
82733
84795
  const handleOpenClipsForHour = React125.useCallback((hour) => {
84796
+ prefetchClipsForSelectedHour(hour);
82734
84797
  setPendingClipHourFilter({
82735
84798
  startTime: hour.startTime,
82736
84799
  endTime: hour.endTime,
82737
84800
  sourceLabel: hour.timeRange,
82738
- status: hour.status === "below_target" || hour.status === "above_standard" ? "below_target" : "met_target"
84801
+ status: hour.status === "below_target" || hour.status === "above_standard" ? "below_target" : "met_target",
84802
+ timezone: hour.timezone,
84803
+ categoryId: "cycle_completion",
84804
+ categoryIds: ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time", "recent_flow_red_streak"]
82739
84805
  });
82740
84806
  setActiveTab("bottlenecks");
82741
- }, []);
84807
+ }, [prefetchClipsForSelectedHour]);
82742
84808
  const handleWatchClipsFromChart = React125.useCallback((payload) => {
82743
84809
  trackCoreEvent("Hourly Output Watch Clips Clicked", buildHourlyOutputActionTrackingProps(payload));
82744
84810
  handleOpenClipsForHour({
@@ -82747,6 +84813,7 @@ var WorkspaceDetailView = ({
82747
84813
  timeRange: payload.timeRange,
82748
84814
  startTime: payload.startTime,
82749
84815
  endTime: payload.endTime,
84816
+ timezone: payload.timezone,
82750
84817
  status: payload.status,
82751
84818
  output: payload.output,
82752
84819
  target: payload.target
@@ -82769,11 +84836,6 @@ var WorkspaceDetailView = ({
82769
84836
  startDate: isFullRange ? void 0 : rangeStart,
82770
84837
  endDate: isFullRange ? void 0 : rangeEnd
82771
84838
  });
82772
- const {
82773
- isFastSlowClipFiltersEnabled,
82774
- isResolved: isFastSlowClipFiltersResolved
82775
- } = useCompanyFastSlowClipFiltersEnabled();
82776
- const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
82777
84839
  dashboardConfig?.supervisorConfig?.enabled || false;
82778
84840
  const routedLineId = lineId || selectedLineId;
82779
84841
  const latestCachedDetailedMetrics = React125.useMemo(() => {
@@ -84520,6 +86582,7 @@ var WorkspaceDetailView = ({
84520
86582
  workspaceMetrics: detailedWorkspaceMetrics || void 0,
84521
86583
  prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts : null,
84522
86584
  lowMomentsPrefetch,
86585
+ initialTimePrefetch,
84523
86586
  initialTimeFilter: pendingClipHourFilter,
84524
86587
  className: "h-[calc(100vh-10rem)]"
84525
86588
  }
@@ -94507,6 +96570,7 @@ exports.parseDateKeyToDate = parseDateKeyToDate;
94507
96570
  exports.parseS3Uri = parseS3Uri;
94508
96571
  exports.pickPreferredLineMetricsRow = pickPreferredLineMetricsRow;
94509
96572
  exports.preInitializeWorkspaceDisplayNames = preInitializeWorkspaceDisplayNames;
96573
+ exports.preInitializeWorkspaceDisplayNamesForLines = preInitializeWorkspaceDisplayNamesForLines;
94510
96574
  exports.preloadS3Video = preloadS3Video;
94511
96575
  exports.preloadS3VideoUrl = preloadS3VideoUrl;
94512
96576
  exports.preloadS3VideosUrl = preloadS3VideosUrl;
@@ -94657,3 +96721,5 @@ exports.withRegistry = withRegistry;
94657
96721
  exports.withTimezone = withTimezone;
94658
96722
  exports.workspaceHealthService = workspaceHealthService;
94659
96723
  exports.workspaceService = workspaceService;
96724
+ //# sourceMappingURL=index.js.map
96725
+ //# sourceMappingURL=index.js.map