@optifye/dashboard-core 6.9.11 → 6.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2,8 +2,8 @@ import * as React23 from 'react';
2
2
  import React23__default, { createContext, useRef, useCallback, useState, useMemo, useEffect, forwardRef, useImperativeHandle, useLayoutEffect, memo, useContext, useId, Children, isValidElement, useInsertionEffect, Fragment as Fragment$1, createElement, Component } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useRouter } from 'next/router';
5
- import { toZonedTime, formatInTimeZone } from 'date-fns-tz';
6
- import { subDays, format, parseISO, isValid, formatDistanceToNow, isFuture, isToday } from 'date-fns';
5
+ import { toZonedTime, formatInTimeZone, fromZonedTime } from 'date-fns-tz';
6
+ import { subDays, format, parseISO, isValid, addMinutes, differenceInMinutes, formatDistanceToNow, isFuture, isToday } from 'date-fns';
7
7
  import mixpanel from 'mixpanel-browser';
8
8
  import { EventEmitter } from 'events';
9
9
  import { createClient, REALTIME_SUBSCRIBE_STATES } from '@supabase/supabase-js';
@@ -2134,6 +2134,88 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2134
2134
  setCache(key, data) {
2135
2135
  this.cache.set(key, { data, timestamp: Date.now() });
2136
2136
  }
2137
+ getShiftTiming(timezone, shiftConfig) {
2138
+ const { shiftId, date } = getCurrentShift(timezone, shiftConfig);
2139
+ const dayShiftId = shiftConfig?.dayShift?.id ?? 0;
2140
+ const isDayShift = shiftId === dayShiftId;
2141
+ const defaultDayStart = "06:00";
2142
+ const defaultDayEnd = "18:00";
2143
+ const defaultNightStart = "18:00";
2144
+ const defaultNightEnd = "06:00";
2145
+ const shiftStartStr = isDayShift ? shiftConfig?.dayShift?.startTime || defaultDayStart : shiftConfig?.nightShift?.startTime || defaultNightStart;
2146
+ const shiftEndStr = isDayShift ? shiftConfig?.dayShift?.endTime || defaultDayEnd : shiftConfig?.nightShift?.endTime || defaultNightEnd;
2147
+ const shiftLabel = isDayShift ? "Day Shift" : "Night Shift";
2148
+ const parseTime = (value) => {
2149
+ const [hourPart = "0", minutePart = "0"] = value.split(":");
2150
+ const hour = Number.parseInt(hourPart, 10);
2151
+ const minute = Number.parseInt(minutePart, 10);
2152
+ return {
2153
+ hour: Number.isFinite(hour) ? hour : 0,
2154
+ minute: Number.isFinite(minute) ? minute : 0
2155
+ };
2156
+ };
2157
+ const getShiftDurationMinutes = (start, end) => {
2158
+ const startParsed = parseTime(start);
2159
+ const endParsed = parseTime(end);
2160
+ const startTotal = startParsed.hour * 60 + startParsed.minute;
2161
+ const endTotal = endParsed.hour * 60 + endParsed.minute;
2162
+ let duration = endTotal - startTotal;
2163
+ if (duration <= 0) {
2164
+ duration += 24 * 60;
2165
+ }
2166
+ return duration;
2167
+ };
2168
+ const shiftStartDate = fromZonedTime(`${date}T${shiftStartStr}:00`, timezone);
2169
+ const totalMinutes = getShiftDurationMinutes(shiftStartStr, shiftEndStr);
2170
+ const shiftEndDate = addMinutes(shiftStartDate, totalMinutes);
2171
+ const now2 = /* @__PURE__ */ new Date();
2172
+ let completedMinutes = differenceInMinutes(now2 < shiftEndDate ? now2 : shiftEndDate, shiftStartDate);
2173
+ if (completedMinutes < 0) completedMinutes = 0;
2174
+ if (completedMinutes > totalMinutes) completedMinutes = totalMinutes;
2175
+ const pendingMinutes = Math.max(0, totalMinutes - completedMinutes);
2176
+ return {
2177
+ shiftId,
2178
+ date,
2179
+ shiftLabel,
2180
+ shiftStartDate,
2181
+ shiftEndDate,
2182
+ totalMinutes,
2183
+ completedMinutes,
2184
+ pendingMinutes
2185
+ };
2186
+ }
2187
+ normalizeHourBucket(bucket) {
2188
+ if (Array.isArray(bucket)) return bucket;
2189
+ if (bucket && Array.isArray(bucket.values)) return bucket.values;
2190
+ return void 0;
2191
+ }
2192
+ normalizeOutputHourly(outputHourlyRaw) {
2193
+ return outputHourlyRaw && typeof outputHourlyRaw === "object" ? Object.fromEntries(
2194
+ Object.entries(outputHourlyRaw).map(([key, value]) => [key, this.normalizeHourBucket(value) || []])
2195
+ ) : {};
2196
+ }
2197
+ interpretUptimeValue(value) {
2198
+ if (value === null || value === void 0) return "down";
2199
+ if (typeof value === "string") {
2200
+ return value.trim().toLowerCase() === "x" ? "down" : "up";
2201
+ }
2202
+ return "up";
2203
+ }
2204
+ deriveStatusForMinute(minuteOffset, minuteDate, outputHourly, outputArray, timezone) {
2205
+ const hourKey = formatInTimeZone(minuteDate, timezone, "H");
2206
+ const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
2207
+ const hourBucket = outputHourly[hourKey];
2208
+ if (Array.isArray(hourBucket)) {
2209
+ const value = hourBucket[minuteKey];
2210
+ if (value !== void 0) {
2211
+ return this.interpretUptimeValue(value);
2212
+ }
2213
+ }
2214
+ if (minuteOffset < outputArray.length) {
2215
+ return this.interpretUptimeValue(outputArray[minuteOffset]);
2216
+ }
2217
+ return "down";
2218
+ }
2137
2219
  async getWorkspaceHealthStatus(options = {}) {
2138
2220
  const supabase = _getSupabaseInstance();
2139
2221
  if (!supabase) throw new Error("Supabase client not initialized");
@@ -2150,15 +2232,13 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2150
2232
  throw error;
2151
2233
  }
2152
2234
  const processedData = (data || []).map((item) => this.processHealthStatus(item));
2235
+ const companyId = options.companyId || data?.[0]?.company_id;
2153
2236
  let uptimeMap = /* @__PURE__ */ new Map();
2154
- if (options.companyId || data && data.length > 0) {
2155
- const companyId = options.companyId || data[0]?.company_id;
2156
- if (companyId) {
2157
- try {
2158
- uptimeMap = await this.calculateWorkspaceUptime(companyId);
2159
- } catch (error2) {
2160
- console.error("Error calculating uptime:", error2);
2161
- }
2237
+ if (companyId) {
2238
+ try {
2239
+ uptimeMap = await this.calculateWorkspaceUptime(companyId);
2240
+ } catch (error2) {
2241
+ console.error("Error calculating uptime:", error2);
2162
2242
  }
2163
2243
  }
2164
2244
  const dataWithUptime = processedData.map((workspace) => {
@@ -2174,16 +2254,29 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2174
2254
  });
2175
2255
  let filteredData = dataWithUptime;
2176
2256
  try {
2177
- const { data: enabledWorkspaces, error: workspaceError } = await supabase.from("workspaces").select("workspace_id, display_name").eq("enable", true);
2257
+ const enabledQuery = supabase.from("workspaces").select("workspace_id, display_name, line_id").eq("enable", true);
2258
+ if (options.lineId) {
2259
+ enabledQuery.eq("line_id", options.lineId);
2260
+ }
2261
+ const { data: enabledWorkspaces, error: workspaceError } = await enabledQuery;
2178
2262
  if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length > 0) {
2179
- const enabledWorkspaceNames = /* @__PURE__ */ new Set();
2263
+ const enabledByLineAndId = /* @__PURE__ */ new Set();
2264
+ const enabledByLineAndName = /* @__PURE__ */ new Set();
2180
2265
  enabledWorkspaces.forEach((w) => {
2181
- if (w.workspace_id) enabledWorkspaceNames.add(w.workspace_id);
2182
- if (w.display_name) enabledWorkspaceNames.add(w.display_name);
2266
+ const lineKey = w.line_id ? String(w.line_id) : "";
2267
+ const idKey = w.workspace_id ? String(w.workspace_id) : "";
2268
+ const nameKey = w.display_name ? String(w.display_name) : "";
2269
+ if (lineKey && idKey) enabledByLineAndId.add(`${lineKey}::${idKey}`);
2270
+ if (lineKey && nameKey) enabledByLineAndName.add(`${lineKey}::${nameKey}`);
2183
2271
  });
2184
2272
  filteredData = filteredData.filter((item) => {
2185
- const displayName = item.workspace_display_name || "";
2186
- return enabledWorkspaceNames.has(displayName);
2273
+ const lineKey = item.line_id ? String(item.line_id) : "";
2274
+ if (!lineKey) return false;
2275
+ const idKey = item.workspace_id ? `${lineKey}::${item.workspace_id}` : "";
2276
+ const nameKey = item.workspace_display_name ? `${lineKey}::${item.workspace_display_name}` : "";
2277
+ if (idKey && enabledByLineAndId.has(idKey)) return true;
2278
+ if (nameKey && enabledByLineAndName.has(nameKey)) return true;
2279
+ return false;
2187
2280
  });
2188
2281
  } else if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length === 0) {
2189
2282
  return [];
@@ -2221,6 +2314,127 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2221
2314
  }
2222
2315
  return filteredData;
2223
2316
  }
2317
+ async getWorkspaceUptimeTimeline(workspaceId, companyId) {
2318
+ if (!workspaceId) {
2319
+ throw new Error("workspaceId is required to fetch uptime timeline");
2320
+ }
2321
+ if (!companyId) {
2322
+ throw new Error("companyId is required to fetch uptime timeline");
2323
+ }
2324
+ const supabase = _getSupabaseInstance();
2325
+ if (!supabase) throw new Error("Supabase client not initialized");
2326
+ const dashboardConfig = _getDashboardConfigInstance();
2327
+ const timezone = dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
2328
+ const shiftConfig = dashboardConfig?.shiftConfig;
2329
+ const {
2330
+ shiftId,
2331
+ shiftLabel,
2332
+ shiftStartDate,
2333
+ shiftEndDate,
2334
+ totalMinutes,
2335
+ completedMinutes,
2336
+ pendingMinutes,
2337
+ date
2338
+ } = this.getShiftTiming(timezone, shiftConfig);
2339
+ const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
2340
+ const { data, error } = await supabase.from(tableName).select("workspace_id, output_hourly, output_array").eq("workspace_id", workspaceId).eq("date", date).eq("shift_id", shiftId).limit(1);
2341
+ if (error) {
2342
+ console.error("Error fetching uptime timeline metrics:", error);
2343
+ throw error;
2344
+ }
2345
+ const record = Array.isArray(data) && data.length > 0 ? data[0] : null;
2346
+ const outputHourly = this.normalizeOutputHourly(record?.output_hourly || {});
2347
+ const outputArray = Array.isArray(record?.output_array) ? record.output_array : [];
2348
+ const points = [];
2349
+ let uptimeMinutes = 0;
2350
+ let downtimeMinutes = 0;
2351
+ const MIN_DOWNTIME_MINUTES = 2;
2352
+ for (let minuteIndex = 0; minuteIndex < totalMinutes; minuteIndex++) {
2353
+ const minuteDate = addMinutes(shiftStartDate, minuteIndex);
2354
+ const timestamp = formatInTimeZone(minuteDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
2355
+ let status;
2356
+ if (minuteIndex < completedMinutes) {
2357
+ status = this.deriveStatusForMinute(
2358
+ minuteIndex,
2359
+ minuteDate,
2360
+ outputHourly,
2361
+ outputArray,
2362
+ timezone
2363
+ );
2364
+ if (status === "up") {
2365
+ uptimeMinutes += 1;
2366
+ } else {
2367
+ downtimeMinutes += 1;
2368
+ }
2369
+ } else {
2370
+ status = "pending";
2371
+ }
2372
+ points.push({
2373
+ minuteIndex,
2374
+ timestamp,
2375
+ status
2376
+ });
2377
+ }
2378
+ const downtimeSegments = [];
2379
+ let currentSegmentStart = null;
2380
+ const pushSegment = (startIndex, endIndex) => {
2381
+ if (endIndex <= startIndex) return;
2382
+ const segmentStartDate = addMinutes(shiftStartDate, startIndex);
2383
+ const segmentEndDate = addMinutes(shiftStartDate, endIndex);
2384
+ downtimeSegments.push({
2385
+ startMinuteIndex: startIndex,
2386
+ endMinuteIndex: endIndex,
2387
+ startTime: formatInTimeZone(segmentStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2388
+ endTime: formatInTimeZone(segmentEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2389
+ durationMinutes: endIndex - startIndex
2390
+ });
2391
+ };
2392
+ for (let i = 0; i < completedMinutes; i++) {
2393
+ const point = points[i];
2394
+ if (point.status === "down") {
2395
+ if (currentSegmentStart === null) {
2396
+ currentSegmentStart = i;
2397
+ }
2398
+ } else if (currentSegmentStart !== null) {
2399
+ pushSegment(currentSegmentStart, i);
2400
+ currentSegmentStart = null;
2401
+ }
2402
+ }
2403
+ if (currentSegmentStart !== null) {
2404
+ pushSegment(currentSegmentStart, completedMinutes);
2405
+ }
2406
+ const filteredSegments = [];
2407
+ downtimeSegments.forEach((segment) => {
2408
+ if (segment.durationMinutes >= MIN_DOWNTIME_MINUTES) {
2409
+ filteredSegments.push(segment);
2410
+ } else {
2411
+ for (let i = segment.startMinuteIndex; i < segment.endMinuteIndex; i++) {
2412
+ if (points[i] && points[i].status === "down") {
2413
+ points[i].status = "up";
2414
+ downtimeMinutes = Math.max(0, downtimeMinutes - 1);
2415
+ uptimeMinutes += 1;
2416
+ }
2417
+ }
2418
+ }
2419
+ });
2420
+ const completedWindow = Math.max(1, uptimeMinutes + downtimeMinutes);
2421
+ const uptimePercentage = completedMinutes > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
2422
+ return {
2423
+ shiftId,
2424
+ shiftLabel,
2425
+ shiftStart: formatInTimeZone(shiftStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2426
+ shiftEnd: formatInTimeZone(shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2427
+ totalMinutes,
2428
+ completedMinutes,
2429
+ uptimeMinutes,
2430
+ downtimeMinutes,
2431
+ pendingMinutes,
2432
+ uptimePercentage,
2433
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2434
+ points,
2435
+ downtimeSegments: filteredSegments
2436
+ };
2437
+ }
2224
2438
  async getWorkspaceHealthById(workspaceId) {
2225
2439
  const cacheKey = `health-${workspaceId}`;
2226
2440
  const cached = this.getFromCache(cacheKey);
@@ -2358,145 +2572,63 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2358
2572
  const dashboardConfig = _getDashboardConfigInstance();
2359
2573
  const timezone = dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
2360
2574
  const shiftConfig = dashboardConfig?.shiftConfig;
2361
- const currentShiftInfo = getCurrentShift(timezone, shiftConfig);
2362
- const currentDate = currentShiftInfo.date;
2363
- const currentShiftId = currentShiftInfo.shiftId;
2364
- const now2 = /* @__PURE__ */ new Date();
2365
- const currentHour = now2.getHours();
2366
- const currentMinute = now2.getMinutes();
2367
- const dayShiftStart = shiftConfig?.dayShift?.startTime || "08:00";
2368
- const dayShiftEnd = shiftConfig?.dayShift?.endTime || "19:30";
2369
- const nightShiftStart = shiftConfig?.nightShift?.startTime || "19:30";
2370
- const nightShiftEnd = shiftConfig?.nightShift?.endTime || "06:00";
2371
- const parseShiftTime = (timeStr) => {
2372
- const [hour, minute] = timeStr.split(":").map(Number);
2373
- return { hour, minute };
2374
- };
2375
- let shiftStart, shiftEnd;
2376
- if (currentShiftId === 0) {
2377
- shiftStart = parseShiftTime(dayShiftStart);
2378
- shiftEnd = parseShiftTime(dayShiftEnd);
2379
- } else {
2380
- shiftStart = parseShiftTime(nightShiftStart);
2381
- shiftEnd = parseShiftTime(nightShiftEnd);
2382
- }
2383
- let elapsedMinutes = 0;
2384
- if (currentShiftId === 0) {
2385
- const shiftStartTotalMinutes = shiftStart.hour * 60 + shiftStart.minute;
2386
- const currentTotalMinutes = currentHour * 60 + currentMinute;
2387
- const shiftEndTotalMinutes = shiftEnd.hour * 60 + shiftEnd.minute;
2388
- if (currentTotalMinutes >= shiftStartTotalMinutes && currentTotalMinutes < shiftEndTotalMinutes) {
2389
- elapsedMinutes = currentTotalMinutes - shiftStartTotalMinutes;
2390
- } else if (currentTotalMinutes >= shiftEndTotalMinutes) {
2391
- elapsedMinutes = shiftEndTotalMinutes - shiftStartTotalMinutes;
2392
- }
2393
- } else {
2394
- const shiftStartTotalMinutes = shiftStart.hour * 60 + shiftStart.minute;
2395
- const currentTotalMinutes = currentHour * 60 + currentMinute;
2396
- const shiftEndTotalMinutes = shiftEnd.hour * 60 + shiftEnd.minute;
2397
- if (currentHour >= shiftStart.hour || currentHour < shiftEnd.hour) {
2398
- if (currentHour >= shiftStart.hour) {
2399
- elapsedMinutes = currentTotalMinutes - shiftStartTotalMinutes;
2400
- } else {
2401
- elapsedMinutes = 24 * 60 - shiftStartTotalMinutes + currentTotalMinutes;
2402
- }
2403
- if (currentHour >= shiftEnd.hour && currentTotalMinutes >= shiftEndTotalMinutes) {
2404
- const totalShiftMinutes = 24 * 60 - shiftStartTotalMinutes + shiftEndTotalMinutes;
2405
- elapsedMinutes = Math.min(elapsedMinutes, totalShiftMinutes);
2406
- }
2407
- }
2408
- }
2575
+ const {
2576
+ shiftId,
2577
+ date,
2578
+ shiftStartDate,
2579
+ completedMinutes
2580
+ } = this.getShiftTiming(timezone, shiftConfig);
2409
2581
  const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
2410
2582
  try {
2411
- const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly").eq("date", currentDate).eq("shift_id", currentShiftId);
2583
+ const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array").eq("date", date).eq("shift_id", shiftId);
2412
2584
  if (error) {
2413
2585
  console.error("Error fetching performance metrics:", error);
2414
2586
  return /* @__PURE__ */ new Map();
2415
2587
  }
2416
2588
  const uptimeMap = /* @__PURE__ */ new Map();
2417
2589
  for (const record of queryData || []) {
2418
- let actualMinutes = 0;
2419
- let totalPossibleMinutes = 0;
2420
- const outputHourly = record.output_hourly || {};
2421
- const hoursElapsed = Math.ceil(elapsedMinutes / 60);
2422
- for (let hourIndex = 0; hourIndex < hoursElapsed; hourIndex++) {
2423
- const actualHour = (shiftStart.hour + hourIndex) % 24;
2424
- let hourData = [];
2425
- let minutesInThisHour = 60;
2426
- if (shiftStart.minute > 0) {
2427
- if (hourIndex === 0) {
2428
- const firstHourData = outputHourly[actualHour.toString()] || [];
2429
- const nextHour = (actualHour + 1) % 24;
2430
- const nextHourData = outputHourly[nextHour.toString()] || [];
2431
- hourData = [
2432
- ...Array.isArray(firstHourData) ? firstHourData.slice(shiftStart.minute) : [],
2433
- ...Array.isArray(nextHourData) ? nextHourData.slice(0, shiftStart.minute) : []
2434
- ];
2435
- } else if (hourIndex < hoursElapsed - 1) {
2436
- const currentHourData = outputHourly[actualHour.toString()] || [];
2437
- const nextHour = (actualHour + 1) % 24;
2438
- const nextHourData = outputHourly[nextHour.toString()] || [];
2439
- hourData = [
2440
- ...Array.isArray(currentHourData) ? currentHourData.slice(shiftStart.minute) : [],
2441
- ...Array.isArray(nextHourData) ? nextHourData.slice(0, shiftStart.minute) : []
2442
- ];
2443
- } else {
2444
- const isLastHourPartial = hourIndex === hoursElapsed - 1 && elapsedMinutes % 60 > 0;
2445
- if (isLastHourPartial) {
2446
- minutesInThisHour = elapsedMinutes % 60;
2447
- const currentHourData = outputHourly[actualHour.toString()] || [];
2448
- if (shiftStart.minute > 0) {
2449
- const nextHour = (actualHour + 1) % 24;
2450
- const nextHourData = outputHourly[nextHour.toString()] || [];
2451
- const firstPart = Array.isArray(currentHourData) ? currentHourData.slice(shiftStart.minute) : [];
2452
- const secondPart = Array.isArray(nextHourData) ? nextHourData.slice(0, Math.min(shiftStart.minute, minutesInThisHour)) : [];
2453
- hourData = [...firstPart, ...secondPart].slice(0, minutesInThisHour);
2454
- } else {
2455
- hourData = Array.isArray(currentHourData) ? currentHourData.slice(0, minutesInThisHour) : [];
2456
- }
2457
- } else {
2458
- const currentHourData = outputHourly[actualHour.toString()] || [];
2459
- const nextHour = (actualHour + 1) % 24;
2460
- const nextHourData = outputHourly[nextHour.toString()] || [];
2461
- hourData = [
2462
- ...Array.isArray(currentHourData) ? currentHourData.slice(shiftStart.minute) : [],
2463
- ...Array.isArray(nextHourData) ? nextHourData.slice(0, shiftStart.minute) : []
2464
- ];
2465
- }
2466
- }
2590
+ const outputHourly = this.normalizeOutputHourly(record.output_hourly || {});
2591
+ const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
2592
+ let uptimeMinutes = 0;
2593
+ let downtimeMinutes = 0;
2594
+ const MIN_DOWNTIME_MINUTES = 2;
2595
+ let currentDownRun = 0;
2596
+ for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex++) {
2597
+ const minuteDate = addMinutes(shiftStartDate, minuteIndex);
2598
+ const status = this.deriveStatusForMinute(
2599
+ minuteIndex,
2600
+ minuteDate,
2601
+ outputHourly,
2602
+ outputArray,
2603
+ timezone
2604
+ );
2605
+ if (status === "down") {
2606
+ currentDownRun += 1;
2467
2607
  } else {
2468
- hourData = outputHourly[actualHour.toString()] || [];
2469
- if (hourIndex === hoursElapsed - 1) {
2470
- const remainingMinutes = elapsedMinutes % 60;
2471
- if (remainingMinutes > 0) {
2472
- minutesInThisHour = remainingMinutes;
2473
- hourData = Array.isArray(hourData) ? hourData.slice(0, minutesInThisHour) : [];
2608
+ if (currentDownRun > 0) {
2609
+ if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
2610
+ downtimeMinutes += currentDownRun;
2611
+ } else {
2612
+ uptimeMinutes += currentDownRun;
2474
2613
  }
2614
+ currentDownRun = 0;
2475
2615
  }
2616
+ uptimeMinutes += 1;
2476
2617
  }
2477
- const hasXArchitecture = Array.isArray(hourData) && hourData.some((val) => val === "x");
2478
- if (hasXArchitecture) {
2479
- const xCount = hourData.filter((val) => val === "x").length;
2480
- if (xCount <= 2) {
2481
- actualMinutes += minutesInThisHour;
2482
- } else {
2483
- const uptimeCount = hourData.filter((val) => val !== "x").length;
2484
- actualMinutes += Math.min(uptimeCount, minutesInThisHour);
2485
- }
2618
+ }
2619
+ if (currentDownRun > 0) {
2620
+ if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
2621
+ downtimeMinutes += currentDownRun;
2486
2622
  } else {
2487
- const arrayLength = Array.isArray(hourData) ? hourData.length : 0;
2488
- if (arrayLength >= Math.min(58, minutesInThisHour - 2)) {
2489
- actualMinutes += minutesInThisHour;
2490
- } else {
2491
- actualMinutes += Math.min(arrayLength, minutesInThisHour);
2492
- }
2623
+ uptimeMinutes += currentDownRun;
2493
2624
  }
2494
- totalPossibleMinutes += minutesInThisHour;
2625
+ currentDownRun = 0;
2495
2626
  }
2496
- const percentage = totalPossibleMinutes > 0 ? Math.round(actualMinutes / totalPossibleMinutes * 1e3) / 10 : 100;
2627
+ const completedWindow = uptimeMinutes + downtimeMinutes;
2628
+ const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
2497
2629
  uptimeMap.set(record.workspace_id, {
2498
- expectedMinutes: totalPossibleMinutes,
2499
- actualMinutes,
2630
+ expectedMinutes: completedMinutes,
2631
+ actualMinutes: uptimeMinutes,
2500
2632
  percentage,
2501
2633
  lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
2502
2634
  });
@@ -2896,8 +3028,8 @@ var AuthService = class {
2896
3028
  "Authorization": `Bearer ${accessToken}`,
2897
3029
  "Content-Type": "application/json"
2898
3030
  },
2899
- timeout: 3e5,
2900
- // 5 minutes
3031
+ timeout: 1e4,
3032
+ // 10 seconds
2901
3033
  retries: 1,
2902
3034
  silentErrors: false
2903
3035
  // We want to know about auth errors
@@ -2934,8 +3066,8 @@ var AuthService = class {
2934
3066
  "Authorization": `Bearer ${accessToken}`,
2935
3067
  "Content-Type": "application/json"
2936
3068
  },
2937
- timeout: 3e5,
2938
- // 5 minutes
3069
+ timeout: 1e4,
3070
+ // 10 seconds
2939
3071
  retries: 2,
2940
3072
  // More retries for validation
2941
3073
  silentErrors: true,
@@ -2967,8 +3099,8 @@ var AuthService = class {
2967
3099
  "Authorization": `Bearer ${accessToken}`,
2968
3100
  "Content-Type": "application/json"
2969
3101
  },
2970
- timeout: 3e5,
2971
- // 5 minutes
3102
+ timeout: 1e4,
3103
+ // 10 seconds
2972
3104
  retries: 1,
2973
3105
  silentErrors: false
2974
3106
  }
@@ -10868,6 +11000,65 @@ var useWorkspaceHealthStatus = (workspaceId) => {
10868
11000
  refetch: fetchHealthStatus
10869
11001
  };
10870
11002
  };
11003
+ var useWorkspaceUptimeTimeline = (options) => {
11004
+ const {
11005
+ workspaceId,
11006
+ companyId,
11007
+ enabled = true,
11008
+ refreshInterval
11009
+ } = options;
11010
+ const [timeline, setTimeline] = useState(null);
11011
+ const [loading, setLoading] = useState(false);
11012
+ const [error, setError] = useState(null);
11013
+ const isFetchingRef = useRef(false);
11014
+ const intervalRef = useRef(null);
11015
+ const fetchTimeline = useCallback(async () => {
11016
+ if (!enabled) return;
11017
+ if (!workspaceId || !companyId) {
11018
+ setTimeline(null);
11019
+ return;
11020
+ }
11021
+ if (isFetchingRef.current) return;
11022
+ try {
11023
+ isFetchingRef.current = true;
11024
+ setLoading(true);
11025
+ setError(null);
11026
+ const data = await workspaceHealthService.getWorkspaceUptimeTimeline(
11027
+ workspaceId,
11028
+ companyId
11029
+ );
11030
+ setTimeline(data);
11031
+ } catch (err) {
11032
+ console.error("[useWorkspaceUptimeTimeline] Failed to fetch timeline:", err);
11033
+ setError({ message: err?.message || "Failed to load uptime timeline", code: err?.code || "FETCH_ERROR" });
11034
+ } finally {
11035
+ setLoading(false);
11036
+ isFetchingRef.current = false;
11037
+ }
11038
+ }, [enabled, workspaceId, companyId]);
11039
+ useEffect(() => {
11040
+ fetchTimeline();
11041
+ }, [fetchTimeline]);
11042
+ useEffect(() => {
11043
+ if (!refreshInterval || refreshInterval <= 0 || !enabled) {
11044
+ return;
11045
+ }
11046
+ intervalRef.current = setInterval(() => {
11047
+ fetchTimeline();
11048
+ }, refreshInterval);
11049
+ return () => {
11050
+ if (intervalRef.current) {
11051
+ clearInterval(intervalRef.current);
11052
+ }
11053
+ };
11054
+ }, [refreshInterval, enabled, fetchTimeline]);
11055
+ return {
11056
+ timeline,
11057
+ loading,
11058
+ error,
11059
+ refetch: fetchTimeline
11060
+ };
11061
+ };
10871
11062
  function useDateFormatter() {
10872
11063
  const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
10873
11064
  const formatDate = useCallback(
@@ -10883,7 +11074,7 @@ function useDateFormatter() {
10883
11074
  },
10884
11075
  [defaultTimezone, defaultLocale, dateFormatOptions]
10885
11076
  );
10886
- const formatTime3 = useCallback(
11077
+ const formatTime5 = useCallback(
10887
11078
  (date, formatString) => {
10888
11079
  const dateObj = typeof date === "string" ? parseISO(date) : date;
10889
11080
  if (!isValid(dateObj)) return "Invalid Time";
@@ -10914,7 +11105,7 @@ function useDateFormatter() {
10914
11105
  }, []);
10915
11106
  return {
10916
11107
  formatDate,
10917
- formatTime: formatTime3,
11108
+ formatTime: formatTime5,
10918
11109
  formatDateTime,
10919
11110
  getNow,
10920
11111
  timezone: defaultTimezone || "UTC",
@@ -23900,7 +24091,7 @@ var HourlyOutputChartComponent = ({
23900
24091
  endHour = Math.floor(endDecimalHour) % 24;
23901
24092
  endMinute = Math.round(endDecimalHour % 1 * 60);
23902
24093
  }
23903
- const formatTime3 = (h, m) => {
24094
+ const formatTime5 = (h, m) => {
23904
24095
  const period = h >= 12 ? "PM" : "AM";
23905
24096
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23906
24097
  if (m === 0) {
@@ -23908,9 +24099,9 @@ var HourlyOutputChartComponent = ({
23908
24099
  }
23909
24100
  return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
23910
24101
  };
23911
- return `${formatTime3(startHour, startMinute)}-${formatTime3(endHour, endMinute)}`;
24102
+ return `${formatTime5(startHour, startMinute)}-${formatTime5(endHour, endMinute)}`;
23912
24103
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23913
- const formatTimeRange = React23__default.useCallback((hourIndex) => {
24104
+ const formatTimeRange2 = React23__default.useCallback((hourIndex) => {
23914
24105
  const isLastHour = hourIndex === SHIFT_DURATION - 1;
23915
24106
  const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
23916
24107
  const startHour = Math.floor(startDecimalHour) % 24;
@@ -23924,12 +24115,12 @@ var HourlyOutputChartComponent = ({
23924
24115
  endHour = Math.floor(endDecimalHour) % 24;
23925
24116
  endMinute = Math.round(endDecimalHour % 1 * 60);
23926
24117
  }
23927
- const formatTime3 = (h, m) => {
24118
+ const formatTime5 = (h, m) => {
23928
24119
  const period = h >= 12 ? "PM" : "AM";
23929
24120
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23930
24121
  return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
23931
24122
  };
23932
- return `${formatTime3(startHour, startMinute)} - ${formatTime3(endHour, endMinute)}`;
24123
+ return `${formatTime5(startHour, startMinute)} - ${formatTime5(endHour, endMinute)}`;
23933
24124
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23934
24125
  const chartData = React23__default.useMemo(() => {
23935
24126
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
@@ -23990,7 +24181,7 @@ var HourlyOutputChartComponent = ({
23990
24181
  idleMinutes = idleArray.filter((val) => val === "1" || val === 1).length;
23991
24182
  return {
23992
24183
  hour: formatHour(i),
23993
- timeRange: formatTimeRange(i),
24184
+ timeRange: formatTimeRange2(i),
23994
24185
  output: animatedData[i] || 0,
23995
24186
  originalOutput: data[i] || 0,
23996
24187
  // Keep original data for labels
@@ -23999,7 +24190,7 @@ var HourlyOutputChartComponent = ({
23999
24190
  idleArray
24000
24191
  };
24001
24192
  });
24002
- }, [animatedData, data, pphThreshold, idleTimeHourly, shiftStartTime.hour, shiftStartTime.minute, shiftEndTime, formatHour, formatTimeRange, SHIFT_DURATION]);
24193
+ }, [animatedData, data, pphThreshold, idleTimeHourly, shiftStartTime.hour, shiftStartTime.minute, shiftEndTime, formatHour, formatTimeRange2, SHIFT_DURATION]);
24003
24194
  const IdleBar = React23__default.useMemo(() => {
24004
24195
  if (!idleBarState.visible) return null;
24005
24196
  return /* @__PURE__ */ jsx(
@@ -25100,7 +25291,7 @@ var SOPComplianceChart = ({
25100
25291
  }
25101
25292
  };
25102
25293
  }, [data, animateToNewData, mockData]);
25103
- const formatTime3 = (minuteIndex) => {
25294
+ const formatTime5 = (minuteIndex) => {
25104
25295
  const totalMinutes = shiftStartHour * 60 + minuteIndex;
25105
25296
  const hours = Math.floor(totalMinutes / 60) % 24;
25106
25297
  const minutes = totalMinutes % 60;
@@ -25112,7 +25303,7 @@ var SOPComplianceChart = ({
25112
25303
  const hasDataForMinute = index < animatedData.length - 10;
25113
25304
  return {
25114
25305
  minute: index,
25115
- time: formatTime3(index),
25306
+ time: formatTime5(index),
25116
25307
  compliance: hasDataForMinute ? animatedData[index] : null
25117
25308
  };
25118
25309
  });
@@ -25546,7 +25737,7 @@ var DateTimeDisplay = ({
25546
25737
  const {
25547
25738
  defaultTimezone
25548
25739
  } = useDateTimeConfig();
25549
- const { formatDate, formatTime: formatTime3 } = useDateFormatter();
25740
+ const { formatDate, formatTime: formatTime5 } = useDateFormatter();
25550
25741
  const [now2, setNow] = useState(() => getCurrentTimeInZone(defaultTimezone || "UTC"));
25551
25742
  useEffect(() => {
25552
25743
  const timerId = setInterval(() => {
@@ -25558,7 +25749,7 @@ var DateTimeDisplay = ({
25558
25749
  return null;
25559
25750
  }
25560
25751
  const formattedDate = showDate ? formatDate(now2) : "";
25561
- const formattedTime = showTime ? formatTime3(now2) : "";
25752
+ const formattedTime = showTime ? formatTime5(now2) : "";
25562
25753
  return /* @__PURE__ */ jsxs("div", { className: clsx_default("flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-300", className), children: [
25563
25754
  showDate && /* @__PURE__ */ jsx("span", { className: "date-display", "aria-label": `Current date: ${formattedDate}`, children: formattedDate }),
25564
25755
  showDate && showTime && formattedDate && formattedTime && /* @__PURE__ */ jsx("span", { className: "separator", "aria-hidden": "true", children: "|" }),
@@ -25722,7 +25913,7 @@ var BreakNotificationPopup = ({
25722
25913
  const handlePrevious = () => {
25723
25914
  setCurrentIndex((prev) => (prev - 1 + visibleBreaks.length) % visibleBreaks.length);
25724
25915
  };
25725
- const formatTime3 = (minutes) => {
25916
+ const formatTime5 = (minutes) => {
25726
25917
  const hours = Math.floor(minutes / 60);
25727
25918
  const mins = minutes % 60;
25728
25919
  if (hours > 0) {
@@ -25796,9 +25987,9 @@ var BreakNotificationPopup = ({
25796
25987
  formatTo12Hour(currentBreak.endTime)
25797
25988
  ] }),
25798
25989
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 mb-2", children: [
25799
- formatTime3(currentBreak.elapsedMinutes),
25990
+ formatTime5(currentBreak.elapsedMinutes),
25800
25991
  " elapsed of ",
25801
- formatTime3(currentBreak.duration),
25992
+ formatTime5(currentBreak.duration),
25802
25993
  " total"
25803
25994
  ] }),
25804
25995
  /* @__PURE__ */ jsx("div", { className: "w-full bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsx(
@@ -26102,64 +26293,208 @@ var getSeverityColor = (severity) => {
26102
26293
  return "bg-gray-500";
26103
26294
  }
26104
26295
  };
26105
- var PlayPauseIndicator = ({
26106
- show,
26296
+ var formatTime2 = (seconds) => {
26297
+ if (!seconds || isNaN(seconds)) return "0:00";
26298
+ const h = Math.floor(seconds / 3600);
26299
+ const m = Math.floor(seconds % 3600 / 60);
26300
+ const s = Math.floor(seconds % 60);
26301
+ if (h > 0) {
26302
+ return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
26303
+ }
26304
+ return `${m}:${s.toString().padStart(2, "0")}`;
26305
+ };
26306
+ var VideoControls = ({
26107
26307
  isPlaying,
26108
- duration = 600
26308
+ currentTime,
26309
+ duration,
26310
+ buffered,
26311
+ showControls,
26312
+ controlsPinned = false,
26313
+ onTogglePinControls,
26314
+ playbackRate = 1,
26315
+ onPlayPause,
26316
+ onSeek,
26317
+ onSeekStart,
26318
+ onSeekEnd,
26319
+ onToggleFullscreen,
26320
+ onPlaybackRateChange,
26321
+ className = ""
26109
26322
  }) => {
26110
- const [isVisible, setIsVisible] = useState(false);
26111
- const [isFading, setIsFading] = useState(false);
26323
+ const [isDragging, setIsDragging] = useState(false);
26324
+ const [dragTime, setDragTime] = useState(0);
26325
+ const [isHoveringProgressBar, setIsHoveringProgressBar] = useState(false);
26326
+ const [showSpeedMenu, setShowSpeedMenu] = useState(false);
26327
+ const speedMenuRef = useRef(null);
26328
+ const progressColor = "#4b5563";
26329
+ const controlsVisible = showControls || controlsPinned;
26330
+ const getPercentage = (current, total) => {
26331
+ if (!total || total === 0) return 0;
26332
+ return Math.min(Math.max(current / total * 100, 0), 100);
26333
+ };
26334
+ const handleSeekChange = (e) => {
26335
+ const newTime = parseFloat(e.target.value);
26336
+ setDragTime(newTime);
26337
+ onSeek(newTime);
26338
+ };
26339
+ const handleSeekStart = () => {
26340
+ setIsDragging(true);
26341
+ setDragTime(currentTime);
26342
+ onSeekStart?.();
26343
+ };
26344
+ const handleSeekEnd = () => {
26345
+ setIsDragging(false);
26346
+ onSeekEnd?.();
26347
+ };
26112
26348
  useEffect(() => {
26113
- if (show) {
26114
- setIsVisible(true);
26115
- setIsFading(false);
26116
- const fadeTimer = setTimeout(() => {
26117
- setIsFading(true);
26118
- }, 100);
26119
- const hideTimer = setTimeout(() => {
26120
- setIsVisible(false);
26121
- setIsFading(false);
26122
- }, duration);
26123
- return () => {
26124
- clearTimeout(fadeTimer);
26125
- clearTimeout(hideTimer);
26126
- };
26127
- }
26128
- }, [show, duration]);
26129
- if (!isVisible) return null;
26130
- return /* @__PURE__ */ jsx(
26349
+ const handleClickOutside = (event) => {
26350
+ if (speedMenuRef.current && !speedMenuRef.current.contains(event.target)) {
26351
+ setShowSpeedMenu(false);
26352
+ }
26353
+ };
26354
+ document.addEventListener("mousedown", handleClickOutside);
26355
+ return () => {
26356
+ document.removeEventListener("mousedown", handleClickOutside);
26357
+ };
26358
+ }, []);
26359
+ const displayTime = isDragging ? dragTime : currentTime;
26360
+ const progressPercent = getPercentage(displayTime, duration);
26361
+ const bufferedPercent = getPercentage(buffered, duration);
26362
+ return /* @__PURE__ */ jsxs(
26131
26363
  "div",
26132
26364
  {
26133
- className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
26134
- style: {
26135
- opacity: isFading ? 0 : 1,
26136
- transition: `opacity ${duration - 100}ms ease-out`
26137
- },
26138
- children: /* @__PURE__ */ jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
26139
- // Play icon (triangle)
26140
- /* @__PURE__ */ jsx(
26141
- "svg",
26142
- {
26143
- xmlns: "http://www.w3.org/2000/svg",
26144
- viewBox: "0 0 24 24",
26145
- fill: "white",
26146
- className: "w-16 h-16",
26147
- children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
26148
- }
26149
- )
26150
- ) : (
26151
- // Pause icon (two bars)
26152
- /* @__PURE__ */ jsx(
26153
- "svg",
26365
+ className: `absolute bottom-0 left-0 right-0 px-3 pb-3 pt-12 bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-opacity duration-300 ${controlsVisible ? "opacity-100" : "opacity-0 pointer-events-none"} ${className}`,
26366
+ style: { touchAction: "none" },
26367
+ children: [
26368
+ /* @__PURE__ */ jsxs(
26369
+ "div",
26154
26370
  {
26155
- xmlns: "http://www.w3.org/2000/svg",
26156
- viewBox: "0 0 24 24",
26157
- fill: "white",
26158
- className: "w-16 h-16",
26159
- children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
26371
+ className: "relative h-1 mb-4 group cursor-pointer",
26372
+ onMouseEnter: () => setIsHoveringProgressBar(true),
26373
+ onMouseLeave: () => setIsHoveringProgressBar(false),
26374
+ children: [
26375
+ /* @__PURE__ */ jsx("div", { className: "absolute -top-2 -bottom-2 left-0 right-0 z-20" }),
26376
+ /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 bottom-0 bg-white/20 rounded-full overflow-hidden z-0", children: /* @__PURE__ */ jsx(
26377
+ "div",
26378
+ {
26379
+ className: "absolute top-0 left-0 bottom-0 bg-white/40 transition-all duration-200",
26380
+ style: { width: `${bufferedPercent}%` }
26381
+ }
26382
+ ) }),
26383
+ /* @__PURE__ */ jsx(
26384
+ "div",
26385
+ {
26386
+ className: "absolute top-0 left-0 bottom-0 bg-[#007bff] transition-all duration-75 z-10",
26387
+ style: { width: `${progressPercent}%`, backgroundColor: progressColor },
26388
+ children: /* @__PURE__ */ jsx(
26389
+ "div",
26390
+ {
26391
+ className: `absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 w-3 h-3 rounded-full shadow transform transition-transform duration-200 ${isHoveringProgressBar || isDragging ? "scale-100" : "scale-0"}`,
26392
+ style: { backgroundColor: progressColor }
26393
+ }
26394
+ )
26395
+ }
26396
+ ),
26397
+ /* @__PURE__ */ jsx(
26398
+ "input",
26399
+ {
26400
+ type: "range",
26401
+ min: "0",
26402
+ max: duration || 100,
26403
+ step: "0.1",
26404
+ value: displayTime,
26405
+ onChange: handleSeekChange,
26406
+ onMouseDown: handleSeekStart,
26407
+ onMouseUp: handleSeekEnd,
26408
+ onTouchStart: handleSeekStart,
26409
+ onTouchEnd: handleSeekEnd,
26410
+ className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-30 margin-0 padding-0"
26411
+ }
26412
+ )
26413
+ ]
26160
26414
  }
26161
- )
26162
- ) })
26415
+ ),
26416
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-white", children: [
26417
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
26418
+ /* @__PURE__ */ jsx(
26419
+ "button",
26420
+ {
26421
+ onClick: (e) => {
26422
+ e.stopPropagation();
26423
+ onPlayPause();
26424
+ },
26425
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26426
+ "aria-label": isPlaying ? "Pause" : "Play",
26427
+ children: isPlaying ? /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) }) : /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" }) })
26428
+ }
26429
+ ),
26430
+ /* @__PURE__ */ jsxs("div", { className: "text-xs font-medium font-sans", children: [
26431
+ /* @__PURE__ */ jsx("span", { children: formatTime2(displayTime) }),
26432
+ /* @__PURE__ */ jsx("span", { className: "mx-1 text-white/70", children: "/" }),
26433
+ /* @__PURE__ */ jsx("span", { className: "text-white/70", children: formatTime2(duration) })
26434
+ ] })
26435
+ ] }),
26436
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
26437
+ onTogglePinControls && /* @__PURE__ */ jsx(
26438
+ "button",
26439
+ {
26440
+ onClick: (e) => {
26441
+ e.stopPropagation();
26442
+ onTogglePinControls();
26443
+ },
26444
+ className: `transition-colors focus:outline-none ${controlsPinned ? "text-[#007bff]" : "hover:text-[#007bff]"}`,
26445
+ "aria-label": controlsPinned ? "Unpin controls" : "Pin controls",
26446
+ title: controlsPinned ? "Unpin controls" : "Pin controls",
26447
+ children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: controlsPinned ? /* @__PURE__ */ jsx("path", { d: "M9 3h6l-1 7h3v2h-4.5l-.5 4.5-2 1L10 12H6v-2h3z" }) : /* @__PURE__ */ jsx("path", { d: "M9 3h6l-1 7h3v2h-4v5l-2 1-1-6H6v-2h3z" }) })
26448
+ }
26449
+ ),
26450
+ onPlaybackRateChange && /* @__PURE__ */ jsxs("div", { className: "relative", ref: speedMenuRef, children: [
26451
+ /* @__PURE__ */ jsxs(
26452
+ "button",
26453
+ {
26454
+ onClick: (e) => {
26455
+ e.stopPropagation();
26456
+ setShowSpeedMenu(!showSpeedMenu);
26457
+ },
26458
+ className: "text-xs font-medium hover:text-[#007bff] transition-colors focus:outline-none min-w-[32px]",
26459
+ "aria-label": "Playback Speed",
26460
+ children: [
26461
+ playbackRate,
26462
+ "x"
26463
+ ]
26464
+ }
26465
+ ),
26466
+ showSpeedMenu && /* @__PURE__ */ jsx("div", { className: "absolute bottom-full right-0 mb-2 bg-black/90 text-white rounded shadow-lg overflow-hidden z-50 min-w-[80px]", children: [0.5, 1, 1.5, 2, 2.5, 3, 4, 5].map((rate) => /* @__PURE__ */ jsxs(
26467
+ "button",
26468
+ {
26469
+ onClick: (e) => {
26470
+ e.stopPropagation();
26471
+ onPlaybackRateChange(rate);
26472
+ setShowSpeedMenu(false);
26473
+ },
26474
+ className: `block w-full text-left px-4 py-2 text-xs hover:bg-white/20 transition-colors ${playbackRate === rate ? "text-[#007bff] font-bold" : ""}`,
26475
+ children: [
26476
+ rate,
26477
+ "x"
26478
+ ]
26479
+ },
26480
+ rate
26481
+ )) })
26482
+ ] }),
26483
+ onToggleFullscreen && /* @__PURE__ */ jsx(
26484
+ "button",
26485
+ {
26486
+ onClick: (e) => {
26487
+ e.stopPropagation();
26488
+ onToggleFullscreen();
26489
+ },
26490
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26491
+ "aria-label": "Toggle Fullscreen",
26492
+ children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
26493
+ }
26494
+ )
26495
+ ] })
26496
+ ] })
26497
+ ]
26163
26498
  }
26164
26499
  );
26165
26500
  };
@@ -26287,9 +26622,15 @@ var HlsVideoPlayer = forwardRef(({
26287
26622
  const blobUrlRef = useRef(null);
26288
26623
  const [isReady, setIsReady] = useState(false);
26289
26624
  const [isLoading, setIsLoading] = useState(true);
26290
- const [showIndicator, setShowIndicator] = useState(false);
26291
- const [indicatorIsPlaying, setIndicatorIsPlaying] = useState(false);
26292
- const indicatorKeyRef = useRef(0);
26625
+ const [showControls, setShowControls] = useState(true);
26626
+ const [controlsPinned, setControlsPinned] = useState(false);
26627
+ const [isPlaying, setIsPlaying] = useState(false);
26628
+ const [currentTime, setCurrentTime] = useState(0);
26629
+ const [duration, setDuration] = useState(0);
26630
+ const [buffered, setBuffered] = useState(0);
26631
+ const [playbackRate, setPlaybackRate] = useState(1);
26632
+ const userSeekingRef = useRef(false);
26633
+ const controlsTimeoutRef = useRef(null);
26293
26634
  const eventCallbacksRef = useRef({
26294
26635
  onReady,
26295
26636
  onPlay,
@@ -26468,8 +26809,6 @@ var HlsVideoPlayer = forwardRef(({
26468
26809
  }
26469
26810
  });
26470
26811
  hls.on(Events.FRAG_LOADING, () => {
26471
- setIsLoading(true);
26472
- eventCallbacksRef.current.onLoadingChange?.(true);
26473
26812
  });
26474
26813
  hls.on(Events.FRAG_LOADED, () => {
26475
26814
  setIsLoading(false);
@@ -26523,25 +26862,52 @@ var HlsVideoPlayer = forwardRef(({
26523
26862
  const handleCanPlay = () => {
26524
26863
  if (!hlsRef.current) {
26525
26864
  setIsReady(true);
26526
- onReady?.(player);
26527
26865
  }
26866
+ setIsLoading(false);
26867
+ eventCallbacksRef.current.onLoadingChange?.(false);
26868
+ onReady?.(player);
26869
+ };
26870
+ const handlePlay = () => {
26871
+ setIsPlaying(true);
26872
+ eventCallbacksRef.current.onPlay?.(player);
26873
+ };
26874
+ const handlePause = () => {
26875
+ if (userSeekingRef.current && videoRef.current) {
26876
+ videoRef.current.play().catch((err) => console.warn("Auto-resume after seek pause failed:", err));
26877
+ return;
26878
+ }
26879
+ setIsPlaying(false);
26880
+ eventCallbacksRef.current.onPause?.(player);
26528
26881
  };
26529
- const handlePlay = () => eventCallbacksRef.current.onPlay?.(player);
26530
- const handlePause = () => eventCallbacksRef.current.onPause?.(player);
26531
26882
  const handlePlaying = () => {
26532
26883
  setIsLoading(false);
26884
+ setIsPlaying(true);
26533
26885
  eventCallbacksRef.current.onLoadingChange?.(false);
26534
26886
  eventCallbacksRef.current.onPlaying?.(player);
26535
26887
  };
26536
26888
  const handleTimeUpdate = () => {
26537
26889
  const currentTime2 = video.currentTime || 0;
26890
+ setCurrentTime(currentTime2);
26891
+ if (video.buffered.length > 0) {
26892
+ for (let i = 0; i < video.buffered.length; i++) {
26893
+ if (video.buffered.start(i) <= currentTime2 && video.buffered.end(i) >= currentTime2) {
26894
+ setBuffered(video.buffered.end(i));
26895
+ break;
26896
+ }
26897
+ }
26898
+ }
26538
26899
  eventCallbacksRef.current.onTimeUpdate?.(player, currentTime2);
26539
26900
  };
26540
26901
  const handleDurationChange = () => {
26541
26902
  const duration2 = video.duration || 0;
26903
+ setDuration(duration2);
26542
26904
  eventCallbacksRef.current.onDurationChange?.(player, duration2);
26543
26905
  };
26544
- const handleEnded = () => eventCallbacksRef.current.onEnded?.(player);
26906
+ const handleEnded = () => {
26907
+ setIsPlaying(false);
26908
+ userSeekingRef.current = false;
26909
+ eventCallbacksRef.current.onEnded?.(player);
26910
+ };
26545
26911
  const handleLoadStart = () => {
26546
26912
  setIsLoading(true);
26547
26913
  eventCallbacksRef.current.onLoadingChange?.(true);
@@ -26557,8 +26923,19 @@ var HlsVideoPlayer = forwardRef(({
26557
26923
  setIsLoading(true);
26558
26924
  eventCallbacksRef.current.onLoadingChange?.(true);
26559
26925
  };
26560
- const handleSeeking = () => eventCallbacksRef.current.onSeeking?.(player);
26561
- const handleSeeked = () => eventCallbacksRef.current.onSeeked?.(player);
26926
+ const handleSeeking = () => {
26927
+ userSeekingRef.current = true;
26928
+ eventCallbacksRef.current.onSeeking?.(player);
26929
+ };
26930
+ const handleSeeked = () => {
26931
+ setIsLoading(false);
26932
+ eventCallbacksRef.current.onLoadingChange?.(false);
26933
+ if (videoRef.current) {
26934
+ videoRef.current.play().catch((err) => console.warn("Resume playback after seek failed:", err));
26935
+ }
26936
+ userSeekingRef.current = false;
26937
+ eventCallbacksRef.current.onSeeked?.(player);
26938
+ };
26562
26939
  const handleError = () => {
26563
26940
  const error = video.error;
26564
26941
  if (error) {
@@ -26571,6 +26948,10 @@ var HlsVideoPlayer = forwardRef(({
26571
26948
  eventCallbacksRef.current.onError?.(player, errorInfo);
26572
26949
  }
26573
26950
  };
26951
+ const handlePlaybackRateChange2 = (e) => {
26952
+ const target = e.target;
26953
+ setPlaybackRate(target.playbackRate);
26954
+ };
26574
26955
  video.addEventListener("canplay", handleCanPlay);
26575
26956
  video.addEventListener("play", handlePlay);
26576
26957
  video.addEventListener("pause", handlePause);
@@ -26585,6 +26966,7 @@ var HlsVideoPlayer = forwardRef(({
26585
26966
  video.addEventListener("seeking", handleSeeking);
26586
26967
  video.addEventListener("seeked", handleSeeked);
26587
26968
  video.addEventListener("error", handleError);
26969
+ video.addEventListener("ratechange", handlePlaybackRateChange2);
26588
26970
  return () => {
26589
26971
  video.removeEventListener("canplay", handleCanPlay);
26590
26972
  video.removeEventListener("play", handlePlay);
@@ -26600,6 +26982,7 @@ var HlsVideoPlayer = forwardRef(({
26600
26982
  video.removeEventListener("seeking", handleSeeking);
26601
26983
  video.removeEventListener("seeked", handleSeeked);
26602
26984
  video.removeEventListener("error", handleError);
26985
+ video.removeEventListener("ratechange", handlePlaybackRateChange2);
26603
26986
  };
26604
26987
  }, [
26605
26988
  src,
@@ -26628,20 +27011,46 @@ var HlsVideoPlayer = forwardRef(({
26628
27011
  }
26629
27012
  }
26630
27013
  }, [autoplay]);
27014
+ const resetControlsTimeout = useCallback(() => {
27015
+ if (controlsPinned) {
27016
+ setShowControls(true);
27017
+ return;
27018
+ }
27019
+ setShowControls(true);
27020
+ if (controlsTimeoutRef.current) {
27021
+ clearTimeout(controlsTimeoutRef.current);
27022
+ }
27023
+ if (isPlaying) {
27024
+ controlsTimeoutRef.current = setTimeout(() => {
27025
+ setShowControls(false);
27026
+ }, 3e3);
27027
+ }
27028
+ }, [isPlaying, controlsPinned]);
27029
+ const handleMouseMove = useCallback(() => {
27030
+ resetControlsTimeout();
27031
+ }, [resetControlsTimeout]);
27032
+ useEffect(() => {
27033
+ resetControlsTimeout();
27034
+ return () => {
27035
+ if (controlsTimeoutRef.current) {
27036
+ clearTimeout(controlsTimeoutRef.current);
27037
+ }
27038
+ };
27039
+ }, [isPlaying, resetControlsTimeout]);
26631
27040
  const play = useCallback(() => {
26632
27041
  return videoRef.current?.play();
26633
27042
  }, []);
26634
27043
  const pause = useCallback(() => {
26635
27044
  videoRef.current?.pause();
26636
27045
  }, []);
26637
- const currentTime = useCallback((time2) => {
27046
+ const currentTimeProp = useCallback((time2) => {
26638
27047
  if (time2 !== void 0 && videoRef.current) {
26639
27048
  videoRef.current.currentTime = time2;
26640
27049
  return time2;
26641
27050
  }
26642
27051
  return videoRef.current?.currentTime || 0;
26643
27052
  }, []);
26644
- const duration = useCallback(() => {
27053
+ const durationProp = useCallback(() => {
26645
27054
  return videoRef.current?.duration || 0;
26646
27055
  }, []);
26647
27056
  const paused = useCallback(() => {
@@ -26654,95 +27063,144 @@ var HlsVideoPlayer = forwardRef(({
26654
27063
  }
26655
27064
  return videoRef.current?.muted ?? false;
26656
27065
  }, []);
26657
- const volume = useCallback((level) => {
27066
+ const volumeProp = useCallback((level) => {
26658
27067
  if (level !== void 0 && videoRef.current) {
26659
27068
  videoRef.current.volume = level;
26660
27069
  return level;
26661
27070
  }
26662
27071
  return videoRef.current?.volume ?? 1;
26663
27072
  }, []);
26664
- const playbackRate = useCallback((rate) => {
27073
+ const playbackRateProp = useCallback((rate) => {
26665
27074
  if (rate !== void 0 && videoRef.current) {
26666
27075
  videoRef.current.playbackRate = rate;
26667
27076
  return rate;
26668
27077
  }
26669
27078
  return videoRef.current?.playbackRate ?? 1;
26670
27079
  }, []);
27080
+ const handleTogglePlay = useCallback(() => {
27081
+ if (videoRef.current) {
27082
+ if (videoRef.current.paused) {
27083
+ videoRef.current.play();
27084
+ } else {
27085
+ videoRef.current.pause();
27086
+ }
27087
+ }
27088
+ }, []);
27089
+ const handleSeek = useCallback((time2) => {
27090
+ if (videoRef.current) {
27091
+ videoRef.current.currentTime = time2;
27092
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed during seek:", err));
27093
+ }
27094
+ }, []);
27095
+ const handleSeekStart = useCallback(() => {
27096
+ userSeekingRef.current = true;
27097
+ }, []);
27098
+ const handleSeekEnd = useCallback(() => {
27099
+ if (videoRef.current) {
27100
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed after seek:", err));
27101
+ }
27102
+ }, []);
27103
+ const handlePlaybackRateChange = useCallback((rate) => {
27104
+ if (videoRef.current) {
27105
+ videoRef.current.playbackRate = rate;
27106
+ }
27107
+ }, []);
27108
+ const handleToggleFullscreen = useCallback(() => {
27109
+ if (videoContainerRef.current) {
27110
+ if (!document.fullscreenElement) {
27111
+ videoContainerRef.current.requestFullscreen();
27112
+ } else {
27113
+ document.exitFullscreen();
27114
+ }
27115
+ }
27116
+ }, []);
26671
27117
  useImperativeHandle(ref, () => ({
26672
27118
  hls: hlsRef.current,
26673
27119
  video: videoRef.current,
26674
27120
  play,
26675
27121
  pause,
26676
- currentTime,
26677
- duration,
27122
+ currentTime: currentTimeProp,
27123
+ duration: durationProp,
26678
27124
  paused,
26679
27125
  mute,
26680
- volume,
26681
- playbackRate,
27126
+ volume: volumeProp,
27127
+ playbackRate: playbackRateProp,
26682
27128
  dispose,
26683
27129
  isReady,
26684
27130
  // For backward compatibility with Video.js API
26685
27131
  player: playerLikeObject()
26686
- }), [play, pause, currentTime, duration, paused, mute, volume, playbackRate, dispose, isReady, playerLikeObject]);
26687
- const handleClickWithIndicator = useCallback(() => {
26688
- if (!onClick || !videoRef.current) return;
26689
- const willBePlaying = videoRef.current.paused;
26690
- setIndicatorIsPlaying(willBePlaying);
26691
- setShowIndicator(false);
26692
- setTimeout(() => {
26693
- indicatorKeyRef.current += 1;
26694
- setShowIndicator(true);
26695
- }, 0);
26696
- onClick();
26697
- }, [onClick]);
26698
- return /* @__PURE__ */ jsxs("div", { className: `hls-video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
26699
- /* @__PURE__ */ jsx(
26700
- "div",
26701
- {
26702
- className: "hls-video-player-container",
26703
- ref: videoContainerRef,
26704
- children: /* @__PURE__ */ jsx(
26705
- "video",
27132
+ }), [play, pause, currentTimeProp, durationProp, paused, mute, volumeProp, playbackRateProp, dispose, isReady, playerLikeObject]);
27133
+ const handleContainerClick = useCallback(() => {
27134
+ if (!onClick && !controls) {
27135
+ handleTogglePlay();
27136
+ }
27137
+ if (onClick) {
27138
+ onClick();
27139
+ }
27140
+ }, [onClick, controls, handleTogglePlay]);
27141
+ return /* @__PURE__ */ jsxs(
27142
+ "div",
27143
+ {
27144
+ className: `hls-video-player-wrapper ${className} group`,
27145
+ style: { position: "relative", width: "100%", height: "100%" },
27146
+ onMouseMove: handleMouseMove,
27147
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
27148
+ children: [
27149
+ /* @__PURE__ */ jsxs(
27150
+ "div",
26706
27151
  {
26707
- ref: videoRef,
26708
- className: "hls-video-element",
26709
- poster,
26710
- controls,
26711
- loop,
26712
- muted,
26713
- playsInline,
26714
- autoPlay: autoplay,
26715
- preload: "metadata"
27152
+ className: "hls-video-player-container",
27153
+ ref: videoContainerRef,
27154
+ onClick: handleContainerClick,
27155
+ children: [
27156
+ /* @__PURE__ */ jsx(
27157
+ "video",
27158
+ {
27159
+ ref: videoRef,
27160
+ className: "hls-video-element",
27161
+ poster,
27162
+ controls: false,
27163
+ loop,
27164
+ muted,
27165
+ playsInline,
27166
+ autoPlay: autoplay,
27167
+ preload: "metadata"
27168
+ }
27169
+ ),
27170
+ controls && /* @__PURE__ */ jsx(
27171
+ VideoControls,
27172
+ {
27173
+ isPlaying,
27174
+ currentTime,
27175
+ duration,
27176
+ buffered,
27177
+ showControls: controlsPinned || showControls || !isPlaying,
27178
+ controlsPinned,
27179
+ playbackRate,
27180
+ onPlayPause: handleTogglePlay,
27181
+ onSeek: handleSeek,
27182
+ onSeekStart: handleSeekStart,
27183
+ onSeekEnd: handleSeekEnd,
27184
+ onPlaybackRateChange: handlePlaybackRateChange,
27185
+ onTogglePinControls: () => setControlsPinned((prev) => {
27186
+ const next = !prev;
27187
+ if (next) {
27188
+ setShowControls(true);
27189
+ } else {
27190
+ resetControlsTimeout();
27191
+ }
27192
+ return next;
27193
+ }),
27194
+ onToggleFullscreen: handleToggleFullscreen
27195
+ }
27196
+ )
27197
+ ]
26716
27198
  }
26717
- )
26718
- }
26719
- ),
26720
- isLoading && !externalLoadingControl && /* @__PURE__ */ jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
26721
- onClick && !controls && /* @__PURE__ */ jsx(
26722
- "div",
26723
- {
26724
- onClick: handleClickWithIndicator,
26725
- style: {
26726
- position: "absolute",
26727
- top: 0,
26728
- left: 0,
26729
- right: 0,
26730
- bottom: 0,
26731
- zIndex: 1,
26732
- cursor: "pointer"
26733
- },
26734
- "aria-label": "Click to play/pause"
26735
- }
26736
- ),
26737
- onClick && !controls && /* @__PURE__ */ jsx(
26738
- PlayPauseIndicator,
26739
- {
26740
- show: showIndicator,
26741
- isPlaying: indicatorIsPlaying
26742
- },
26743
- indicatorKeyRef.current
26744
- )
26745
- ] });
27199
+ ),
27200
+ isLoading && !externalLoadingControl && /* @__PURE__ */ jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) })
27201
+ ]
27202
+ }
27203
+ );
26746
27204
  });
26747
27205
  HlsVideoPlayer.displayName = "HlsVideoPlayer";
26748
27206
  var VideoPlayer = HlsVideoPlayer;
@@ -26750,6 +27208,7 @@ var CroppedHlsVideoPlayer = forwardRef(({
26750
27208
  crop,
26751
27209
  debug = false,
26752
27210
  onClick,
27211
+ controls = true,
26753
27212
  ...videoProps
26754
27213
  }, ref) => {
26755
27214
  const {
@@ -26771,9 +27230,15 @@ var CroppedHlsVideoPlayer = forwardRef(({
26771
27230
  const [isVideoReady, setIsVideoReady] = useState(false);
26772
27231
  const [canvasDimensions, setCanvasDimensions] = useState({ width: 0, height: 0 });
26773
27232
  const [isProcessing, setIsProcessing] = useState(false);
26774
- const [showIndicator, setShowIndicator] = useState(false);
26775
- const [indicatorIsPlaying, setIndicatorIsPlaying] = useState(false);
26776
- const indicatorKeyRef = useRef(0);
27233
+ const [showControls, setShowControls] = useState(true);
27234
+ const [isPlaying, setIsPlaying] = useState(false);
27235
+ const [currentTime, setCurrentTime] = useState(0);
27236
+ const [duration, setDuration] = useState(0);
27237
+ const [buffered, setBuffered] = useState(0);
27238
+ const [playbackRate, setPlaybackRate] = useState(1);
27239
+ const controlsTimeoutRef = useRef(null);
27240
+ const userSeekingRef = useRef(false);
27241
+ const [controlsPinned, setControlsPinned] = useState(false);
26777
27242
  const stopCanvasRendering = useCallback(() => {
26778
27243
  if (animationFrameRef.current) {
26779
27244
  cancelAnimationFrame(animationFrameRef.current);
@@ -26858,7 +27323,7 @@ var CroppedHlsVideoPlayer = forwardRef(({
26858
27323
  const canvas = canvasRef.current;
26859
27324
  const video = videoElementRef.current;
26860
27325
  const ctx = canvas.getContext("2d");
26861
- if (!ctx || video.paused || video.ended) {
27326
+ if (!ctx || video.readyState < 2) {
26862
27327
  return;
26863
27328
  }
26864
27329
  const videoWidth = video.videoWidth;
@@ -26885,7 +27350,9 @@ var CroppedHlsVideoPlayer = forwardRef(({
26885
27350
  canvas.height
26886
27351
  // Destination (full canvas)
26887
27352
  );
26888
- animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27353
+ if (!video.paused && !video.ended) {
27354
+ animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27355
+ }
26889
27356
  }, [crop]);
26890
27357
  const handleVideoReady = useCallback((player) => {
26891
27358
  console.log("[CroppedHlsVideoPlayer] Video player ready");
@@ -26893,11 +27360,15 @@ var CroppedHlsVideoPlayer = forwardRef(({
26893
27360
  if (videoEl) {
26894
27361
  videoElementRef.current = videoEl;
26895
27362
  setIsVideoReady(true);
27363
+ if (videoEl.readyState >= 2) {
27364
+ renderFrameToCanvas();
27365
+ }
26896
27366
  }
26897
27367
  onReadyProp?.(player);
26898
- }, [onReadyProp]);
27368
+ }, [onReadyProp, renderFrameToCanvas]);
26899
27369
  const handleVideoPlay = useCallback((player) => {
26900
27370
  console.log("[CroppedHlsVideoPlayer] Video playing, starting canvas rendering");
27371
+ setIsPlaying(true);
26901
27372
  if (crop && canvasRef.current) {
26902
27373
  setIsProcessing(true);
26903
27374
  renderFrameToCanvas();
@@ -26905,43 +27376,40 @@ var CroppedHlsVideoPlayer = forwardRef(({
26905
27376
  onPlayProp?.(player);
26906
27377
  }, [crop, renderFrameToCanvas, onPlayProp]);
26907
27378
  const handleVideoPause = useCallback((player) => {
26908
- console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
27379
+ console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering (keeping last frame)");
27380
+ if (userSeekingRef.current && hiddenVideoRef.current) {
27381
+ hiddenVideoRef.current.play()?.catch(() => {
27382
+ });
27383
+ return;
27384
+ }
26909
27385
  stopCanvasRendering();
26910
27386
  setIsProcessing(false);
26911
- if (canvasRef.current) {
26912
- const ctx = canvasRef.current.getContext("2d");
26913
- if (ctx) {
26914
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26915
- ctx.fillStyle = "black";
26916
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26917
- }
26918
- }
27387
+ setIsPlaying(false);
27388
+ renderFrameToCanvas();
26919
27389
  onPauseProp?.(player);
26920
- }, [stopCanvasRendering, onPauseProp]);
27390
+ }, [stopCanvasRendering, onPauseProp, renderFrameToCanvas]);
26921
27391
  const handleVideoEnded = useCallback((player) => {
26922
- console.log("[CroppedHlsVideoPlayer] Video ended, CLEARING canvas");
27392
+ console.log("[CroppedHlsVideoPlayer] Video ended, stopping canvas rendering (keeping last frame)");
26923
27393
  stopCanvasRendering();
26924
27394
  setIsProcessing(false);
26925
- if (canvasRef.current) {
26926
- const ctx = canvasRef.current.getContext("2d");
26927
- if (ctx) {
26928
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26929
- ctx.fillStyle = "black";
26930
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26931
- }
26932
- }
27395
+ setIsPlaying(false);
27396
+ userSeekingRef.current = false;
26933
27397
  onEndedProp?.(player);
26934
27398
  }, [stopCanvasRendering, onEndedProp]);
26935
27399
  const handleSeeking = useCallback((player) => {
26936
27400
  console.log("[CroppedHlsVideoPlayer] Video seeking");
26937
- if (crop && !videoElementRef.current?.paused) {
27401
+ userSeekingRef.current = true;
27402
+ if (crop) {
26938
27403
  renderFrameToCanvas();
26939
27404
  }
26940
27405
  onSeekingProp?.(player);
26941
27406
  }, [crop, renderFrameToCanvas, onSeekingProp]);
26942
27407
  const handleSeeked = useCallback((player) => {
26943
27408
  console.log("[CroppedHlsVideoPlayer] Video seeked");
26944
- if (crop && !videoElementRef.current?.paused) {
27409
+ hiddenVideoRef.current?.play()?.catch(() => {
27410
+ });
27411
+ userSeekingRef.current = false;
27412
+ if (crop) {
26945
27413
  renderFrameToCanvas();
26946
27414
  }
26947
27415
  onSeekedProp?.(player);
@@ -26949,8 +27417,29 @@ var CroppedHlsVideoPlayer = forwardRef(({
26949
27417
  const handleLoadedMetadata = useCallback((player) => {
26950
27418
  console.log("[CroppedHlsVideoPlayer] Video metadata loaded");
26951
27419
  calculateCanvasDimensions();
27420
+ if (hiddenVideoRef.current?.video) {
27421
+ setDuration(hiddenVideoRef.current.video.duration || 0);
27422
+ }
27423
+ requestAnimationFrame(() => renderFrameToCanvas());
26952
27424
  onLoadedMetadataProp?.(player);
26953
- }, [calculateCanvasDimensions, onLoadedMetadataProp]);
27425
+ }, [calculateCanvasDimensions, onLoadedMetadataProp, renderFrameToCanvas]);
27426
+ const handleTimeUpdate = useCallback((player, time2) => {
27427
+ setCurrentTime(time2);
27428
+ if (hiddenVideoRef.current?.video && hiddenVideoRef.current.video.buffered.length > 0) {
27429
+ const video = hiddenVideoRef.current.video;
27430
+ for (let i = 0; i < video.buffered.length; i++) {
27431
+ if (video.buffered.start(i) <= time2 && video.buffered.end(i) >= time2) {
27432
+ setBuffered(video.buffered.end(i));
27433
+ break;
27434
+ }
27435
+ }
27436
+ }
27437
+ videoProps.onTimeUpdate?.(player, time2);
27438
+ }, [videoProps.onTimeUpdate]);
27439
+ const handleDurationChange = useCallback((player, dur) => {
27440
+ setDuration(dur);
27441
+ videoProps.onDurationChange?.(player, dur);
27442
+ }, [videoProps.onDurationChange]);
26954
27443
  useEffect(() => {
26955
27444
  calculateCanvasDimensions();
26956
27445
  const handleResize = () => {
@@ -26978,33 +27467,97 @@ var CroppedHlsVideoPlayer = forwardRef(({
26978
27467
  stopCanvasRendering();
26979
27468
  };
26980
27469
  }, [stopCanvasRendering]);
27470
+ const resetControlsTimeout = useCallback(() => {
27471
+ if (controlsPinned) {
27472
+ setShowControls(true);
27473
+ return;
27474
+ }
27475
+ setShowControls(true);
27476
+ if (controlsTimeoutRef.current) {
27477
+ clearTimeout(controlsTimeoutRef.current);
27478
+ }
27479
+ if (isPlaying) {
27480
+ controlsTimeoutRef.current = setTimeout(() => {
27481
+ setShowControls(false);
27482
+ }, 3e3);
27483
+ }
27484
+ }, [isPlaying, controlsPinned]);
27485
+ const handleMouseMove = useCallback(() => {
27486
+ resetControlsTimeout();
27487
+ }, [resetControlsTimeout]);
27488
+ useEffect(() => {
27489
+ resetControlsTimeout();
27490
+ return () => {
27491
+ if (controlsTimeoutRef.current) {
27492
+ clearTimeout(controlsTimeoutRef.current);
27493
+ }
27494
+ };
27495
+ }, [isPlaying, resetControlsTimeout]);
27496
+ const handleTogglePlay = useCallback(() => {
27497
+ if (hiddenVideoRef.current?.video) {
27498
+ if (hiddenVideoRef.current.video.paused) {
27499
+ hiddenVideoRef.current.play();
27500
+ } else {
27501
+ hiddenVideoRef.current.pause();
27502
+ }
27503
+ }
27504
+ }, []);
27505
+ const handleSeek = useCallback((time2) => {
27506
+ if (hiddenVideoRef.current) {
27507
+ hiddenVideoRef.current.currentTime(time2);
27508
+ hiddenVideoRef.current.play()?.catch(() => {
27509
+ });
27510
+ setTimeout(() => renderFrameToCanvas(), 50);
27511
+ }
27512
+ }, [renderFrameToCanvas]);
27513
+ const handleSeekStart = useCallback(() => {
27514
+ userSeekingRef.current = true;
27515
+ }, []);
27516
+ const handleSeekEnd = useCallback(() => {
27517
+ if (hiddenVideoRef.current) {
27518
+ hiddenVideoRef.current.play()?.catch(() => {
27519
+ });
27520
+ }
27521
+ }, []);
27522
+ const handlePlaybackRateChange = useCallback((rate) => {
27523
+ if (hiddenVideoRef.current) {
27524
+ hiddenVideoRef.current.playbackRate(rate);
27525
+ setPlaybackRate(rate);
27526
+ }
27527
+ }, []);
27528
+ const handleToggleFullscreen = useCallback(() => {
27529
+ if (videoContainerRef.current) {
27530
+ if (!document.fullscreenElement) {
27531
+ videoContainerRef.current.requestFullscreen();
27532
+ } else {
27533
+ document.exitFullscreen();
27534
+ }
27535
+ }
27536
+ }, []);
26981
27537
  if (!crop) {
26982
- return /* @__PURE__ */ jsx(HlsVideoPlayer, { ref, ...videoProps, onClick });
26983
- }
26984
- const handleClickWithIndicator = () => {
26985
- if (!onClick || !hiddenVideoRef.current?.video) return;
26986
- const video = hiddenVideoRef.current.video;
26987
- const willBePlaying = video.paused;
26988
- setIndicatorIsPlaying(willBePlaying);
26989
- setShowIndicator(false);
26990
- setTimeout(() => {
26991
- indicatorKeyRef.current += 1;
26992
- setShowIndicator(true);
26993
- }, 0);
26994
- onClick();
27538
+ return /* @__PURE__ */ jsx(HlsVideoPlayer, { ref, ...videoProps, onClick, controls });
27539
+ }
27540
+ const handleClick = () => {
27541
+ if (!onClick && !controls) {
27542
+ handleTogglePlay();
27543
+ }
27544
+ if (onClick) onClick();
26995
27545
  };
26996
27546
  return /* @__PURE__ */ jsxs(
26997
27547
  "div",
26998
27548
  {
26999
27549
  ref: videoContainerRef,
27000
- className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${inheritedClassName}`,
27001
- onClick: handleClickWithIndicator,
27550
+ className: `relative w-full h-full flex items-center justify-center bg-black group ${inheritedClassName} ${onClick || controls ? "cursor-pointer" : ""}`,
27551
+ onClick: handleClick,
27552
+ onMouseMove: handleMouseMove,
27553
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
27002
27554
  children: [
27003
27555
  /* @__PURE__ */ jsx("div", { className: "hidden", children: /* @__PURE__ */ jsx(
27004
27556
  HlsVideoPlayer,
27005
27557
  {
27006
27558
  ref: hiddenVideoRef,
27007
27559
  ...videoProps,
27560
+ controls: false,
27008
27561
  onReady: handleVideoReady,
27009
27562
  onPlay: handleVideoPlay,
27010
27563
  onPause: handleVideoPause,
@@ -27014,7 +27567,9 @@ var CroppedHlsVideoPlayer = forwardRef(({
27014
27567
  onLoadedMetadata: handleLoadedMetadata,
27015
27568
  onLoadedData: videoProps.onLoadedData,
27016
27569
  onPlaying: videoProps.onPlaying,
27017
- onLoadingChange: videoProps.onLoadingChange
27570
+ onLoadingChange: videoProps.onLoadingChange,
27571
+ onTimeUpdate: handleTimeUpdate,
27572
+ onDurationChange: handleDurationChange
27018
27573
  }
27019
27574
  ) }),
27020
27575
  /* @__PURE__ */ jsx(
@@ -27031,8 +27586,8 @@ var CroppedHlsVideoPlayer = forwardRef(({
27031
27586
  }
27032
27587
  }
27033
27588
  ),
27034
- !isVideoReady && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27035
- debug && isVideoReady && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
27589
+ !isVideoReady && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27590
+ debug && isVideoReady && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono pointer-events-none z-20", children: [
27036
27591
  /* @__PURE__ */ jsxs("div", { children: [
27037
27592
  "Crop: ",
27038
27593
  crop.x,
@@ -27056,13 +27611,32 @@ var CroppedHlsVideoPlayer = forwardRef(({
27056
27611
  isProcessing ? "Yes" : "No"
27057
27612
  ] })
27058
27613
  ] }),
27059
- onClick && /* @__PURE__ */ jsx(
27060
- PlayPauseIndicator,
27614
+ controls && isVideoReady && /* @__PURE__ */ jsx(
27615
+ VideoControls,
27061
27616
  {
27062
- show: showIndicator,
27063
- isPlaying: indicatorIsPlaying
27064
- },
27065
- indicatorKeyRef.current
27617
+ isPlaying,
27618
+ currentTime,
27619
+ duration,
27620
+ buffered,
27621
+ showControls: controlsPinned || showControls || !isPlaying,
27622
+ controlsPinned,
27623
+ playbackRate,
27624
+ onPlayPause: handleTogglePlay,
27625
+ onSeek: handleSeek,
27626
+ onSeekStart: handleSeekStart,
27627
+ onSeekEnd: handleSeekEnd,
27628
+ onPlaybackRateChange: handlePlaybackRateChange,
27629
+ onTogglePinControls: () => setControlsPinned((prev) => {
27630
+ const next = !prev;
27631
+ if (next) {
27632
+ setShowControls(true);
27633
+ } else {
27634
+ resetControlsTimeout();
27635
+ }
27636
+ return next;
27637
+ }),
27638
+ onToggleFullscreen: handleToggleFullscreen
27639
+ }
27066
27640
  )
27067
27641
  ]
27068
27642
  }
@@ -27601,6 +28175,67 @@ var SilentErrorBoundary = class extends React23__default.Component {
27601
28175
  ] }) });
27602
28176
  }
27603
28177
  };
28178
+ var PlayPauseIndicator = ({
28179
+ show,
28180
+ isPlaying,
28181
+ duration = 600
28182
+ }) => {
28183
+ const [isVisible, setIsVisible] = useState(false);
28184
+ const [isFading, setIsFading] = useState(false);
28185
+ useEffect(() => {
28186
+ if (show) {
28187
+ setIsVisible(true);
28188
+ setIsFading(false);
28189
+ const fadeTimer = setTimeout(() => {
28190
+ setIsFading(true);
28191
+ }, 100);
28192
+ const hideTimer = setTimeout(() => {
28193
+ setIsVisible(false);
28194
+ setIsFading(false);
28195
+ }, duration);
28196
+ return () => {
28197
+ clearTimeout(fadeTimer);
28198
+ clearTimeout(hideTimer);
28199
+ };
28200
+ }
28201
+ }, [show, duration]);
28202
+ if (!isVisible) return null;
28203
+ return /* @__PURE__ */ jsx(
28204
+ "div",
28205
+ {
28206
+ className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
28207
+ style: {
28208
+ opacity: isFading ? 0 : 1,
28209
+ transition: `opacity ${duration - 100}ms ease-out`
28210
+ },
28211
+ children: /* @__PURE__ */ jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
28212
+ // Play icon (triangle)
28213
+ /* @__PURE__ */ jsx(
28214
+ "svg",
28215
+ {
28216
+ xmlns: "http://www.w3.org/2000/svg",
28217
+ viewBox: "0 0 24 24",
28218
+ fill: "white",
28219
+ className: "w-16 h-16",
28220
+ children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
28221
+ }
28222
+ )
28223
+ ) : (
28224
+ // Pause icon (two bars)
28225
+ /* @__PURE__ */ jsx(
28226
+ "svg",
28227
+ {
28228
+ xmlns: "http://www.w3.org/2000/svg",
28229
+ viewBox: "0 0 24 24",
28230
+ fill: "white",
28231
+ className: "w-16 h-16",
28232
+ children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
28233
+ }
28234
+ )
28235
+ ) })
28236
+ }
28237
+ );
28238
+ };
27604
28239
  var BackButton = ({
27605
28240
  onClick,
27606
28241
  text = "Back",
@@ -31020,7 +31655,7 @@ function DiagnosisVideoModal({
31020
31655
  }
31021
31656
  loadClip();
31022
31657
  }, [clipId, supabase, transformPlaylistUrls]);
31023
- const formatTime3 = (seconds) => {
31658
+ const formatTime5 = (seconds) => {
31024
31659
  const mins = Math.floor(seconds / 60);
31025
31660
  const secs = Math.floor(seconds % 60);
31026
31661
  return `${mins}:${secs.toString().padStart(2, "0")}`;
@@ -31212,9 +31847,9 @@ function DiagnosisVideoModal({
31212
31847
  }
31213
31848
  ),
31214
31849
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
31215
- formatTime3(currentTime),
31850
+ formatTime5(currentTime),
31216
31851
  " / ",
31217
- formatTime3(duration)
31852
+ formatTime5(duration)
31218
31853
  ] }),
31219
31854
  /* @__PURE__ */ jsx(
31220
31855
  "input",
@@ -33034,45 +33669,35 @@ var LinePdfGenerator = ({
33034
33669
  const doc = new jsPDF$1();
33035
33670
  const pageHeight = doc.internal.pageSize.height;
33036
33671
  const addHeaderPage1 = () => {
33037
- doc.setFontSize(12);
33672
+ doc.setFontSize(14);
33038
33673
  doc.setFont("helvetica", "bold");
33039
- doc.setTextColor(70, 70, 70);
33674
+ doc.setTextColor(50, 50, 50);
33040
33675
  doc.text("OPTIFYE.AI", 20, 15);
33041
- doc.setFontSize(10);
33676
+ doc.setFontSize(9);
33042
33677
  doc.setFont("helvetica", "normal");
33043
33678
  doc.setTextColor(100, 100, 100);
33044
- const reportText = "REAL-TIME PERFORMANCE REPORT";
33045
- const reportTextWidth = doc.getStringUnitWidth(reportText) * 10 / doc.internal.scaleFactor;
33046
- doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
33047
- doc.setDrawColor(220, 220, 220);
33048
- doc.setLineWidth(0.3);
33679
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
33680
+ const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
33681
+ doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
33682
+ doc.setDrawColor(200, 200, 200);
33683
+ doc.setLineWidth(0.5);
33049
33684
  doc.line(20, 20, 190, 20);
33050
33685
  };
33051
33686
  const addHeaderPage2 = () => {
33052
- doc.setFontSize(12);
33687
+ doc.setFontSize(14);
33053
33688
  doc.setFont("helvetica", "bold");
33054
- doc.setTextColor(70, 70, 70);
33689
+ doc.setTextColor(50, 50, 50);
33055
33690
  doc.text("OPTIFYE.AI", 20, 15);
33056
- doc.setFontSize(10);
33691
+ doc.setFontSize(9);
33057
33692
  doc.setFont("helvetica", "normal");
33058
33693
  doc.setTextColor(100, 100, 100);
33059
- const reportText = "REAL-TIME PERFORMANCE REPORT";
33060
- const reportTextWidth = doc.getStringUnitWidth(reportText) * 10 / doc.internal.scaleFactor;
33061
- doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
33062
- doc.text("Page 2", doc.internal.pageSize.width - 30, pageHeight - 15);
33063
- doc.setDrawColor(220, 220, 220);
33064
- doc.setLineWidth(0.3);
33065
- doc.line(20, 20, 190, 20);
33066
- return 35;
33067
- };
33068
- const addFooter = (pageNum) => {
33069
- doc.setFontSize(9);
33070
- doc.setTextColor(130, 130, 130);
33071
33694
  const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
33072
- doc.text(generatedText, 20, pageHeight - 15);
33073
- if (pageNum === 1) {
33074
- doc.text("Page 1", doc.internal.pageSize.width - 30, pageHeight - 15);
33075
- }
33695
+ const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
33696
+ doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
33697
+ doc.setDrawColor(200, 200, 200);
33698
+ doc.setLineWidth(0.5);
33699
+ doc.line(20, 20, 190, 20);
33700
+ return 30;
33076
33701
  };
33077
33702
  addHeaderPage1();
33078
33703
  doc.setFontSize(26);
@@ -33118,37 +33743,47 @@ var LinePdfGenerator = ({
33118
33743
  doc.setDrawColor(200, 200, 200);
33119
33744
  doc.setLineWidth(0.5);
33120
33745
  doc.line(20, 53, 190, 53);
33121
- doc.setFontSize(16);
33746
+ const perfOverviewStartY = 58;
33747
+ doc.setFillColor(245, 245, 245);
33748
+ doc.roundedRect(15, perfOverviewStartY, 180, 50, 3, 3, "F");
33749
+ doc.setFontSize(18);
33122
33750
  doc.setFont("helvetica", "bold");
33123
- doc.setTextColor(70, 70, 70);
33124
- doc.text("Line Performance Overview", 20, 65);
33751
+ doc.setTextColor(40, 40, 40);
33752
+ doc.text("Line Performance Overview", 20, 68);
33125
33753
  doc.setTextColor(0, 0, 0);
33126
33754
  const createKPIBox = (y) => {
33127
- doc.setFillColor(250, 250, 250);
33128
- doc.roundedRect(22, y - 7, 165, 12, 1, 1, "F");
33755
+ doc.setFillColor(255, 255, 255);
33756
+ doc.roundedRect(22, y - 7, 165, 12, 2, 2, "F");
33757
+ doc.setDrawColor(230, 230, 230);
33758
+ doc.setLineWidth(0.2);
33759
+ doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
33129
33760
  };
33130
- createKPIBox(77);
33131
- doc.setFontSize(12);
33761
+ const kpiStartY = 80;
33762
+ const kpiSpacing = 10;
33763
+ createKPIBox(kpiStartY);
33764
+ doc.setFontSize(11);
33132
33765
  doc.setFont("helvetica", "normal");
33133
- doc.text("Output:", 25, 77);
33766
+ doc.text("Output:", 25, kpiStartY);
33134
33767
  doc.setFont("helvetica", "bold");
33135
- doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, 77);
33136
- createKPIBox(87);
33768
+ doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
33769
+ createKPIBox(kpiStartY + kpiSpacing);
33137
33770
  doc.setFont("helvetica", "normal");
33138
- doc.text("Underperforming Workspaces:", 25, 87);
33771
+ doc.text("Underperforming Workspaces:", 25, kpiStartY + kpiSpacing);
33139
33772
  doc.setFont("helvetica", "bold");
33140
- doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120, 87);
33141
- createKPIBox(97);
33773
+ doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120, kpiStartY + kpiSpacing);
33774
+ createKPIBox(kpiStartY + kpiSpacing * 2);
33142
33775
  doc.setFont("helvetica", "normal");
33143
- doc.text("Average Efficiency:", 25, 97);
33776
+ doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing * 2);
33144
33777
  doc.setFont("helvetica", "bold");
33145
- doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, 97);
33146
- doc.setDrawColor(200, 200, 200);
33147
- doc.line(20, 110, 190, 110);
33148
- doc.setFontSize(16);
33778
+ doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, kpiStartY + kpiSpacing * 2);
33779
+ doc.setDrawColor(180, 180, 180);
33780
+ doc.setLineWidth(0.8);
33781
+ doc.line(20, 118, 190, 118);
33782
+ const hourlyOverviewStartY = 123;
33783
+ doc.setFontSize(18);
33149
33784
  doc.setFont("helvetica", "bold");
33150
- doc.setTextColor(70, 70, 70);
33151
- doc.text("Hourly Output Overview", 20, 135);
33785
+ doc.setTextColor(40, 40, 40);
33786
+ doc.text("Hourly Output Overview", 20, 133);
33152
33787
  doc.setTextColor(0, 0, 0);
33153
33788
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
33154
33789
  const [hours, minutes] = startTimeStr.split(":");
@@ -33185,7 +33820,7 @@ var LinePdfGenerator = ({
33185
33820
  }
33186
33821
  hourEndTime.setSeconds(0);
33187
33822
  hourEndTime.setMilliseconds(0);
33188
- const formatTime3 = (date2) => {
33823
+ const formatTime5 = (date2) => {
33189
33824
  return date2.toLocaleTimeString("en-IN", {
33190
33825
  hour: "2-digit",
33191
33826
  minute: "2-digit",
@@ -33193,7 +33828,7 @@ var LinePdfGenerator = ({
33193
33828
  timeZone: "Asia/Kolkata"
33194
33829
  });
33195
33830
  };
33196
- return `${formatTime3(hourStartTime)} - ${formatTime3(hourEndTime)}`;
33831
+ return `${formatTime5(hourStartTime)} - ${formatTime5(hourEndTime)}`;
33197
33832
  });
33198
33833
  };
33199
33834
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
@@ -33340,86 +33975,94 @@ var LinePdfGenerator = ({
33340
33975
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
33341
33976
  });
33342
33977
  }
33978
+ const tableHeaderY = 143;
33979
+ const tableStartY = 151;
33980
+ const rowSpacing = 8;
33981
+ const bottomPadding = 8;
33982
+ const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
33983
+ const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
33984
+ doc.setFillColor(245, 245, 245);
33985
+ doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
33343
33986
  doc.setFontSize(11);
33344
33987
  doc.setFont("helvetica", "bold");
33345
- doc.setFillColor(245, 245, 245);
33346
- doc.roundedRect(20, 140, 170, 8, 1, 1, "F");
33347
- doc.text("Time Range", 25, 145);
33348
- doc.text("Actual Output", 80, 145);
33349
- doc.text("Target Output", 125, 145);
33350
- doc.text("Status", 170, 145);
33351
- doc.setLineWidth(0.2);
33352
- doc.setDrawColor(220, 220, 220);
33353
- doc.line(20, 148, 190, 148);
33988
+ doc.text("Time Range", 25, tableHeaderY);
33989
+ doc.text("Output", 80, tableHeaderY);
33990
+ doc.text("Target", 125, tableHeaderY);
33991
+ doc.text("Status", 170, tableHeaderY);
33992
+ doc.setLineWidth(0.3);
33993
+ doc.setDrawColor(200, 200, 200);
33994
+ doc.line(20, 146, 190, 146);
33354
33995
  doc.setFont("helvetica", "normal");
33355
- let yPos = 155;
33356
- const rowSpacing = 7;
33996
+ let yPos = tableStartY;
33997
+ const lineDateForTable = new Date(lineInfo.date);
33998
+ const todayForTable = /* @__PURE__ */ new Date();
33999
+ todayForTable.setHours(0, 0, 0, 0);
34000
+ lineDateForTable.setHours(0, 0, 0, 0);
34001
+ const isTodayForTable = lineDateForTable.getTime() === todayForTable.getTime();
34002
+ let currentHour = 24;
34003
+ if (isTodayForTable) {
34004
+ const now2 = /* @__PURE__ */ new Date();
34005
+ const currentTimeIST = new Date(now2.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
34006
+ currentHour = currentTimeIST.getHours();
34007
+ }
33357
34008
  hourlyTimeRanges.forEach((timeRange, index) => {
33358
- if (index % 2 === 0) {
33359
- doc.setFillColor(252, 252, 252);
33360
- doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
33361
- }
33362
- const actualOutput = hourlyActualOutput[index];
33363
- const isOverTarget = actualOutput >= targetOutputPerHour;
34009
+ const actualOutput = hourlyActualOutput[index] || 0;
34010
+ const [startHourStr] = (lineInfo.metrics.shift_start || "6:00").split(":");
34011
+ const startHour = parseInt(startHourStr);
34012
+ const hourNumber = (startHour + index) % 24;
34013
+ const dataCollected = !isTodayForTable || hourNumber < currentHour;
34014
+ const outputStr = dataCollected ? actualOutput.toString() : "TBD";
34015
+ const targetStr = targetOutputPerHour.toString();
33364
34016
  doc.text(timeRange, 25, yPos);
33365
- doc.text(actualOutput.toString(), 80, yPos);
33366
- doc.text(targetOutputPerHour.toString(), 125, yPos);
33367
- if (isOverTarget) {
33368
- doc.setDrawColor(0, 171, 69);
33369
- doc.setLineWidth(0.5);
33370
- const tickX = 170;
33371
- const tickY = yPos - 1;
33372
- doc.line(tickX - 2, tickY, tickX, tickY + 1.5);
33373
- doc.line(tickX, tickY + 1.5, tickX + 2, tickY - 2);
34017
+ doc.text(outputStr, 80, yPos);
34018
+ doc.text(targetStr, 125, yPos);
34019
+ if (!dataCollected) {
34020
+ doc.setTextColor(100, 100, 100);
34021
+ doc.text("-", 170, yPos);
34022
+ } else if (actualOutput >= targetOutputPerHour) {
34023
+ doc.setTextColor(0, 171, 69);
34024
+ doc.setFont("ZapfDingbats", "normal");
34025
+ doc.text("4", 170, yPos);
34026
+ doc.setFont("helvetica", "normal");
33374
34027
  } else {
33375
- doc.setDrawColor(227, 67, 41);
33376
- doc.setLineWidth(0.5);
33377
- const crossX = 170;
33378
- const crossY = yPos - 1;
33379
- doc.line(crossX - 2, crossY - 2, crossX + 2, crossY + 2);
33380
- doc.line(crossX - 2, crossY + 2, crossX + 2, crossY - 2);
34028
+ doc.setTextColor(227, 67, 41);
34029
+ doc.text("\xD7", 170, yPos);
33381
34030
  }
33382
- doc.setDrawColor(220, 220, 220);
33383
- doc.setLineWidth(0.2);
34031
+ doc.setTextColor(0, 0, 0);
33384
34032
  yPos += rowSpacing;
33385
34033
  });
33386
- doc.roundedRect(20, 140, 170, yPos - 140 - 3, 1, 1, "S");
33387
- addFooter(1);
33388
34034
  doc.addPage();
33389
34035
  yPos = addHeaderPage2();
33390
- doc.setFontSize(16);
34036
+ const workspaceSectionStartY = yPos;
34037
+ const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
34038
+ const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
34039
+ const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
34040
+ doc.setFillColor(245, 245, 245);
34041
+ doc.roundedRect(15, workspaceSectionStartY, 180, wsTableHeight, 3, 3, "F");
34042
+ doc.setFontSize(18);
33391
34043
  doc.setFont("helvetica", "bold");
33392
- doc.setTextColor(70, 70, 70);
34044
+ doc.setTextColor(40, 40, 40);
33393
34045
  doc.text("Poorest Performing Workspaces", 20, yPos);
33394
34046
  doc.setTextColor(0, 0, 0);
33395
34047
  yPos += 10;
33396
34048
  doc.setFontSize(11);
33397
34049
  doc.setFont("helvetica", "bold");
33398
- doc.setFillColor(245, 245, 245);
33399
- doc.roundedRect(20, yPos, 170, 8, 1, 1, "F");
33400
34050
  yPos += 5;
33401
34051
  doc.text("Workspace", 25, yPos);
33402
- doc.text("Current/Ideal", 85, yPos);
34052
+ doc.text("Current/Target", 85, yPos);
33403
34053
  doc.text("Efficiency", 145, yPos);
33404
34054
  yPos += 3;
33405
- doc.setLineWidth(0.2);
33406
- doc.setDrawColor(220, 220, 220);
34055
+ doc.setLineWidth(0.3);
34056
+ doc.setDrawColor(200, 200, 200);
33407
34057
  doc.line(20, yPos, 190, yPos);
33408
34058
  doc.setFont("helvetica", "normal");
33409
- const tableStartY = yPos;
33410
34059
  yPos += 7;
33411
- const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
33412
34060
  if (sortedWorkspaces.length === 0) {
33413
34061
  doc.text("No workspace data available", 25, yPos);
33414
34062
  yPos += 10;
33415
34063
  } else {
33416
34064
  sortedWorkspaces.forEach((ws, index) => {
33417
- if (index % 2 === 0) {
33418
- doc.setFillColor(252, 252, 252);
33419
- doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
33420
- }
33421
34065
  const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
33422
- const maxWidth = 55;
33423
34066
  const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
33424
34067
  doc.text(truncatedName, 25, yPos);
33425
34068
  doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
@@ -33427,9 +34070,6 @@ var LinePdfGenerator = ({
33427
34070
  yPos += 8;
33428
34071
  });
33429
34072
  }
33430
- const wsTableHeight = yPos - tableStartY - 3;
33431
- doc.roundedRect(20, tableStartY, 170, wsTableHeight, 1, 1, "S");
33432
- addFooter(2);
33433
34073
  doc.save(`${lineInfo.line_name}_${date.split(",")[0]}.pdf`);
33434
34074
  } catch (error) {
33435
34075
  console.error("PDF generation failed:", error);
@@ -34603,25 +35243,25 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34603
35243
  doc.setFont("helvetica", "bold");
34604
35244
  doc.setTextColor(50, 50, 50);
34605
35245
  doc.text("OPTIFYE.AI", 20, 15);
34606
- doc.setFontSize(11);
35246
+ doc.setFontSize(9);
34607
35247
  doc.setFont("helvetica", "normal");
34608
- doc.setTextColor(80, 80, 80);
34609
- const reportText = "REAL-TIME PERFORMANCE REPORT";
34610
- const reportTextWidth = doc.getStringUnitWidth(reportText) * 11 / doc.internal.scaleFactor;
34611
- doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
35248
+ doc.setTextColor(100, 100, 100);
35249
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
35250
+ const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
35251
+ doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
34612
35252
  doc.setDrawColor(200, 200, 200);
34613
35253
  doc.setLineWidth(0.5);
34614
35254
  doc.line(20, 20, 190, 20);
34615
35255
  doc.setFillColor(250, 250, 250);
34616
- doc.roundedRect(15, 25, 180, 55, 3, 3, "F");
35256
+ doc.roundedRect(15, 25, 180, 53, 3, 3, "F");
34617
35257
  doc.setFontSize(32);
34618
35258
  doc.setFont("helvetica", "bold");
34619
35259
  doc.setTextColor(0, 0, 0);
34620
- doc.text(lineName, 20, 40);
35260
+ doc.text(lineName, 20, 39);
34621
35261
  doc.setFontSize(22);
34622
35262
  doc.setFont("helvetica", "normal");
34623
35263
  doc.setTextColor(40, 40, 40);
34624
- doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20, 52);
35264
+ doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20, 51);
34625
35265
  doc.setFontSize(13);
34626
35266
  doc.setFont("helvetica", "normal");
34627
35267
  doc.setTextColor(60, 60, 60);
@@ -34632,8 +35272,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34632
35272
  timeZone: "Asia/Kolkata"
34633
35273
  });
34634
35274
  const shiftType = "Day Shift";
34635
- doc.text(`${date}`, 20, 65);
34636
- doc.text(`${shiftType}`, 20, 73);
35275
+ doc.text(`${date}`, 20, 63);
35276
+ doc.text(`${shiftType}`, 20, 71);
34637
35277
  const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
34638
35278
  hour: "2-digit",
34639
35279
  minute: "2-digit",
@@ -34646,11 +35286,11 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34646
35286
  });
34647
35287
  doc.setFontSize(12);
34648
35288
  doc.setTextColor(80, 80, 80);
34649
- doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20, 81);
35289
+ doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20, 79);
34650
35290
  doc.setTextColor(0, 0, 0);
34651
35291
  doc.setDrawColor(180, 180, 180);
34652
35292
  doc.setLineWidth(0.8);
34653
- doc.line(20, 90, 190, 90);
35293
+ doc.line(20, 88, 190, 88);
34654
35294
  const createKPIBox = (y) => {
34655
35295
  doc.setFillColor(255, 255, 255);
34656
35296
  doc.roundedRect(22, y - 7, 165, 12, 2, 2, "F");
@@ -34658,66 +35298,72 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34658
35298
  doc.setLineWidth(0.2);
34659
35299
  doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
34660
35300
  };
35301
+ const perfOverviewStartY = 93;
34661
35302
  doc.setFillColor(245, 245, 245);
34662
- doc.roundedRect(15, 95, 180, 60, 3, 3, "F");
35303
+ doc.roundedRect(15, perfOverviewStartY, 180, 60, 3, 3, "F");
34663
35304
  doc.setFontSize(18);
34664
35305
  doc.setFont("helvetica", "bold");
34665
35306
  doc.setTextColor(40, 40, 40);
34666
- doc.text("Performance Overview", 20, 105);
35307
+ doc.text("Performance Overview", 20, 103);
34667
35308
  doc.setTextColor(0, 0, 0);
34668
- const kpiStartY = 117;
35309
+ const kpiStartY = 115;
34669
35310
  const kpiSpacing = 10;
34670
35311
  createKPIBox(kpiStartY);
34671
35312
  doc.setFontSize(11);
34672
35313
  doc.setFont("helvetica", "normal");
34673
- doc.text("Progress:", 25, kpiStartY);
35314
+ doc.text("Current Output/Target Output:", 25, kpiStartY);
34674
35315
  doc.setFont("helvetica", "bold");
34675
- doc.text(`${(workspace.total_actions / workspace.target_output * 100).toFixed(0)}% of Today's target`, 120, kpiStartY);
35316
+ doc.text(`${workspace.total_actions} / ${workspace.target_output}`, 120, kpiStartY);
34676
35317
  createKPIBox(kpiStartY + kpiSpacing);
34677
35318
  doc.setFont("helvetica", "normal");
34678
- doc.text("Current Output/Ideal Output:", 25, kpiStartY + kpiSpacing);
35319
+ doc.text("Efficiency:", 25, kpiStartY + kpiSpacing);
34679
35320
  doc.setFont("helvetica", "bold");
34680
- doc.text(`${workspace.total_actions} / ${workspace.target_output}`, 120, kpiStartY + kpiSpacing);
35321
+ doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing);
34681
35322
  createKPIBox(kpiStartY + kpiSpacing * 2);
34682
35323
  doc.setFont("helvetica", "normal");
34683
- doc.text("Efficiency:", 25, kpiStartY + kpiSpacing * 2);
34684
- doc.setFont("helvetica", "bold");
34685
- doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing * 2);
34686
- createKPIBox(kpiStartY + kpiSpacing * 3);
34687
- doc.setFont("helvetica", "normal");
34688
- doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 3);
35324
+ doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
34689
35325
  doc.setFont("helvetica", "bold");
34690
- doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 3);
35326
+ doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 2);
34691
35327
  doc.setDrawColor(180, 180, 180);
34692
35328
  doc.setLineWidth(0.8);
34693
- doc.line(20, 170, 190, 170);
35329
+ doc.line(20, 163, 190, 163);
35330
+ const hourlyPerfStartY = 168;
35331
+ const hourlyData = workspace.hourly_action_counts || [];
35332
+ const hourlyTarget = workspace.pph_threshold;
35333
+ const tableStartY = 199;
35334
+ const rowHeight = 8;
35335
+ const bottomPadding = 8;
35336
+ const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
34694
35337
  doc.setFillColor(245, 245, 245);
34695
- doc.roundedRect(15, 175, 180, 85, 3, 3, "F");
35338
+ doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
34696
35339
  doc.setFontSize(18);
34697
35340
  doc.setFont("helvetica", "bold");
34698
35341
  doc.setTextColor(40, 40, 40);
34699
- doc.text("Hourly Performance", 20, 185);
35342
+ doc.text("Hourly Performance", 20, 178);
34700
35343
  doc.setTextColor(0, 0, 0);
34701
35344
  doc.setFontSize(11);
34702
35345
  doc.setFont("helvetica", "bold");
34703
- doc.setFillColor(245, 245, 245);
34704
- doc.roundedRect(20, 177, 170, 8, 1, 1, "F");
34705
- doc.text("Time Range", 25, 182);
34706
- doc.text("Output", 95, 182);
34707
- doc.text("Target", 135, 182);
34708
- doc.text("Status", 170, 182);
34709
- doc.setLineWidth(0.2);
34710
- doc.setDrawColor(220, 220, 220);
34711
- doc.line(20, 185, 190, 185);
35346
+ doc.text("Time Range", 25, 188);
35347
+ doc.text("Output", 95, 188);
35348
+ doc.text("Target", 135, 188);
35349
+ doc.text("Status", 170, 188);
35350
+ doc.setLineWidth(0.3);
35351
+ doc.setDrawColor(200, 200, 200);
35352
+ doc.line(20, 191, 190, 191);
34712
35353
  doc.setFont("helvetica", "normal");
34713
- let yPos = 191;
34714
- const hourlyData = workspace.hourly_action_counts || [];
34715
- const hourlyTarget = workspace.pph_threshold;
35354
+ let yPos = tableStartY;
35355
+ const workspaceDate = new Date(workspace.date);
35356
+ const today = /* @__PURE__ */ new Date();
35357
+ today.setHours(0, 0, 0, 0);
35358
+ workspaceDate.setHours(0, 0, 0, 0);
35359
+ const isToday = workspaceDate.getTime() === today.getTime();
35360
+ let currentHour = 24;
35361
+ if (isToday) {
35362
+ const now2 = /* @__PURE__ */ new Date();
35363
+ const currentTimeIST = new Date(now2.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
35364
+ currentHour = currentTimeIST.getHours();
35365
+ }
34716
35366
  hourlyData.forEach((output, index) => {
34717
- if (index % 2 === 0) {
34718
- doc.setFillColor(252, 252, 252);
34719
- doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
34720
- }
34721
35367
  const startTime = /* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`);
34722
35368
  startTime.setHours(startTime.getHours() + index);
34723
35369
  const endTime = new Date(startTime);
@@ -34729,14 +35375,17 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34729
35375
  hour: "numeric",
34730
35376
  hour12: true
34731
35377
  })}`;
34732
- const outputStr = output.toString();
35378
+ const hourNumber = startTime.getHours();
35379
+ const dataCollected = !isToday || hourNumber < currentHour;
35380
+ const outputStr = dataCollected ? output.toString() : "TBD";
34733
35381
  const targetStr = hourlyTarget.toString();
34734
- const outputX = 95 + doc.getStringUnitWidth(outputStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
34735
- const targetX = 135 + doc.getStringUnitWidth(targetStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
34736
35382
  doc.text(timeRange, 25, yPos);
34737
- doc.text(outputStr, outputX, yPos);
34738
- doc.text(targetStr, targetX, yPos);
34739
- if (output >= hourlyTarget) {
35383
+ doc.text(outputStr, 95, yPos);
35384
+ doc.text(targetStr, 135, yPos);
35385
+ if (!dataCollected) {
35386
+ doc.setTextColor(100, 100, 100);
35387
+ doc.text("-", 170, yPos);
35388
+ } else if (output >= hourlyTarget) {
34740
35389
  doc.setTextColor(0, 171, 69);
34741
35390
  doc.setFont("ZapfDingbats", "normal");
34742
35391
  doc.text("4", 170, yPos);
@@ -34746,15 +35395,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34746
35395
  doc.text("\xD7", 170, yPos);
34747
35396
  }
34748
35397
  doc.setTextColor(0, 0, 0);
34749
- yPos += 8;
35398
+ yPos += rowHeight;
34750
35399
  });
34751
- doc.setLineWidth(0.2);
34752
- doc.setDrawColor(220, 220, 220);
34753
- doc.roundedRect(20, 187, 170, yPos - 187 - 3, 1, 1, "S");
34754
- doc.setFontSize(9);
34755
- doc.setTextColor(130, 130, 130);
34756
- const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
34757
- doc.text(generatedText, 20, 280);
34758
35400
  doc.save(`${workspace.workspace_name}_${date.split(",")[0]}.pdf`);
34759
35401
  } catch (error) {
34760
35402
  console.error("PDF generation failed:", error);
@@ -35834,7 +36476,8 @@ var WorkspaceHealthCard = ({
35834
36476
  workspace,
35835
36477
  onClick,
35836
36478
  showDetails = true,
35837
- className = ""
36479
+ className = "",
36480
+ onViewDetails
35838
36481
  }) => {
35839
36482
  const getStatusConfig = () => {
35840
36483
  switch (workspace.status) {
@@ -35887,38 +36530,101 @@ var WorkspaceHealthCard = ({
35887
36530
  onClick(workspace);
35888
36531
  }
35889
36532
  };
36533
+ const handleViewDetails = (event) => {
36534
+ event.stopPropagation();
36535
+ event.preventDefault();
36536
+ if (onViewDetails) {
36537
+ onViewDetails(workspace);
36538
+ }
36539
+ };
35890
36540
  const handleKeyDown = (event) => {
35891
36541
  if (onClick && (event.key === "Enter" || event.key === " ")) {
35892
36542
  event.preventDefault();
35893
36543
  onClick(workspace);
35894
36544
  }
35895
36545
  };
36546
+ const formatDuration3 = (minutes) => {
36547
+ if (!minutes || minutes <= 0) return "0 min";
36548
+ const rounded = Math.max(Math.round(minutes), 0);
36549
+ const days = Math.floor(rounded / 1440);
36550
+ const hours = Math.floor(rounded % 1440 / 60);
36551
+ const mins = rounded % 60;
36552
+ const parts = [];
36553
+ if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
36554
+ if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
36555
+ if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
36556
+ if (!parts.length) {
36557
+ parts.push("1 min");
36558
+ }
36559
+ return parts.join(" ");
36560
+ };
35896
36561
  const formatTimeAgo = (timeString) => {
35897
- return timeString.replace("about ", "").replace(" ago", "");
36562
+ if (!timeString) return "Unknown";
36563
+ const cleaned = timeString.replace("about ", "").trim();
36564
+ if (cleaned.toLowerCase() === "never") return "Never";
36565
+ const minuteMatch = cleaned.match(/(\d+)\s*m/);
36566
+ if (!minuteMatch) {
36567
+ return cleaned;
36568
+ }
36569
+ const minutes = parseInt(minuteMatch[1], 10);
36570
+ if (!Number.isFinite(minutes)) return cleaned;
36571
+ if (minutes < 1) return "Just now";
36572
+ if (minutes < 60) {
36573
+ return `${minutes} min ago`;
36574
+ }
36575
+ const hours = Math.floor(minutes / 60);
36576
+ const remainingMinutes = minutes % 60;
36577
+ if (hours < 24) {
36578
+ const parts2 = [`${hours} hr${hours === 1 ? "" : "s"}`];
36579
+ if (remainingMinutes) {
36580
+ parts2.push(`${remainingMinutes} min`);
36581
+ }
36582
+ return `${parts2.join(" ")} ago`;
36583
+ }
36584
+ const days = Math.floor(hours / 24);
36585
+ const remainingHours = hours % 24;
36586
+ const parts = [`${days} day${days === 1 ? "" : "s"}`];
36587
+ if (remainingHours) {
36588
+ parts.push(`${remainingHours} hr${remainingHours === 1 ? "" : "s"}`);
36589
+ }
36590
+ return `${parts.join(" ")} ago`;
35898
36591
  };
35899
- const formatDowntime = (uptimeDetails) => {
35900
- if (!uptimeDetails) return "";
36592
+ const getDowntimeConfig = (uptimeDetails) => {
36593
+ if (!uptimeDetails) {
36594
+ if (workspace.status === "healthy") {
36595
+ return {
36596
+ text: "0m",
36597
+ className: "text-emerald-600 dark:text-emerald-400",
36598
+ label: "Total Downtime"
36599
+ };
36600
+ }
36601
+ return { text: "--", className: "text-slate-400", label: "Total Downtime" };
36602
+ }
35901
36603
  const downtimeMinutes = Math.max(0, uptimeDetails.expectedMinutes - uptimeDetails.actualMinutes);
35902
- if (downtimeMinutes === 0) return "No downtime";
35903
- if (downtimeMinutes < 1) return "< 1 min downtime";
35904
- if (downtimeMinutes < 60) return `${downtimeMinutes} min downtime`;
35905
- const hours = Math.floor(downtimeMinutes / 60);
35906
- const minutes = downtimeMinutes % 60;
35907
- if (minutes === 0) {
35908
- return `${hours} hr downtime`;
36604
+ if (downtimeMinutes === 0) {
36605
+ return {
36606
+ text: "0m",
36607
+ className: "text-emerald-600 dark:text-emerald-400",
36608
+ label: "Total Downtime"
36609
+ };
35909
36610
  }
35910
- return `${hours} hr ${minutes} min downtime`;
36611
+ return {
36612
+ text: `${formatDuration3(downtimeMinutes)}`,
36613
+ className: downtimeMinutes > 60 ? "text-rose-600 dark:text-rose-400" : "text-amber-600 dark:text-amber-400",
36614
+ label: "Total Downtime"
36615
+ };
35911
36616
  };
36617
+ const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
35912
36618
  return /* @__PURE__ */ jsx(
35913
36619
  Card2,
35914
36620
  {
35915
36621
  className: clsx(
35916
- "relative overflow-hidden transition-all duration-300",
36622
+ "relative overflow-hidden transition-all duration-300 h-full flex flex-col",
35917
36623
  "bg-gradient-to-br",
35918
36624
  config.gradient,
35919
36625
  "border",
35920
36626
  config.border,
35921
- "shadow-sm hover:shadow-md",
36627
+ "shadow-sm hover:shadow-lg hover:border-blue-300 dark:hover:border-blue-700",
35922
36628
  onClick && "cursor-pointer hover:scale-[1.01]",
35923
36629
  workspace.isStale && "opacity-90",
35924
36630
  className
@@ -35928,7 +36634,7 @@ var WorkspaceHealthCard = ({
35928
36634
  tabIndex: onClick ? 0 : void 0,
35929
36635
  role: onClick ? "button" : void 0,
35930
36636
  "aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
35931
- children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
36637
+ children: /* @__PURE__ */ jsxs("div", { className: "p-4 flex-grow flex flex-col", children: [
35932
36638
  /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start gap-2 sm:gap-3", children: [
35933
36639
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
35934
36640
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1 break-words", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 8)}` }),
@@ -35950,21 +36656,20 @@ var WorkspaceHealthCard = ({
35950
36656
  /* @__PURE__ */ jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
35951
36657
  ] })
35952
36658
  ] }),
35953
- workspace.uptimePercentage !== void 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
35954
- /* @__PURE__ */ jsx(Activity, { className: "h-3.5 w-3.5 text-gray-400" }),
35955
- /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap", children: [
35956
- "Uptime today: ",
35957
- /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
35958
- workspace.uptimePercentage.toFixed(1),
35959
- "%"
35960
- ] })
35961
- ] })
35962
- ] }),
35963
- workspace.uptimeDetails && workspace.uptimeDetails.expectedMinutes > workspace.uptimeDetails.actualMinutes && workspace.uptimePercentage !== void 0 && /* @__PURE__ */ jsx("div", { className: "flex", children: /* @__PURE__ */ jsx("span", { className: clsx(
35964
- "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
35965
- workspace.uptimePercentage >= 97 ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400" : workspace.uptimePercentage >= 90 ? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400" : "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
35966
- ), children: formatDowntime(workspace.uptimeDetails) }) })
35967
- ] })
36659
+ /* @__PURE__ */ jsx("div", { className: "mt-3 pt-3 border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
36660
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: "Total Downtime" }),
36661
+ /* @__PURE__ */ jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
36662
+ ] }) })
36663
+ ] }),
36664
+ onViewDetails && /* @__PURE__ */ jsx("div", { className: "pt-4 mt-auto", children: /* @__PURE__ */ jsx(
36665
+ "button",
36666
+ {
36667
+ onClick: handleViewDetails,
36668
+ className: "w-full inline-flex items-center justify-center rounded-lg border border-slate-200/60 bg-white/60 px-3 py-2 text-sm font-medium text-slate-700 shadow-sm backdrop-blur-sm transition-all hover:bg-white/90 hover:text-slate-900 dark:bg-white/10 dark:text-slate-200 dark:border-white/10 dark:hover:bg-white/20",
36669
+ type: "button",
36670
+ children: "View shift timeline"
36671
+ }
36672
+ ) })
35968
36673
  ] })
35969
36674
  }
35970
36675
  );
@@ -35972,8 +36677,11 @@ var WorkspaceHealthCard = ({
35972
36677
  var CompactWorkspaceHealthCard = ({
35973
36678
  workspace,
35974
36679
  onClick,
35975
- className = ""
36680
+ className = "",
36681
+ onViewDetails
35976
36682
  }) => {
36683
+ const downtimeMinutes = workspace.uptimeDetails ? Math.max(0, workspace.uptimeDetails.expectedMinutes - workspace.uptimeDetails.actualMinutes) : null;
36684
+ const downtimeLabel = downtimeMinutes === null ? "No downtime data" : downtimeMinutes === 0 ? "No downtime" : `${downtimeMinutes} min down`;
35977
36685
  const getStatusConfig = () => {
35978
36686
  switch (workspace.status) {
35979
36687
  case "healthy":
@@ -36013,6 +36721,13 @@ var CompactWorkspaceHealthCard = ({
36013
36721
  onClick(workspace);
36014
36722
  }
36015
36723
  };
36724
+ const handleViewDetails = (event) => {
36725
+ event.stopPropagation();
36726
+ event.preventDefault();
36727
+ if (onViewDetails) {
36728
+ onViewDetails(workspace);
36729
+ }
36730
+ };
36016
36731
  return /* @__PURE__ */ jsxs(
36017
36732
  "div",
36018
36733
  {
@@ -36040,14 +36755,20 @@ var CompactWorkspaceHealthCard = ({
36040
36755
  workspace.uptimePercentage.toFixed(1),
36041
36756
  "%"
36042
36757
  ] }),
36043
- workspace.uptimeDetails && workspace.uptimeDetails.expectedMinutes > workspace.uptimeDetails.actualMinutes && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 dark:text-gray-400", children: "\u2022" }),
36044
- workspace.uptimeDetails && workspace.uptimeDetails.expectedMinutes > workspace.uptimeDetails.actualMinutes && /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: [
36045
- Math.max(0, workspace.uptimeDetails.expectedMinutes - workspace.uptimeDetails.actualMinutes),
36046
- "m down"
36047
- ] })
36758
+ downtimeMinutes !== null && downtimeMinutes > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 dark:text-gray-400", children: "\u2022" }),
36759
+ downtimeLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: downtimeLabel })
36048
36760
  ] }),
36049
36761
  /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
36050
- /* @__PURE__ */ jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
36762
+ /* @__PURE__ */ jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) }),
36763
+ onViewDetails && /* @__PURE__ */ jsx(
36764
+ "button",
36765
+ {
36766
+ onClick: handleViewDetails,
36767
+ className: "rounded-full border border-gray-200 px-2 py-0.5 text-[11px] font-medium text-gray-600 hover:bg-gray-50",
36768
+ type: "button",
36769
+ children: "View"
36770
+ }
36771
+ )
36051
36772
  ] })
36052
36773
  ]
36053
36774
  }
@@ -36056,6 +36777,7 @@ var CompactWorkspaceHealthCard = ({
36056
36777
  var HealthStatusGrid = ({
36057
36778
  workspaces,
36058
36779
  onWorkspaceClick,
36780
+ onWorkspaceViewDetails,
36059
36781
  showFilters = true,
36060
36782
  groupBy: initialGroupBy = "none",
36061
36783
  className = ""
@@ -36243,7 +36965,8 @@ var HealthStatusGrid = ({
36243
36965
  {
36244
36966
  workspace,
36245
36967
  onClick: onWorkspaceClick,
36246
- showDetails: true
36968
+ showDetails: true,
36969
+ onViewDetails: onWorkspaceViewDetails
36247
36970
  },
36248
36971
  workspace.workspace_id
36249
36972
  )) })
@@ -39779,7 +40502,7 @@ var AIAgentView = () => {
39779
40502
  }
39780
40503
  return formattedLines.join("");
39781
40504
  };
39782
- const formatTime3 = (timestamp) => {
40505
+ const formatTime5 = (timestamp) => {
39783
40506
  const date = new Date(timestamp);
39784
40507
  return date.toLocaleTimeString([], {
39785
40508
  hour: "2-digit",
@@ -41043,7 +41766,7 @@ var AIAgentView = () => {
41043
41766
  }
41044
41767
  ),
41045
41768
  /* @__PURE__ */ jsxs("div", { className: `mt-1.5 sm:mt-2 flex items-center gap-2 text-xs text-gray-400 ${message.role === "user" ? "justify-end" : "justify-start"}`, children: [
41046
- /* @__PURE__ */ jsx("span", { children: formatTime3(message.created_at) }),
41769
+ /* @__PURE__ */ jsx("span", { children: formatTime5(message.created_at) }),
41047
41770
  message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
41048
41771
  /* @__PURE__ */ jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
41049
41772
  /* @__PURE__ */ jsx("span", { children: "Axel" })
@@ -43874,8 +44597,7 @@ var KPIsOverviewView = ({
43874
44597
  " Shift"
43875
44598
  ] })
43876
44599
  ] })
43877
- ] }) }),
43878
- /* @__PURE__ */ jsx("p", { className: "text-xs sm:text-sm text-gray-600 text-center mt-2 sm:mt-3 px-2 sm:px-0", children: "Click on any line to view detailed performance metrics" })
44600
+ ] }) })
43879
44601
  ] }) }),
43880
44602
  /* @__PURE__ */ jsx("main", { className: "flex-1 p-3 sm:p-4 md:p-6 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 sm:gap-4 md:gap-6", children: lines.map((line) => /* @__PURE__ */ jsx(
43881
44603
  LineCard,
@@ -48192,18 +48914,41 @@ var useWorkspaceHealth = (options) => {
48192
48914
  const isFetchingRef = useRef(false);
48193
48915
  const refreshIntervalRef = useRef(null);
48194
48916
  const healthTable = databaseConfig?.tables?.workspace_health || "workspace_health_status";
48195
- const calculateHealthStatus = (workspace) => {
48196
- if (!workspace.last_heartbeat) return "unknown";
48197
- const now2 = /* @__PURE__ */ new Date();
48198
- const lastHeartbeat = new Date(workspace.last_heartbeat);
48199
- const minutesSince = (now2.getTime() - lastHeartbeat.getTime()) / 6e4;
48200
- if (minutesSince < 5) return "healthy";
48201
- if (minutesSince < 15) return "warning";
48202
- return "unhealthy";
48203
- };
48917
+ const computeSummary = useCallback((data) => {
48918
+ const total = data.length;
48919
+ const healthy = data.filter((w) => w.status === "healthy").length;
48920
+ const unhealthy = data.filter((w) => w.status === "unhealthy").length;
48921
+ const warning6 = data.filter((w) => w.status === "warning").length;
48922
+ let uptimePercentage = total > 0 ? healthy / total * 100 : 100;
48923
+ let totalDowntimeMinutes;
48924
+ const withUptime = data.filter(
48925
+ (w) => w.uptimePercentage !== void 0 && w.uptimeDetails !== void 0
48926
+ );
48927
+ if (withUptime.length > 0) {
48928
+ const totalUptime = withUptime.reduce((sum, w) => sum + (w.uptimePercentage || 0), 0);
48929
+ uptimePercentage = totalUptime / withUptime.length;
48930
+ totalDowntimeMinutes = withUptime.reduce((sum, w) => {
48931
+ if (w.uptimeDetails) {
48932
+ return sum + Math.max(0, w.uptimeDetails.expectedMinutes - w.uptimeDetails.actualMinutes);
48933
+ }
48934
+ return sum;
48935
+ }, 0);
48936
+ }
48937
+ return {
48938
+ totalWorkspaces: total,
48939
+ healthyWorkspaces: healthy,
48940
+ unhealthyWorkspaces: unhealthy,
48941
+ warningWorkspaces: warning6,
48942
+ uptimePercentage,
48943
+ totalDowntimeMinutes,
48944
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
48945
+ };
48946
+ }, []);
48204
48947
  const fetchWorkspacesHealth = useCallback(async () => {
48205
- if (!supabase || isFetchingRef.current) return;
48948
+ if (isFetchingRef.current) return;
48206
48949
  if (!options.companyId) {
48950
+ setWorkspaces([]);
48951
+ setSummary(computeSummary([]));
48207
48952
  setLoading(false);
48208
48953
  return;
48209
48954
  }
@@ -48211,39 +48956,12 @@ var useWorkspaceHealth = (options) => {
48211
48956
  isFetchingRef.current = true;
48212
48957
  setLoading(true);
48213
48958
  setError(null);
48214
- let query = supabase.from(healthTable).select("*").eq("company_id", options.companyId);
48215
- if (options.lineId) {
48216
- query = query.eq("line_id", options.lineId);
48217
- }
48218
- query = query.order("workspace_display_name", { ascending: true });
48219
- const { data, error: fetchError } = await query;
48220
- if (fetchError) throw fetchError;
48221
- const healthData = data || [];
48222
- const workspacesWithStatus = healthData.map((ws) => {
48223
- const status = calculateHealthStatus(ws);
48224
- const now2 = /* @__PURE__ */ new Date();
48225
- const lastUpdate = ws.last_heartbeat ? new Date(ws.last_heartbeat) : null;
48226
- const timeSinceLastUpdate = lastUpdate ? `${Math.floor((now2.getTime() - lastUpdate.getTime()) / 6e4)}m ago` : "Never";
48227
- return {
48228
- ...ws,
48229
- status,
48230
- timeSinceLastUpdate,
48231
- isStale: !lastUpdate || now2.getTime() - lastUpdate.getTime() > 15 * 6e4
48232
- };
48959
+ const workspacesWithStatus = await workspaceHealthService.getWorkspaceHealthStatus({
48960
+ lineId: options.lineId,
48961
+ companyId: options.companyId
48233
48962
  });
48234
48963
  setWorkspaces(workspacesWithStatus);
48235
- const total = workspacesWithStatus.length;
48236
- const healthy = workspacesWithStatus.filter((w) => w.status === "healthy").length;
48237
- const unhealthy = workspacesWithStatus.filter((w) => w.status === "unhealthy").length;
48238
- const warning6 = workspacesWithStatus.filter((w) => w.status === "warning").length;
48239
- setSummary({
48240
- totalWorkspaces: total,
48241
- healthyWorkspaces: healthy,
48242
- unhealthyWorkspaces: unhealthy,
48243
- warningWorkspaces: warning6,
48244
- uptimePercentage: total > 0 ? healthy / total * 100 : 100,
48245
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
48246
- });
48964
+ setSummary(computeSummary(workspacesWithStatus));
48247
48965
  } catch (err) {
48248
48966
  console.error("[useWorkspaceHealth] Error fetching workspace health:", err);
48249
48967
  setError({ message: err.message, code: err.code || "FETCH_ERROR" });
@@ -48252,7 +48970,7 @@ var useWorkspaceHealth = (options) => {
48252
48970
  setLoading(false);
48253
48971
  isFetchingRef.current = false;
48254
48972
  }
48255
- }, [supabase, options.companyId, options.lineId, healthTable]);
48973
+ }, [options.companyId, options.lineId, computeSummary]);
48256
48974
  useEffect(() => {
48257
48975
  fetchWorkspacesHealth();
48258
48976
  }, [fetchWorkspacesHealth]);
@@ -48297,6 +49015,395 @@ var useWorkspaceHealth = (options) => {
48297
49015
  refetch: fetchWorkspacesHealth
48298
49016
  };
48299
49017
  };
49018
+ var STATUS_COLORS = {
49019
+ up: "bg-emerald-500",
49020
+ down: "bg-rose-500",
49021
+ pending: "bg-gray-200"
49022
+ };
49023
+ var STATUS_TITLES = {
49024
+ up: "Uptime",
49025
+ down: "Downtime",
49026
+ pending: "Pending"
49027
+ };
49028
+ var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
49029
+ hour: "numeric",
49030
+ minute: "2-digit",
49031
+ hour12: true,
49032
+ timeZone: timezone
49033
+ }).format(date);
49034
+ var formatDuration = (minutes) => {
49035
+ if (minutes < 1) return "<1 min";
49036
+ if (minutes < 60) return `${minutes} min`;
49037
+ const hours = Math.floor(minutes / 60);
49038
+ const remainder = minutes % 60;
49039
+ if (remainder === 0) return `${hours} hr${hours > 1 ? "s" : ""}`;
49040
+ return `${hours} hr ${remainder} min`;
49041
+ };
49042
+ var formatDowntimeLabel = (minutes, includeSuffix = true) => {
49043
+ if (!minutes || minutes <= 0) {
49044
+ return includeSuffix ? "No downtime" : "0 min";
49045
+ }
49046
+ const rounded = Math.max(Math.round(minutes), 0);
49047
+ const days = Math.floor(rounded / 1440);
49048
+ const hours = Math.floor(rounded % 1440 / 60);
49049
+ const mins = rounded % 60;
49050
+ const parts = [];
49051
+ if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
49052
+ if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
49053
+ if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
49054
+ if (!parts.length) {
49055
+ parts.push("1 min");
49056
+ }
49057
+ const label = parts.join(" ");
49058
+ return includeSuffix ? `${label} down` : label;
49059
+ };
49060
+ var UptimeTimelineStrip = ({
49061
+ points,
49062
+ totalMinutes,
49063
+ shiftStart,
49064
+ shiftEnd,
49065
+ timezone,
49066
+ className = "",
49067
+ uptimePercentage = 0,
49068
+ downtimeMinutes = 0
49069
+ }) => {
49070
+ const segments = useMemo(() => {
49071
+ if (!points.length || totalMinutes <= 0) return [];
49072
+ const result = [];
49073
+ let current = {
49074
+ status: points[0].status,
49075
+ length: 1,
49076
+ startMinuteIndex: points[0].minuteIndex,
49077
+ startTimestamp: points[0].timestamp,
49078
+ endTimestamp: points[0].timestamp
49079
+ };
49080
+ for (let i = 1; i < points.length; i++) {
49081
+ const point = points[i];
49082
+ if (point.status === current.status) {
49083
+ current.length += 1;
49084
+ current.endTimestamp = point.timestamp;
49085
+ } else {
49086
+ result.push(current);
49087
+ current = {
49088
+ status: point.status,
49089
+ length: 1,
49090
+ startMinuteIndex: point.minuteIndex,
49091
+ startTimestamp: point.timestamp,
49092
+ endTimestamp: point.timestamp
49093
+ };
49094
+ }
49095
+ }
49096
+ result.push(current);
49097
+ return result;
49098
+ }, [points, totalMinutes]);
49099
+ const markers = useMemo(() => {
49100
+ if (totalMinutes <= 0) return [];
49101
+ const startDate = new Date(shiftStart);
49102
+ const endDate = new Date(shiftEnd);
49103
+ const roundedTotal = Math.max(totalMinutes, 1);
49104
+ const markerInterval = totalMinutes > 360 ? 120 : 60;
49105
+ const markerList = [];
49106
+ for (let minute = 0; minute <= roundedTotal; minute += markerInterval) {
49107
+ const markerMinute = Math.min(minute, roundedTotal);
49108
+ const markerDate = addMinutes(startDate, markerMinute);
49109
+ markerList.push({
49110
+ minute: markerMinute,
49111
+ label: formatTime4(markerDate, timezone)
49112
+ });
49113
+ }
49114
+ const endLabel = formatTime4(endDate, timezone);
49115
+ if (!markerList.some((marker) => marker.minute === roundedTotal)) {
49116
+ markerList.push({
49117
+ minute: roundedTotal,
49118
+ label: endLabel
49119
+ });
49120
+ } else {
49121
+ markerList[markerList.length - 1] = {
49122
+ minute: roundedTotal,
49123
+ label: endLabel
49124
+ };
49125
+ }
49126
+ return markerList;
49127
+ }, [shiftStart, shiftEnd, timezone, totalMinutes]);
49128
+ if (!points.length || totalMinutes <= 0) {
49129
+ return /* @__PURE__ */ jsx("div", { className: "w-full rounded-xl border border-dashed border-gray-200 bg-gray-50/50 p-6 text-center text-sm text-gray-600", children: "No uptime data available for this shift yet." });
49130
+ }
49131
+ return /* @__PURE__ */ jsxs("div", { className: `relative w-full ${className}`, children: [
49132
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: /* @__PURE__ */ jsxs("span", { className: "text-gray-900 font-semibold", children: [
49133
+ uptimePercentage.toFixed(1),
49134
+ " % uptime ",
49135
+ downtimeMinutes > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
49136
+ "(",
49137
+ formatDowntimeLabel(downtimeMinutes),
49138
+ ")"
49139
+ ] })
49140
+ ] }) }),
49141
+ /* @__PURE__ */ jsx("div", { className: "relative flex h-4 overflow-hidden rounded-lg border border-gray-200 shadow-sm bg-white", children: segments.map((segment, index) => {
49142
+ const startDate = new Date(segment.startTimestamp);
49143
+ const endDate = new Date(segment.endTimestamp);
49144
+ if (segment.length > 1) {
49145
+ endDate.setMinutes(endDate.getMinutes() + 1);
49146
+ }
49147
+ const tooltip = `${STATUS_TITLES[segment.status]} \u2022 ${formatDuration(segment.length)} (${formatTime4(
49148
+ startDate,
49149
+ timezone
49150
+ )} - ${formatTime4(endDate, timezone)})`;
49151
+ return /* @__PURE__ */ jsx(
49152
+ "div",
49153
+ {
49154
+ className: `${STATUS_COLORS[segment.status]} transition-all hover:opacity-80 cursor-pointer`,
49155
+ style: { flex: segment.length },
49156
+ title: tooltip
49157
+ },
49158
+ `${segment.status}-${segment.startMinuteIndex}-${index}`
49159
+ );
49160
+ }) }),
49161
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none relative w-full mt-4 min-h-6", children: markers.map((marker) => {
49162
+ const left = totalMinutes > 0 ? marker.minute / totalMinutes * 100 : 0;
49163
+ return /* @__PURE__ */ jsxs(
49164
+ "div",
49165
+ {
49166
+ className: "absolute flex -translate-x-1/2 flex-col items-center",
49167
+ style: { left: `${left}%` },
49168
+ children: [
49169
+ /* @__PURE__ */ jsx("span", { className: "mb-1.5 h-2 w-px bg-gray-300" }),
49170
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-gray-600 whitespace-nowrap", children: marker.label })
49171
+ ]
49172
+ },
49173
+ `${marker.label}-${marker.minute}`
49174
+ );
49175
+ }) })
49176
+ ] });
49177
+ };
49178
+ var UptimeTimelineStrip_default = UptimeTimelineStrip;
49179
+ var SHORT_INTERRUPT_THRESHOLD_MINUTES = 3;
49180
+ var formatDuration2 = (minutes) => {
49181
+ if (!minutes || minutes <= 0) return "0 min";
49182
+ const rounded = Math.max(Math.round(minutes), 0);
49183
+ const days = Math.floor(rounded / 1440);
49184
+ const hours = Math.floor(rounded % 1440 / 60);
49185
+ const mins = rounded % 60;
49186
+ const parts = [];
49187
+ if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
49188
+ if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
49189
+ if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
49190
+ if (!parts.length) {
49191
+ parts.push("1 min");
49192
+ }
49193
+ return parts.join(" ");
49194
+ };
49195
+ var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
49196
+ if (!minutes || minutes <= 0) {
49197
+ return includeSuffix ? "No downtime" : "0 min";
49198
+ }
49199
+ const label = formatDuration2(minutes);
49200
+ return includeSuffix ? `${label} down` : label;
49201
+ };
49202
+ var formatTimeRange = (start, end, timezone) => {
49203
+ const formatter = new Intl.DateTimeFormat("en-IN", {
49204
+ hour: "numeric",
49205
+ minute: "2-digit",
49206
+ hour12: true,
49207
+ timeZone: timezone
49208
+ });
49209
+ return `${formatter.format(start)} - ${formatter.format(end)}`;
49210
+ };
49211
+ var WorkspaceUptimeDetailModal = ({
49212
+ workspace,
49213
+ isOpen,
49214
+ onClose
49215
+ }) => {
49216
+ const timezone = useAppTimezone() || "UTC";
49217
+ const logsContainerRef = useRef(null);
49218
+ const [showScrollIndicator, setShowScrollIndicator] = useState(false);
49219
+ const {
49220
+ timeline,
49221
+ loading,
49222
+ error,
49223
+ refetch
49224
+ } = useWorkspaceUptimeTimeline({
49225
+ workspaceId: workspace?.workspace_id,
49226
+ companyId: workspace?.company_id,
49227
+ enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
49228
+ refreshInterval: 6e4
49229
+ });
49230
+ useEffect(() => {
49231
+ if (!isOpen || !workspace) return;
49232
+ const handleKeyDown = (event) => {
49233
+ if (event.key === "Escape") {
49234
+ onClose();
49235
+ }
49236
+ };
49237
+ window.addEventListener("keydown", handleKeyDown);
49238
+ return () => {
49239
+ window.removeEventListener("keydown", handleKeyDown);
49240
+ };
49241
+ }, [isOpen, onClose, workspace]);
49242
+ const shiftStart = timeline ? new Date(timeline.shiftStart) : null;
49243
+ const shiftEnd = timeline ? new Date(timeline.shiftEnd) : null;
49244
+ const downtimeSegments = timeline?.downtimeSegments || [];
49245
+ downtimeSegments.length;
49246
+ const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
49247
+ const uptimePercentage = timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? 0;
49248
+ const allInterruptionsSorted = useMemo(
49249
+ () => [...downtimeSegments].sort(
49250
+ (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
49251
+ ),
49252
+ [downtimeSegments]
49253
+ );
49254
+ useEffect(() => {
49255
+ const checkScroll = () => {
49256
+ const container2 = logsContainerRef.current;
49257
+ if (container2) {
49258
+ const hasScroll = container2.scrollHeight > container2.clientHeight;
49259
+ const isAtBottom = container2.scrollHeight - container2.scrollTop <= container2.clientHeight + 10;
49260
+ setShowScrollIndicator(hasScroll && !isAtBottom);
49261
+ }
49262
+ };
49263
+ checkScroll();
49264
+ const container = logsContainerRef.current;
49265
+ if (container) {
49266
+ container.addEventListener("scroll", checkScroll);
49267
+ return () => container.removeEventListener("scroll", checkScroll);
49268
+ }
49269
+ }, [downtimeSegments]);
49270
+ const renderSegment = (segment) => {
49271
+ const start = new Date(segment.startTime);
49272
+ const end = new Date(segment.endTime);
49273
+ const isMajor = segment.durationMinutes >= SHORT_INTERRUPT_THRESHOLD_MINUTES;
49274
+ const containerClasses = isMajor ? "border-rose-200 bg-rose-50" : "border-gray-200 bg-white";
49275
+ return /* @__PURE__ */ jsxs(
49276
+ "div",
49277
+ {
49278
+ className: `rounded-lg border px-5 py-3 ${containerClasses}`,
49279
+ children: [
49280
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-gray-900", children: formatTimeRange(start, end, timezone) }),
49281
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 mt-1", children: [
49282
+ "Duration: ",
49283
+ formatDowntimeLabel2(segment.durationMinutes, false)
49284
+ ] })
49285
+ ]
49286
+ },
49287
+ `${segment.startMinuteIndex}-${segment.endMinuteIndex}`
49288
+ );
49289
+ };
49290
+ if (!isOpen || !workspace) {
49291
+ return null;
49292
+ }
49293
+ return /* @__PURE__ */ jsx(
49294
+ "div",
49295
+ {
49296
+ className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4",
49297
+ onClick: onClose,
49298
+ "aria-modal": "true",
49299
+ role: "dialog",
49300
+ "aria-labelledby": "uptime-detail-title",
49301
+ children: /* @__PURE__ */ jsxs(
49302
+ "div",
49303
+ {
49304
+ className: "relative flex w-full max-w-4xl max-h-[90vh] flex-col rounded-2xl bg-white shadow-2xl border border-gray-100",
49305
+ onClick: (event) => event.stopPropagation(),
49306
+ role: "document",
49307
+ "aria-labelledby": "uptime-detail-title",
49308
+ children: [
49309
+ /* @__PURE__ */ jsxs("header", { className: "flex items-start justify-between border-b border-gray-100 px-8 py-6 sticky top-0 z-10 bg-white rounded-t-2xl", children: [
49310
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
49311
+ /* @__PURE__ */ jsx("h2", { id: "uptime-detail-title", className: "text-2xl font-semibold text-gray-900 truncate mb-3", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 6)}` }),
49312
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
49313
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: timeline?.shiftLabel || "Current Shift" }),
49314
+ shiftStart && shiftEnd && /* @__PURE__ */ jsxs(Fragment, { children: [
49315
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
49316
+ /* @__PURE__ */ jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
49317
+ ] })
49318
+ ] }),
49319
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
49320
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
49321
+ /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${workspace.status === "healthy" ? "bg-emerald-500 animate-pulse" : workspace.status === "warning" ? "bg-amber-500" : "bg-rose-500"}` }),
49322
+ /* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${workspace.status === "healthy" ? "text-emerald-700" : workspace.status === "warning" ? "text-amber-700" : "text-rose-700"}`, children: workspace.status === "healthy" ? "Operational" : workspace.status === "warning" ? "Intermittent" : "Down" })
49323
+ ] }),
49324
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
49325
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500", children: [
49326
+ "Last heartbeat ",
49327
+ workspace.timeSinceLastUpdate
49328
+ ] })
49329
+ ] })
49330
+ ] }),
49331
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
49332
+ /* @__PURE__ */ jsxs(
49333
+ "button",
49334
+ {
49335
+ onClick: refetch,
49336
+ disabled: loading,
49337
+ className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 transition-all duration-200 shadow-sm",
49338
+ children: [
49339
+ /* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }),
49340
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Refresh" })
49341
+ ]
49342
+ }
49343
+ ),
49344
+ /* @__PURE__ */ jsx(
49345
+ "button",
49346
+ {
49347
+ onClick: onClose,
49348
+ className: "rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 transition-colors duration-200",
49349
+ "aria-label": "Close uptime details",
49350
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
49351
+ }
49352
+ )
49353
+ ] })
49354
+ ] }),
49355
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-8 py-6 space-y-6", children: error ? /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-rose-100 bg-rose-50 p-5 text-sm text-rose-700", children: [
49356
+ /* @__PURE__ */ jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
49357
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-rose-600/90", children: error.message })
49358
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
49359
+ /* @__PURE__ */ jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
49360
+ /* @__PURE__ */ jsx(
49361
+ UptimeTimelineStrip_default,
49362
+ {
49363
+ points: timeline?.points || [],
49364
+ totalMinutes: timeline?.totalMinutes || 0,
49365
+ shiftStart: timeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
49366
+ shiftEnd: timeline?.shiftEnd || (/* @__PURE__ */ new Date()).toISOString(),
49367
+ timezone,
49368
+ uptimePercentage,
49369
+ downtimeMinutes
49370
+ }
49371
+ ),
49372
+ loading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
49373
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
49374
+ /* @__PURE__ */ jsx("span", { children: "Updating timeline\u2026" })
49375
+ ] })
49376
+ ] }),
49377
+ /* @__PURE__ */ jsxs("div", { className: "pt-4", children: [
49378
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
49379
+ downtimeSegments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No downtime events recorded for this shift." }) }) : /* @__PURE__ */ jsxs("div", { className: "relative", children: [
49380
+ /* @__PURE__ */ jsx(
49381
+ "div",
49382
+ {
49383
+ ref: logsContainerRef,
49384
+ className: "max-h-[400px] overflow-y-auto space-y-2 pr-2",
49385
+ style: {
49386
+ scrollbarWidth: "thin",
49387
+ scrollbarColor: "#CBD5E0 #F7FAFC"
49388
+ },
49389
+ children: allInterruptionsSorted.map((segment) => renderSegment(segment))
49390
+ }
49391
+ ),
49392
+ showScrollIndicator && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white via-white/80 to-transparent pointer-events-none flex items-end justify-center pb-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-gray-500 animate-bounce", children: [
49393
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" }),
49394
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Scroll for more" }),
49395
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
49396
+ ] }) })
49397
+ ] })
49398
+ ] })
49399
+ ] }) }) })
49400
+ ]
49401
+ }
49402
+ )
49403
+ }
49404
+ );
49405
+ };
49406
+ var WorkspaceUptimeDetailModal_default = WorkspaceUptimeDetailModal;
48300
49407
  var WorkspaceHealthView = ({
48301
49408
  lineId,
48302
49409
  companyId,
@@ -48306,6 +49413,7 @@ var WorkspaceHealthView = ({
48306
49413
  const router = useRouter();
48307
49414
  const [groupBy, setGroupBy] = useState("line");
48308
49415
  const timezone = useAppTimezone();
49416
+ const [selectedWorkspace, setSelectedWorkspace] = useState(null);
48309
49417
  const operationalDate = getOperationalDate(timezone || "UTC");
48310
49418
  const currentHour = (/* @__PURE__ */ new Date()).getHours();
48311
49419
  const isNightShift = currentHour >= 18 || currentHour < 6;
@@ -48346,6 +49454,12 @@ var WorkspaceHealthView = ({
48346
49454
  },
48347
49455
  [router, onNavigate]
48348
49456
  );
49457
+ const handleViewDetails = useCallback((workspace) => {
49458
+ setSelectedWorkspace(workspace);
49459
+ }, []);
49460
+ const handleCloseDetails = useCallback(() => {
49461
+ setSelectedWorkspace(null);
49462
+ }, []);
48349
49463
  const handleExport = useCallback(() => {
48350
49464
  const csv = [
48351
49465
  ["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
@@ -48403,178 +49517,189 @@ var WorkspaceHealthView = ({
48403
49517
  )
48404
49518
  ] }) }) }) });
48405
49519
  }
48406
- return /* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
48407
- /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-2 sm:py-2.5 lg:py-3 flex flex-col shadow-sm bg-white", children: [
48408
- /* @__PURE__ */ jsxs("div", { className: "sm:hidden", children: [
48409
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
48410
- /* @__PURE__ */ jsx(
49520
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
49521
+ /* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
49522
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-2 sm:py-2.5 lg:py-3 flex flex-col shadow-sm bg-white", children: [
49523
+ /* @__PURE__ */ jsxs("div", { className: "sm:hidden", children: [
49524
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
49525
+ /* @__PURE__ */ jsx(
49526
+ BackButtonMinimal,
49527
+ {
49528
+ onClick: () => router.push("/"),
49529
+ text: "Back",
49530
+ size: "sm",
49531
+ "aria-label": "Navigate back to dashboard"
49532
+ }
49533
+ ),
49534
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
49535
+ /* @__PURE__ */ jsx(
49536
+ "button",
49537
+ {
49538
+ onClick: () => {
49539
+ refetch();
49540
+ },
49541
+ className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49542
+ "aria-label": "Refresh",
49543
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
49544
+ }
49545
+ ),
49546
+ /* @__PURE__ */ jsx(
49547
+ "button",
49548
+ {
49549
+ onClick: handleExport,
49550
+ className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49551
+ "aria-label": "Export CSV",
49552
+ children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
49553
+ }
49554
+ )
49555
+ ] })
49556
+ ] }),
49557
+ /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
49558
+ /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
49559
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
49560
+ /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
49561
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
49562
+ ] })
49563
+ ] }) })
49564
+ ] }),
49565
+ /* @__PURE__ */ jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
49566
+ /* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
48411
49567
  BackButtonMinimal,
48412
49568
  {
48413
49569
  onClick: () => router.push("/"),
48414
49570
  text: "Back",
48415
- size: "sm",
49571
+ size: "default",
48416
49572
  "aria-label": "Navigate back to dashboard"
48417
49573
  }
48418
- ),
48419
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
49574
+ ) }),
49575
+ /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
49576
+ /* @__PURE__ */ jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: "System Health" }),
49577
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
49578
+ /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
49579
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
49580
+ ] })
49581
+ ] }) }),
49582
+ /* @__PURE__ */ jsxs("div", { className: "absolute right-0 flex gap-2", children: [
48420
49583
  /* @__PURE__ */ jsx(
48421
49584
  "button",
48422
49585
  {
48423
49586
  onClick: () => {
48424
49587
  refetch();
48425
49588
  },
48426
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49589
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48427
49590
  "aria-label": "Refresh",
48428
- children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
49591
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
48429
49592
  }
48430
49593
  ),
48431
49594
  /* @__PURE__ */ jsx(
48432
49595
  "button",
48433
49596
  {
48434
49597
  onClick: handleExport,
48435
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49598
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48436
49599
  "aria-label": "Export CSV",
48437
- children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
49600
+ children: /* @__PURE__ */ jsx(Download, { className: "h-5 w-5" })
48438
49601
  }
48439
49602
  )
48440
- ] })
48441
- ] }),
48442
- /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
48443
- /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
48444
- /* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
48445
- /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
48446
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
49603
+ ] }),
49604
+ /* @__PURE__ */ jsx("div", { className: "w-full h-8" })
49605
+ ] }) }),
49606
+ /* @__PURE__ */ jsx("div", { className: "mt-1 sm:mt-2 bg-blue-50 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-4", children: [
49607
+ /* @__PURE__ */ jsx("div", { className: "text-sm sm:text-base md:text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsx(LiveTimer, {}) }),
49608
+ /* @__PURE__ */ jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
49609
+ /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
49610
+ /* @__PURE__ */ jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
49611
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
49612
+ /* @__PURE__ */ jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
49613
+ /* @__PURE__ */ jsxs("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: [
49614
+ shiftType,
49615
+ " Shift"
49616
+ ] })
48447
49617
  ] })
48448
49618
  ] }) })
48449
49619
  ] }),
48450
- /* @__PURE__ */ jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
48451
- /* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
48452
- BackButtonMinimal,
49620
+ /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
49621
+ summary && /* @__PURE__ */ jsxs(
49622
+ motion.div,
48453
49623
  {
48454
- onClick: () => router.push("/"),
48455
- text: "Back",
48456
- size: "default",
48457
- "aria-label": "Navigate back to dashboard"
48458
- }
48459
- ) }),
48460
- /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
48461
- /* @__PURE__ */ jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: "System Health" }),
48462
- /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
48463
- /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
48464
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
48465
- ] })
48466
- ] }) }),
48467
- /* @__PURE__ */ jsxs("div", { className: "absolute right-0 flex gap-2", children: [
48468
- /* @__PURE__ */ jsx(
48469
- "button",
48470
- {
48471
- onClick: () => {
48472
- refetch();
48473
- },
48474
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48475
- "aria-label": "Refresh",
48476
- children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
48477
- }
48478
- ),
48479
- /* @__PURE__ */ jsx(
48480
- "button",
48481
- {
48482
- onClick: handleExport,
48483
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48484
- "aria-label": "Export CSV",
48485
- children: /* @__PURE__ */ jsx(Download, { className: "h-5 w-5" })
48486
- }
48487
- )
48488
- ] }),
48489
- /* @__PURE__ */ jsx("div", { className: "w-full h-8" })
48490
- ] }) }),
48491
- /* @__PURE__ */ jsx("div", { className: "mt-2 sm:mt-3 bg-blue-50 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-4", children: [
48492
- /* @__PURE__ */ jsx("div", { className: "text-sm sm:text-base md:text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsx(LiveTimer, {}) }),
48493
- /* @__PURE__ */ jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
48494
- /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
48495
- /* @__PURE__ */ jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
48496
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
48497
- /* @__PURE__ */ jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
48498
- /* @__PURE__ */ jsxs("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: [
48499
- shiftType,
48500
- " Shift"
48501
- ] })
48502
- ] })
48503
- ] }) })
48504
- ] }),
48505
- /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
48506
- summary && /* @__PURE__ */ jsxs(
48507
- motion.div,
48508
- {
48509
- initial: { opacity: 0, y: 20 },
48510
- animate: { opacity: 1, y: 0 },
48511
- transition: { duration: 0.3, delay: 0.1 },
48512
- className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
48513
- children: [
48514
- /* @__PURE__ */ jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
48515
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "System Availability" }) }),
48516
- /* @__PURE__ */ jsxs(CardContent2, { children: [
48517
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
48518
- /* @__PURE__ */ jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
48519
- summary.uptimePercentage.toFixed(1),
48520
- "%"
49624
+ initial: { opacity: 0, y: 20 },
49625
+ animate: { opacity: 1, y: 0 },
49626
+ transition: { duration: 0.3, delay: 0.1 },
49627
+ className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
49628
+ children: [
49629
+ /* @__PURE__ */ jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
49630
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "System Availability" }) }),
49631
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
49632
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
49633
+ /* @__PURE__ */ jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
49634
+ summary.uptimePercentage.toFixed(1),
49635
+ "%"
49636
+ ] }),
49637
+ summary.uptimePercentage >= 97 ? /* @__PURE__ */ jsx(TrendingUp, { className: "h-5 w-5 text-green-500" }) : summary.uptimePercentage >= 90 ? /* @__PURE__ */ jsx(Activity, { className: "h-5 w-5 text-yellow-500" }) : /* @__PURE__ */ jsx(TrendingDown, { className: "h-5 w-5 text-red-500" })
48521
49638
  ] }),
48522
- summary.uptimePercentage >= 97 ? /* @__PURE__ */ jsx(TrendingUp, { className: "h-5 w-5 text-green-500" }) : summary.uptimePercentage >= 90 ? /* @__PURE__ */ jsx(Activity, { className: "h-5 w-5 text-yellow-500" }) : /* @__PURE__ */ jsx(TrendingDown, { className: "h-5 w-5 text-red-500" })
48523
- ] }),
48524
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Overall system uptime today" })
48525
- ] })
48526
- ] }),
48527
- /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
48528
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
48529
- getStatusIcon("healthy"),
48530
- "Healthy"
48531
- ] }) }),
48532
- /* @__PURE__ */ jsxs(CardContent2, { children: [
48533
- /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.healthyWorkspaces }),
48534
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
48535
- ] })
48536
- ] }),
48537
- /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
48538
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
48539
- getStatusIcon("warning"),
48540
- "Warning"
48541
- ] }) }),
48542
- /* @__PURE__ */ jsxs(CardContent2, { children: [
48543
- /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.warningWorkspaces }),
48544
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
48545
- ] })
48546
- ] }),
48547
- /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
48548
- /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
48549
- getStatusIcon("unhealthy"),
48550
- "Unhealthy"
48551
- ] }) }),
48552
- /* @__PURE__ */ jsxs(CardContent2, { children: [
48553
- /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.unhealthyWorkspaces }),
48554
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
49639
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Overall system uptime today" })
49640
+ ] })
49641
+ ] }),
49642
+ /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
49643
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49644
+ getStatusIcon("healthy"),
49645
+ "Healthy"
49646
+ ] }) }),
49647
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
49648
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.healthyWorkspaces }),
49649
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
49650
+ ] })
49651
+ ] }),
49652
+ /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
49653
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49654
+ getStatusIcon("warning"),
49655
+ "Warning"
49656
+ ] }) }),
49657
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
49658
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.warningWorkspaces }),
49659
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
49660
+ ] })
49661
+ ] }),
49662
+ /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
49663
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49664
+ getStatusIcon("unhealthy"),
49665
+ "Unhealthy"
49666
+ ] }) }),
49667
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
49668
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.unhealthyWorkspaces }),
49669
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
49670
+ ] })
48555
49671
  ] })
48556
- ] })
48557
- ]
48558
- }
48559
- ),
48560
- /* @__PURE__ */ jsx(
48561
- motion.div,
48562
- {
48563
- initial: { opacity: 0, y: 20 },
48564
- animate: { opacity: 1, y: 0 },
48565
- transition: { duration: 0.3, delay: 0.2 },
48566
- children: /* @__PURE__ */ jsx(
48567
- HealthStatusGrid,
48568
- {
48569
- workspaces,
48570
- onWorkspaceClick: handleWorkspaceClick,
48571
- showFilters: true,
48572
- groupBy
48573
- }
48574
- )
48575
- }
48576
- )
48577
- ] })
49672
+ ]
49673
+ }
49674
+ ),
49675
+ /* @__PURE__ */ jsx(
49676
+ motion.div,
49677
+ {
49678
+ initial: { opacity: 0, y: 20 },
49679
+ animate: { opacity: 1, y: 0 },
49680
+ transition: { duration: 0.3, delay: 0.2 },
49681
+ children: /* @__PURE__ */ jsx(
49682
+ HealthStatusGrid,
49683
+ {
49684
+ workspaces,
49685
+ onWorkspaceClick: handleWorkspaceClick,
49686
+ onWorkspaceViewDetails: handleViewDetails,
49687
+ showFilters: true,
49688
+ groupBy
49689
+ }
49690
+ )
49691
+ }
49692
+ )
49693
+ ] })
49694
+ ] }),
49695
+ /* @__PURE__ */ jsx(
49696
+ WorkspaceUptimeDetailModal_default,
49697
+ {
49698
+ workspace: selectedWorkspace,
49699
+ isOpen: Boolean(selectedWorkspace),
49700
+ onClose: handleCloseDetails
49701
+ }
49702
+ )
48578
49703
  ] });
48579
49704
  };
48580
49705
  var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
@@ -52143,4 +53268,4 @@ function shuffleArray(array) {
52143
53268
  return shuffled;
52144
53269
  }
52145
53270
 
52146
- export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ClipFilterProvider, CompactWorkspaceHealthCard, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, InteractiveOnboardingTour, InvitationService, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserManagementService, UserService, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createInvitationService, createLinesService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getNextUpdateInterval, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
53271
+ export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ClipFilterProvider, CompactWorkspaceHealthCard, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, InteractiveOnboardingTour, InvitationService, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserManagementService, UserService, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createInvitationService, createLinesService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getNextUpdateInterval, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };