@optifye/dashboard-core 6.9.12 → 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.js CHANGED
@@ -2163,6 +2163,88 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2163
2163
  setCache(key, data) {
2164
2164
  this.cache.set(key, { data, timestamp: Date.now() });
2165
2165
  }
2166
+ getShiftTiming(timezone, shiftConfig) {
2167
+ const { shiftId, date } = getCurrentShift(timezone, shiftConfig);
2168
+ const dayShiftId = shiftConfig?.dayShift?.id ?? 0;
2169
+ const isDayShift = shiftId === dayShiftId;
2170
+ const defaultDayStart = "06:00";
2171
+ const defaultDayEnd = "18:00";
2172
+ const defaultNightStart = "18:00";
2173
+ const defaultNightEnd = "06:00";
2174
+ const shiftStartStr = isDayShift ? shiftConfig?.dayShift?.startTime || defaultDayStart : shiftConfig?.nightShift?.startTime || defaultNightStart;
2175
+ const shiftEndStr = isDayShift ? shiftConfig?.dayShift?.endTime || defaultDayEnd : shiftConfig?.nightShift?.endTime || defaultNightEnd;
2176
+ const shiftLabel = isDayShift ? "Day Shift" : "Night Shift";
2177
+ const parseTime = (value) => {
2178
+ const [hourPart = "0", minutePart = "0"] = value.split(":");
2179
+ const hour = Number.parseInt(hourPart, 10);
2180
+ const minute = Number.parseInt(minutePart, 10);
2181
+ return {
2182
+ hour: Number.isFinite(hour) ? hour : 0,
2183
+ minute: Number.isFinite(minute) ? minute : 0
2184
+ };
2185
+ };
2186
+ const getShiftDurationMinutes = (start, end) => {
2187
+ const startParsed = parseTime(start);
2188
+ const endParsed = parseTime(end);
2189
+ const startTotal = startParsed.hour * 60 + startParsed.minute;
2190
+ const endTotal = endParsed.hour * 60 + endParsed.minute;
2191
+ let duration = endTotal - startTotal;
2192
+ if (duration <= 0) {
2193
+ duration += 24 * 60;
2194
+ }
2195
+ return duration;
2196
+ };
2197
+ const shiftStartDate = dateFnsTz.fromZonedTime(`${date}T${shiftStartStr}:00`, timezone);
2198
+ const totalMinutes = getShiftDurationMinutes(shiftStartStr, shiftEndStr);
2199
+ const shiftEndDate = dateFns.addMinutes(shiftStartDate, totalMinutes);
2200
+ const now2 = /* @__PURE__ */ new Date();
2201
+ let completedMinutes = dateFns.differenceInMinutes(now2 < shiftEndDate ? now2 : shiftEndDate, shiftStartDate);
2202
+ if (completedMinutes < 0) completedMinutes = 0;
2203
+ if (completedMinutes > totalMinutes) completedMinutes = totalMinutes;
2204
+ const pendingMinutes = Math.max(0, totalMinutes - completedMinutes);
2205
+ return {
2206
+ shiftId,
2207
+ date,
2208
+ shiftLabel,
2209
+ shiftStartDate,
2210
+ shiftEndDate,
2211
+ totalMinutes,
2212
+ completedMinutes,
2213
+ pendingMinutes
2214
+ };
2215
+ }
2216
+ normalizeHourBucket(bucket) {
2217
+ if (Array.isArray(bucket)) return bucket;
2218
+ if (bucket && Array.isArray(bucket.values)) return bucket.values;
2219
+ return void 0;
2220
+ }
2221
+ normalizeOutputHourly(outputHourlyRaw) {
2222
+ return outputHourlyRaw && typeof outputHourlyRaw === "object" ? Object.fromEntries(
2223
+ Object.entries(outputHourlyRaw).map(([key, value]) => [key, this.normalizeHourBucket(value) || []])
2224
+ ) : {};
2225
+ }
2226
+ interpretUptimeValue(value) {
2227
+ if (value === null || value === void 0) return "down";
2228
+ if (typeof value === "string") {
2229
+ return value.trim().toLowerCase() === "x" ? "down" : "up";
2230
+ }
2231
+ return "up";
2232
+ }
2233
+ deriveStatusForMinute(minuteOffset, minuteDate, outputHourly, outputArray, timezone) {
2234
+ const hourKey = dateFnsTz.formatInTimeZone(minuteDate, timezone, "H");
2235
+ const minuteKey = Number.parseInt(dateFnsTz.formatInTimeZone(minuteDate, timezone, "m"), 10);
2236
+ const hourBucket = outputHourly[hourKey];
2237
+ if (Array.isArray(hourBucket)) {
2238
+ const value = hourBucket[minuteKey];
2239
+ if (value !== void 0) {
2240
+ return this.interpretUptimeValue(value);
2241
+ }
2242
+ }
2243
+ if (minuteOffset < outputArray.length) {
2244
+ return this.interpretUptimeValue(outputArray[minuteOffset]);
2245
+ }
2246
+ return "down";
2247
+ }
2166
2248
  async getWorkspaceHealthStatus(options = {}) {
2167
2249
  const supabase = _getSupabaseInstance();
2168
2250
  if (!supabase) throw new Error("Supabase client not initialized");
@@ -2179,15 +2261,13 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2179
2261
  throw error;
2180
2262
  }
2181
2263
  const processedData = (data || []).map((item) => this.processHealthStatus(item));
2264
+ const companyId = options.companyId || data?.[0]?.company_id;
2182
2265
  let uptimeMap = /* @__PURE__ */ new Map();
2183
- if (options.companyId || data && data.length > 0) {
2184
- const companyId = options.companyId || data[0]?.company_id;
2185
- if (companyId) {
2186
- try {
2187
- uptimeMap = await this.calculateWorkspaceUptime(companyId);
2188
- } catch (error2) {
2189
- console.error("Error calculating uptime:", error2);
2190
- }
2266
+ if (companyId) {
2267
+ try {
2268
+ uptimeMap = await this.calculateWorkspaceUptime(companyId);
2269
+ } catch (error2) {
2270
+ console.error("Error calculating uptime:", error2);
2191
2271
  }
2192
2272
  }
2193
2273
  const dataWithUptime = processedData.map((workspace) => {
@@ -2203,16 +2283,29 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2203
2283
  });
2204
2284
  let filteredData = dataWithUptime;
2205
2285
  try {
2206
- const { data: enabledWorkspaces, error: workspaceError } = await supabase.from("workspaces").select("workspace_id, display_name").eq("enable", true);
2286
+ const enabledQuery = supabase.from("workspaces").select("workspace_id, display_name, line_id").eq("enable", true);
2287
+ if (options.lineId) {
2288
+ enabledQuery.eq("line_id", options.lineId);
2289
+ }
2290
+ const { data: enabledWorkspaces, error: workspaceError } = await enabledQuery;
2207
2291
  if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length > 0) {
2208
- const enabledWorkspaceNames = /* @__PURE__ */ new Set();
2292
+ const enabledByLineAndId = /* @__PURE__ */ new Set();
2293
+ const enabledByLineAndName = /* @__PURE__ */ new Set();
2209
2294
  enabledWorkspaces.forEach((w) => {
2210
- if (w.workspace_id) enabledWorkspaceNames.add(w.workspace_id);
2211
- if (w.display_name) enabledWorkspaceNames.add(w.display_name);
2295
+ const lineKey = w.line_id ? String(w.line_id) : "";
2296
+ const idKey = w.workspace_id ? String(w.workspace_id) : "";
2297
+ const nameKey = w.display_name ? String(w.display_name) : "";
2298
+ if (lineKey && idKey) enabledByLineAndId.add(`${lineKey}::${idKey}`);
2299
+ if (lineKey && nameKey) enabledByLineAndName.add(`${lineKey}::${nameKey}`);
2212
2300
  });
2213
2301
  filteredData = filteredData.filter((item) => {
2214
- const displayName = item.workspace_display_name || "";
2215
- return enabledWorkspaceNames.has(displayName);
2302
+ const lineKey = item.line_id ? String(item.line_id) : "";
2303
+ if (!lineKey) return false;
2304
+ const idKey = item.workspace_id ? `${lineKey}::${item.workspace_id}` : "";
2305
+ const nameKey = item.workspace_display_name ? `${lineKey}::${item.workspace_display_name}` : "";
2306
+ if (idKey && enabledByLineAndId.has(idKey)) return true;
2307
+ if (nameKey && enabledByLineAndName.has(nameKey)) return true;
2308
+ return false;
2216
2309
  });
2217
2310
  } else if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length === 0) {
2218
2311
  return [];
@@ -2250,6 +2343,127 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2250
2343
  }
2251
2344
  return filteredData;
2252
2345
  }
2346
+ async getWorkspaceUptimeTimeline(workspaceId, companyId) {
2347
+ if (!workspaceId) {
2348
+ throw new Error("workspaceId is required to fetch uptime timeline");
2349
+ }
2350
+ if (!companyId) {
2351
+ throw new Error("companyId is required to fetch uptime timeline");
2352
+ }
2353
+ const supabase = _getSupabaseInstance();
2354
+ if (!supabase) throw new Error("Supabase client not initialized");
2355
+ const dashboardConfig = _getDashboardConfigInstance();
2356
+ const timezone = dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
2357
+ const shiftConfig = dashboardConfig?.shiftConfig;
2358
+ const {
2359
+ shiftId,
2360
+ shiftLabel,
2361
+ shiftStartDate,
2362
+ shiftEndDate,
2363
+ totalMinutes,
2364
+ completedMinutes,
2365
+ pendingMinutes,
2366
+ date
2367
+ } = this.getShiftTiming(timezone, shiftConfig);
2368
+ const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
2369
+ 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);
2370
+ if (error) {
2371
+ console.error("Error fetching uptime timeline metrics:", error);
2372
+ throw error;
2373
+ }
2374
+ const record = Array.isArray(data) && data.length > 0 ? data[0] : null;
2375
+ const outputHourly = this.normalizeOutputHourly(record?.output_hourly || {});
2376
+ const outputArray = Array.isArray(record?.output_array) ? record.output_array : [];
2377
+ const points = [];
2378
+ let uptimeMinutes = 0;
2379
+ let downtimeMinutes = 0;
2380
+ const MIN_DOWNTIME_MINUTES = 2;
2381
+ for (let minuteIndex = 0; minuteIndex < totalMinutes; minuteIndex++) {
2382
+ const minuteDate = dateFns.addMinutes(shiftStartDate, minuteIndex);
2383
+ const timestamp = dateFnsTz.formatInTimeZone(minuteDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
2384
+ let status;
2385
+ if (minuteIndex < completedMinutes) {
2386
+ status = this.deriveStatusForMinute(
2387
+ minuteIndex,
2388
+ minuteDate,
2389
+ outputHourly,
2390
+ outputArray,
2391
+ timezone
2392
+ );
2393
+ if (status === "up") {
2394
+ uptimeMinutes += 1;
2395
+ } else {
2396
+ downtimeMinutes += 1;
2397
+ }
2398
+ } else {
2399
+ status = "pending";
2400
+ }
2401
+ points.push({
2402
+ minuteIndex,
2403
+ timestamp,
2404
+ status
2405
+ });
2406
+ }
2407
+ const downtimeSegments = [];
2408
+ let currentSegmentStart = null;
2409
+ const pushSegment = (startIndex, endIndex) => {
2410
+ if (endIndex <= startIndex) return;
2411
+ const segmentStartDate = dateFns.addMinutes(shiftStartDate, startIndex);
2412
+ const segmentEndDate = dateFns.addMinutes(shiftStartDate, endIndex);
2413
+ downtimeSegments.push({
2414
+ startMinuteIndex: startIndex,
2415
+ endMinuteIndex: endIndex,
2416
+ startTime: dateFnsTz.formatInTimeZone(segmentStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2417
+ endTime: dateFnsTz.formatInTimeZone(segmentEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2418
+ durationMinutes: endIndex - startIndex
2419
+ });
2420
+ };
2421
+ for (let i = 0; i < completedMinutes; i++) {
2422
+ const point = points[i];
2423
+ if (point.status === "down") {
2424
+ if (currentSegmentStart === null) {
2425
+ currentSegmentStart = i;
2426
+ }
2427
+ } else if (currentSegmentStart !== null) {
2428
+ pushSegment(currentSegmentStart, i);
2429
+ currentSegmentStart = null;
2430
+ }
2431
+ }
2432
+ if (currentSegmentStart !== null) {
2433
+ pushSegment(currentSegmentStart, completedMinutes);
2434
+ }
2435
+ const filteredSegments = [];
2436
+ downtimeSegments.forEach((segment) => {
2437
+ if (segment.durationMinutes >= MIN_DOWNTIME_MINUTES) {
2438
+ filteredSegments.push(segment);
2439
+ } else {
2440
+ for (let i = segment.startMinuteIndex; i < segment.endMinuteIndex; i++) {
2441
+ if (points[i] && points[i].status === "down") {
2442
+ points[i].status = "up";
2443
+ downtimeMinutes = Math.max(0, downtimeMinutes - 1);
2444
+ uptimeMinutes += 1;
2445
+ }
2446
+ }
2447
+ }
2448
+ });
2449
+ const completedWindow = Math.max(1, uptimeMinutes + downtimeMinutes);
2450
+ const uptimePercentage = completedMinutes > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
2451
+ return {
2452
+ shiftId,
2453
+ shiftLabel,
2454
+ shiftStart: dateFnsTz.formatInTimeZone(shiftStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2455
+ shiftEnd: dateFnsTz.formatInTimeZone(shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
2456
+ totalMinutes,
2457
+ completedMinutes,
2458
+ uptimeMinutes,
2459
+ downtimeMinutes,
2460
+ pendingMinutes,
2461
+ uptimePercentage,
2462
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2463
+ points,
2464
+ downtimeSegments: filteredSegments
2465
+ };
2466
+ }
2253
2467
  async getWorkspaceHealthById(workspaceId) {
2254
2468
  const cacheKey = `health-${workspaceId}`;
2255
2469
  const cached = this.getFromCache(cacheKey);
@@ -2387,145 +2601,63 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2387
2601
  const dashboardConfig = _getDashboardConfigInstance();
2388
2602
  const timezone = dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
2389
2603
  const shiftConfig = dashboardConfig?.shiftConfig;
2390
- const currentShiftInfo = getCurrentShift(timezone, shiftConfig);
2391
- const currentDate = currentShiftInfo.date;
2392
- const currentShiftId = currentShiftInfo.shiftId;
2393
- const now2 = /* @__PURE__ */ new Date();
2394
- const currentHour = now2.getHours();
2395
- const currentMinute = now2.getMinutes();
2396
- const dayShiftStart = shiftConfig?.dayShift?.startTime || "08:00";
2397
- const dayShiftEnd = shiftConfig?.dayShift?.endTime || "19:30";
2398
- const nightShiftStart = shiftConfig?.nightShift?.startTime || "19:30";
2399
- const nightShiftEnd = shiftConfig?.nightShift?.endTime || "06:00";
2400
- const parseShiftTime = (timeStr) => {
2401
- const [hour, minute] = timeStr.split(":").map(Number);
2402
- return { hour, minute };
2403
- };
2404
- let shiftStart, shiftEnd;
2405
- if (currentShiftId === 0) {
2406
- shiftStart = parseShiftTime(dayShiftStart);
2407
- shiftEnd = parseShiftTime(dayShiftEnd);
2408
- } else {
2409
- shiftStart = parseShiftTime(nightShiftStart);
2410
- shiftEnd = parseShiftTime(nightShiftEnd);
2411
- }
2412
- let elapsedMinutes = 0;
2413
- if (currentShiftId === 0) {
2414
- const shiftStartTotalMinutes = shiftStart.hour * 60 + shiftStart.minute;
2415
- const currentTotalMinutes = currentHour * 60 + currentMinute;
2416
- const shiftEndTotalMinutes = shiftEnd.hour * 60 + shiftEnd.minute;
2417
- if (currentTotalMinutes >= shiftStartTotalMinutes && currentTotalMinutes < shiftEndTotalMinutes) {
2418
- elapsedMinutes = currentTotalMinutes - shiftStartTotalMinutes;
2419
- } else if (currentTotalMinutes >= shiftEndTotalMinutes) {
2420
- elapsedMinutes = shiftEndTotalMinutes - shiftStartTotalMinutes;
2421
- }
2422
- } else {
2423
- const shiftStartTotalMinutes = shiftStart.hour * 60 + shiftStart.minute;
2424
- const currentTotalMinutes = currentHour * 60 + currentMinute;
2425
- const shiftEndTotalMinutes = shiftEnd.hour * 60 + shiftEnd.minute;
2426
- if (currentHour >= shiftStart.hour || currentHour < shiftEnd.hour) {
2427
- if (currentHour >= shiftStart.hour) {
2428
- elapsedMinutes = currentTotalMinutes - shiftStartTotalMinutes;
2429
- } else {
2430
- elapsedMinutes = 24 * 60 - shiftStartTotalMinutes + currentTotalMinutes;
2431
- }
2432
- if (currentHour >= shiftEnd.hour && currentTotalMinutes >= shiftEndTotalMinutes) {
2433
- const totalShiftMinutes = 24 * 60 - shiftStartTotalMinutes + shiftEndTotalMinutes;
2434
- elapsedMinutes = Math.min(elapsedMinutes, totalShiftMinutes);
2435
- }
2436
- }
2437
- }
2604
+ const {
2605
+ shiftId,
2606
+ date,
2607
+ shiftStartDate,
2608
+ completedMinutes
2609
+ } = this.getShiftTiming(timezone, shiftConfig);
2438
2610
  const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
2439
2611
  try {
2440
- const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly").eq("date", currentDate).eq("shift_id", currentShiftId);
2612
+ 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);
2441
2613
  if (error) {
2442
2614
  console.error("Error fetching performance metrics:", error);
2443
2615
  return /* @__PURE__ */ new Map();
2444
2616
  }
2445
2617
  const uptimeMap = /* @__PURE__ */ new Map();
2446
2618
  for (const record of queryData || []) {
2447
- let actualMinutes = 0;
2448
- let totalPossibleMinutes = 0;
2449
- const outputHourly = record.output_hourly || {};
2450
- const hoursElapsed = Math.ceil(elapsedMinutes / 60);
2451
- for (let hourIndex = 0; hourIndex < hoursElapsed; hourIndex++) {
2452
- const actualHour = (shiftStart.hour + hourIndex) % 24;
2453
- let hourData = [];
2454
- let minutesInThisHour = 60;
2455
- if (shiftStart.minute > 0) {
2456
- if (hourIndex === 0) {
2457
- const firstHourData = outputHourly[actualHour.toString()] || [];
2458
- const nextHour = (actualHour + 1) % 24;
2459
- const nextHourData = outputHourly[nextHour.toString()] || [];
2460
- hourData = [
2461
- ...Array.isArray(firstHourData) ? firstHourData.slice(shiftStart.minute) : [],
2462
- ...Array.isArray(nextHourData) ? nextHourData.slice(0, shiftStart.minute) : []
2463
- ];
2464
- } else if (hourIndex < hoursElapsed - 1) {
2465
- const currentHourData = outputHourly[actualHour.toString()] || [];
2466
- const nextHour = (actualHour + 1) % 24;
2467
- const nextHourData = outputHourly[nextHour.toString()] || [];
2468
- hourData = [
2469
- ...Array.isArray(currentHourData) ? currentHourData.slice(shiftStart.minute) : [],
2470
- ...Array.isArray(nextHourData) ? nextHourData.slice(0, shiftStart.minute) : []
2471
- ];
2472
- } else {
2473
- const isLastHourPartial = hourIndex === hoursElapsed - 1 && elapsedMinutes % 60 > 0;
2474
- if (isLastHourPartial) {
2475
- minutesInThisHour = elapsedMinutes % 60;
2476
- const currentHourData = outputHourly[actualHour.toString()] || [];
2477
- if (shiftStart.minute > 0) {
2478
- const nextHour = (actualHour + 1) % 24;
2479
- const nextHourData = outputHourly[nextHour.toString()] || [];
2480
- const firstPart = Array.isArray(currentHourData) ? currentHourData.slice(shiftStart.minute) : [];
2481
- const secondPart = Array.isArray(nextHourData) ? nextHourData.slice(0, Math.min(shiftStart.minute, minutesInThisHour)) : [];
2482
- hourData = [...firstPart, ...secondPart].slice(0, minutesInThisHour);
2483
- } else {
2484
- hourData = Array.isArray(currentHourData) ? currentHourData.slice(0, minutesInThisHour) : [];
2485
- }
2486
- } else {
2487
- const currentHourData = outputHourly[actualHour.toString()] || [];
2488
- const nextHour = (actualHour + 1) % 24;
2489
- const nextHourData = outputHourly[nextHour.toString()] || [];
2490
- hourData = [
2491
- ...Array.isArray(currentHourData) ? currentHourData.slice(shiftStart.minute) : [],
2492
- ...Array.isArray(nextHourData) ? nextHourData.slice(0, shiftStart.minute) : []
2493
- ];
2494
- }
2495
- }
2619
+ const outputHourly = this.normalizeOutputHourly(record.output_hourly || {});
2620
+ const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
2621
+ let uptimeMinutes = 0;
2622
+ let downtimeMinutes = 0;
2623
+ const MIN_DOWNTIME_MINUTES = 2;
2624
+ let currentDownRun = 0;
2625
+ for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex++) {
2626
+ const minuteDate = dateFns.addMinutes(shiftStartDate, minuteIndex);
2627
+ const status = this.deriveStatusForMinute(
2628
+ minuteIndex,
2629
+ minuteDate,
2630
+ outputHourly,
2631
+ outputArray,
2632
+ timezone
2633
+ );
2634
+ if (status === "down") {
2635
+ currentDownRun += 1;
2496
2636
  } else {
2497
- hourData = outputHourly[actualHour.toString()] || [];
2498
- if (hourIndex === hoursElapsed - 1) {
2499
- const remainingMinutes = elapsedMinutes % 60;
2500
- if (remainingMinutes > 0) {
2501
- minutesInThisHour = remainingMinutes;
2502
- hourData = Array.isArray(hourData) ? hourData.slice(0, minutesInThisHour) : [];
2637
+ if (currentDownRun > 0) {
2638
+ if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
2639
+ downtimeMinutes += currentDownRun;
2640
+ } else {
2641
+ uptimeMinutes += currentDownRun;
2503
2642
  }
2643
+ currentDownRun = 0;
2504
2644
  }
2645
+ uptimeMinutes += 1;
2505
2646
  }
2506
- const hasXArchitecture = Array.isArray(hourData) && hourData.some((val) => val === "x");
2507
- if (hasXArchitecture) {
2508
- const xCount = hourData.filter((val) => val === "x").length;
2509
- if (xCount <= 2) {
2510
- actualMinutes += minutesInThisHour;
2511
- } else {
2512
- const uptimeCount = hourData.filter((val) => val !== "x").length;
2513
- actualMinutes += Math.min(uptimeCount, minutesInThisHour);
2514
- }
2647
+ }
2648
+ if (currentDownRun > 0) {
2649
+ if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
2650
+ downtimeMinutes += currentDownRun;
2515
2651
  } else {
2516
- const arrayLength = Array.isArray(hourData) ? hourData.length : 0;
2517
- if (arrayLength >= Math.min(58, minutesInThisHour - 2)) {
2518
- actualMinutes += minutesInThisHour;
2519
- } else {
2520
- actualMinutes += Math.min(arrayLength, minutesInThisHour);
2521
- }
2652
+ uptimeMinutes += currentDownRun;
2522
2653
  }
2523
- totalPossibleMinutes += minutesInThisHour;
2654
+ currentDownRun = 0;
2524
2655
  }
2525
- const percentage = totalPossibleMinutes > 0 ? Math.round(actualMinutes / totalPossibleMinutes * 1e3) / 10 : 100;
2656
+ const completedWindow = uptimeMinutes + downtimeMinutes;
2657
+ const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
2526
2658
  uptimeMap.set(record.workspace_id, {
2527
- expectedMinutes: totalPossibleMinutes,
2528
- actualMinutes,
2659
+ expectedMinutes: completedMinutes,
2660
+ actualMinutes: uptimeMinutes,
2529
2661
  percentage,
2530
2662
  lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
2531
2663
  });
@@ -10897,6 +11029,65 @@ var useWorkspaceHealthStatus = (workspaceId) => {
10897
11029
  refetch: fetchHealthStatus
10898
11030
  };
10899
11031
  };
11032
+ var useWorkspaceUptimeTimeline = (options) => {
11033
+ const {
11034
+ workspaceId,
11035
+ companyId,
11036
+ enabled = true,
11037
+ refreshInterval
11038
+ } = options;
11039
+ const [timeline, setTimeline] = React23.useState(null);
11040
+ const [loading, setLoading] = React23.useState(false);
11041
+ const [error, setError] = React23.useState(null);
11042
+ const isFetchingRef = React23.useRef(false);
11043
+ const intervalRef = React23.useRef(null);
11044
+ const fetchTimeline = React23.useCallback(async () => {
11045
+ if (!enabled) return;
11046
+ if (!workspaceId || !companyId) {
11047
+ setTimeline(null);
11048
+ return;
11049
+ }
11050
+ if (isFetchingRef.current) return;
11051
+ try {
11052
+ isFetchingRef.current = true;
11053
+ setLoading(true);
11054
+ setError(null);
11055
+ const data = await workspaceHealthService.getWorkspaceUptimeTimeline(
11056
+ workspaceId,
11057
+ companyId
11058
+ );
11059
+ setTimeline(data);
11060
+ } catch (err) {
11061
+ console.error("[useWorkspaceUptimeTimeline] Failed to fetch timeline:", err);
11062
+ setError({ message: err?.message || "Failed to load uptime timeline", code: err?.code || "FETCH_ERROR" });
11063
+ } finally {
11064
+ setLoading(false);
11065
+ isFetchingRef.current = false;
11066
+ }
11067
+ }, [enabled, workspaceId, companyId]);
11068
+ React23.useEffect(() => {
11069
+ fetchTimeline();
11070
+ }, [fetchTimeline]);
11071
+ React23.useEffect(() => {
11072
+ if (!refreshInterval || refreshInterval <= 0 || !enabled) {
11073
+ return;
11074
+ }
11075
+ intervalRef.current = setInterval(() => {
11076
+ fetchTimeline();
11077
+ }, refreshInterval);
11078
+ return () => {
11079
+ if (intervalRef.current) {
11080
+ clearInterval(intervalRef.current);
11081
+ }
11082
+ };
11083
+ }, [refreshInterval, enabled, fetchTimeline]);
11084
+ return {
11085
+ timeline,
11086
+ loading,
11087
+ error,
11088
+ refetch: fetchTimeline
11089
+ };
11090
+ };
10900
11091
  function useDateFormatter() {
10901
11092
  const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
10902
11093
  const formatDate = React23.useCallback(
@@ -10912,7 +11103,7 @@ function useDateFormatter() {
10912
11103
  },
10913
11104
  [defaultTimezone, defaultLocale, dateFormatOptions]
10914
11105
  );
10915
- const formatTime4 = React23.useCallback(
11106
+ const formatTime5 = React23.useCallback(
10916
11107
  (date, formatString) => {
10917
11108
  const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
10918
11109
  if (!dateFns.isValid(dateObj)) return "Invalid Time";
@@ -10943,7 +11134,7 @@ function useDateFormatter() {
10943
11134
  }, []);
10944
11135
  return {
10945
11136
  formatDate,
10946
- formatTime: formatTime4,
11137
+ formatTime: formatTime5,
10947
11138
  formatDateTime,
10948
11139
  getNow,
10949
11140
  timezone: defaultTimezone || "UTC",
@@ -23929,7 +24120,7 @@ var HourlyOutputChartComponent = ({
23929
24120
  endHour = Math.floor(endDecimalHour) % 24;
23930
24121
  endMinute = Math.round(endDecimalHour % 1 * 60);
23931
24122
  }
23932
- const formatTime4 = (h, m) => {
24123
+ const formatTime5 = (h, m) => {
23933
24124
  const period = h >= 12 ? "PM" : "AM";
23934
24125
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23935
24126
  if (m === 0) {
@@ -23937,9 +24128,9 @@ var HourlyOutputChartComponent = ({
23937
24128
  }
23938
24129
  return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
23939
24130
  };
23940
- return `${formatTime4(startHour, startMinute)}-${formatTime4(endHour, endMinute)}`;
24131
+ return `${formatTime5(startHour, startMinute)}-${formatTime5(endHour, endMinute)}`;
23941
24132
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23942
- const formatTimeRange = React23__namespace.default.useCallback((hourIndex) => {
24133
+ const formatTimeRange2 = React23__namespace.default.useCallback((hourIndex) => {
23943
24134
  const isLastHour = hourIndex === SHIFT_DURATION - 1;
23944
24135
  const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
23945
24136
  const startHour = Math.floor(startDecimalHour) % 24;
@@ -23953,12 +24144,12 @@ var HourlyOutputChartComponent = ({
23953
24144
  endHour = Math.floor(endDecimalHour) % 24;
23954
24145
  endMinute = Math.round(endDecimalHour % 1 * 60);
23955
24146
  }
23956
- const formatTime4 = (h, m) => {
24147
+ const formatTime5 = (h, m) => {
23957
24148
  const period = h >= 12 ? "PM" : "AM";
23958
24149
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23959
24150
  return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
23960
24151
  };
23961
- return `${formatTime4(startHour, startMinute)} - ${formatTime4(endHour, endMinute)}`;
24152
+ return `${formatTime5(startHour, startMinute)} - ${formatTime5(endHour, endMinute)}`;
23962
24153
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23963
24154
  const chartData = React23__namespace.default.useMemo(() => {
23964
24155
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
@@ -24019,7 +24210,7 @@ var HourlyOutputChartComponent = ({
24019
24210
  idleMinutes = idleArray.filter((val) => val === "1" || val === 1).length;
24020
24211
  return {
24021
24212
  hour: formatHour(i),
24022
- timeRange: formatTimeRange(i),
24213
+ timeRange: formatTimeRange2(i),
24023
24214
  output: animatedData[i] || 0,
24024
24215
  originalOutput: data[i] || 0,
24025
24216
  // Keep original data for labels
@@ -24028,7 +24219,7 @@ var HourlyOutputChartComponent = ({
24028
24219
  idleArray
24029
24220
  };
24030
24221
  });
24031
- }, [animatedData, data, pphThreshold, idleTimeHourly, shiftStartTime.hour, shiftStartTime.minute, shiftEndTime, formatHour, formatTimeRange, SHIFT_DURATION]);
24222
+ }, [animatedData, data, pphThreshold, idleTimeHourly, shiftStartTime.hour, shiftStartTime.minute, shiftEndTime, formatHour, formatTimeRange2, SHIFT_DURATION]);
24032
24223
  const IdleBar = React23__namespace.default.useMemo(() => {
24033
24224
  if (!idleBarState.visible) return null;
24034
24225
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -25129,7 +25320,7 @@ var SOPComplianceChart = ({
25129
25320
  }
25130
25321
  };
25131
25322
  }, [data, animateToNewData, mockData]);
25132
- const formatTime4 = (minuteIndex) => {
25323
+ const formatTime5 = (minuteIndex) => {
25133
25324
  const totalMinutes = shiftStartHour * 60 + minuteIndex;
25134
25325
  const hours = Math.floor(totalMinutes / 60) % 24;
25135
25326
  const minutes = totalMinutes % 60;
@@ -25141,7 +25332,7 @@ var SOPComplianceChart = ({
25141
25332
  const hasDataForMinute = index < animatedData.length - 10;
25142
25333
  return {
25143
25334
  minute: index,
25144
- time: formatTime4(index),
25335
+ time: formatTime5(index),
25145
25336
  compliance: hasDataForMinute ? animatedData[index] : null
25146
25337
  };
25147
25338
  });
@@ -25575,7 +25766,7 @@ var DateTimeDisplay = ({
25575
25766
  const {
25576
25767
  defaultTimezone
25577
25768
  } = useDateTimeConfig();
25578
- const { formatDate, formatTime: formatTime4 } = useDateFormatter();
25769
+ const { formatDate, formatTime: formatTime5 } = useDateFormatter();
25579
25770
  const [now2, setNow] = React23.useState(() => getCurrentTimeInZone(defaultTimezone || "UTC"));
25580
25771
  React23.useEffect(() => {
25581
25772
  const timerId = setInterval(() => {
@@ -25587,7 +25778,7 @@ var DateTimeDisplay = ({
25587
25778
  return null;
25588
25779
  }
25589
25780
  const formattedDate = showDate ? formatDate(now2) : "";
25590
- const formattedTime = showTime ? formatTime4(now2) : "";
25781
+ const formattedTime = showTime ? formatTime5(now2) : "";
25591
25782
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx_default("flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-300", className), children: [
25592
25783
  showDate && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "date-display", "aria-label": `Current date: ${formattedDate}`, children: formattedDate }),
25593
25784
  showDate && showTime && formattedDate && formattedTime && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "separator", "aria-hidden": "true", children: "|" }),
@@ -25751,7 +25942,7 @@ var BreakNotificationPopup = ({
25751
25942
  const handlePrevious = () => {
25752
25943
  setCurrentIndex((prev) => (prev - 1 + visibleBreaks.length) % visibleBreaks.length);
25753
25944
  };
25754
- const formatTime4 = (minutes) => {
25945
+ const formatTime5 = (minutes) => {
25755
25946
  const hours = Math.floor(minutes / 60);
25756
25947
  const mins = minutes % 60;
25757
25948
  if (hours > 0) {
@@ -25825,9 +26016,9 @@ var BreakNotificationPopup = ({
25825
26016
  formatTo12Hour(currentBreak.endTime)
25826
26017
  ] }),
25827
26018
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 mb-2", children: [
25828
- formatTime4(currentBreak.elapsedMinutes),
26019
+ formatTime5(currentBreak.elapsedMinutes),
25829
26020
  " elapsed of ",
25830
- formatTime4(currentBreak.duration),
26021
+ formatTime5(currentBreak.duration),
25831
26022
  " total"
25832
26023
  ] }),
25833
26024
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -31493,7 +31684,7 @@ function DiagnosisVideoModal({
31493
31684
  }
31494
31685
  loadClip();
31495
31686
  }, [clipId, supabase, transformPlaylistUrls]);
31496
- const formatTime4 = (seconds) => {
31687
+ const formatTime5 = (seconds) => {
31497
31688
  const mins = Math.floor(seconds / 60);
31498
31689
  const secs = Math.floor(seconds % 60);
31499
31690
  return `${mins}:${secs.toString().padStart(2, "0")}`;
@@ -31685,9 +31876,9 @@ function DiagnosisVideoModal({
31685
31876
  }
31686
31877
  ),
31687
31878
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium", children: [
31688
- formatTime4(currentTime),
31879
+ formatTime5(currentTime),
31689
31880
  " / ",
31690
- formatTime4(duration)
31881
+ formatTime5(duration)
31691
31882
  ] }),
31692
31883
  /* @__PURE__ */ jsxRuntime.jsx(
31693
31884
  "input",
@@ -33507,45 +33698,35 @@ var LinePdfGenerator = ({
33507
33698
  const doc = new jsPDF.jsPDF();
33508
33699
  const pageHeight = doc.internal.pageSize.height;
33509
33700
  const addHeaderPage1 = () => {
33510
- doc.setFontSize(12);
33701
+ doc.setFontSize(14);
33511
33702
  doc.setFont("helvetica", "bold");
33512
- doc.setTextColor(70, 70, 70);
33703
+ doc.setTextColor(50, 50, 50);
33513
33704
  doc.text("OPTIFYE.AI", 20, 15);
33514
- doc.setFontSize(10);
33705
+ doc.setFontSize(9);
33515
33706
  doc.setFont("helvetica", "normal");
33516
33707
  doc.setTextColor(100, 100, 100);
33517
- const reportText = "REAL-TIME PERFORMANCE REPORT";
33518
- const reportTextWidth = doc.getStringUnitWidth(reportText) * 10 / doc.internal.scaleFactor;
33519
- doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
33520
- doc.setDrawColor(220, 220, 220);
33521
- doc.setLineWidth(0.3);
33708
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
33709
+ const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
33710
+ doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
33711
+ doc.setDrawColor(200, 200, 200);
33712
+ doc.setLineWidth(0.5);
33522
33713
  doc.line(20, 20, 190, 20);
33523
33714
  };
33524
33715
  const addHeaderPage2 = () => {
33525
- doc.setFontSize(12);
33716
+ doc.setFontSize(14);
33526
33717
  doc.setFont("helvetica", "bold");
33527
- doc.setTextColor(70, 70, 70);
33718
+ doc.setTextColor(50, 50, 50);
33528
33719
  doc.text("OPTIFYE.AI", 20, 15);
33529
- doc.setFontSize(10);
33720
+ doc.setFontSize(9);
33530
33721
  doc.setFont("helvetica", "normal");
33531
33722
  doc.setTextColor(100, 100, 100);
33532
- const reportText = "REAL-TIME PERFORMANCE REPORT";
33533
- const reportTextWidth = doc.getStringUnitWidth(reportText) * 10 / doc.internal.scaleFactor;
33534
- doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
33535
- doc.text("Page 2", doc.internal.pageSize.width - 30, pageHeight - 15);
33536
- doc.setDrawColor(220, 220, 220);
33537
- doc.setLineWidth(0.3);
33538
- doc.line(20, 20, 190, 20);
33539
- return 35;
33540
- };
33541
- const addFooter = (pageNum) => {
33542
- doc.setFontSize(9);
33543
- doc.setTextColor(130, 130, 130);
33544
33723
  const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
33545
- doc.text(generatedText, 20, pageHeight - 15);
33546
- if (pageNum === 1) {
33547
- doc.text("Page 1", doc.internal.pageSize.width - 30, pageHeight - 15);
33548
- }
33724
+ const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
33725
+ doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
33726
+ doc.setDrawColor(200, 200, 200);
33727
+ doc.setLineWidth(0.5);
33728
+ doc.line(20, 20, 190, 20);
33729
+ return 30;
33549
33730
  };
33550
33731
  addHeaderPage1();
33551
33732
  doc.setFontSize(26);
@@ -33591,37 +33772,47 @@ var LinePdfGenerator = ({
33591
33772
  doc.setDrawColor(200, 200, 200);
33592
33773
  doc.setLineWidth(0.5);
33593
33774
  doc.line(20, 53, 190, 53);
33594
- doc.setFontSize(16);
33775
+ const perfOverviewStartY = 58;
33776
+ doc.setFillColor(245, 245, 245);
33777
+ doc.roundedRect(15, perfOverviewStartY, 180, 50, 3, 3, "F");
33778
+ doc.setFontSize(18);
33595
33779
  doc.setFont("helvetica", "bold");
33596
- doc.setTextColor(70, 70, 70);
33597
- doc.text("Line Performance Overview", 20, 65);
33780
+ doc.setTextColor(40, 40, 40);
33781
+ doc.text("Line Performance Overview", 20, 68);
33598
33782
  doc.setTextColor(0, 0, 0);
33599
33783
  const createKPIBox = (y) => {
33600
- doc.setFillColor(250, 250, 250);
33601
- doc.roundedRect(22, y - 7, 165, 12, 1, 1, "F");
33784
+ doc.setFillColor(255, 255, 255);
33785
+ doc.roundedRect(22, y - 7, 165, 12, 2, 2, "F");
33786
+ doc.setDrawColor(230, 230, 230);
33787
+ doc.setLineWidth(0.2);
33788
+ doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
33602
33789
  };
33603
- createKPIBox(77);
33604
- doc.setFontSize(12);
33790
+ const kpiStartY = 80;
33791
+ const kpiSpacing = 10;
33792
+ createKPIBox(kpiStartY);
33793
+ doc.setFontSize(11);
33605
33794
  doc.setFont("helvetica", "normal");
33606
- doc.text("Output:", 25, 77);
33795
+ doc.text("Output:", 25, kpiStartY);
33607
33796
  doc.setFont("helvetica", "bold");
33608
- doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, 77);
33609
- createKPIBox(87);
33797
+ doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
33798
+ createKPIBox(kpiStartY + kpiSpacing);
33610
33799
  doc.setFont("helvetica", "normal");
33611
- doc.text("Underperforming Workspaces:", 25, 87);
33800
+ doc.text("Underperforming Workspaces:", 25, kpiStartY + kpiSpacing);
33612
33801
  doc.setFont("helvetica", "bold");
33613
- doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120, 87);
33614
- createKPIBox(97);
33802
+ doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120, kpiStartY + kpiSpacing);
33803
+ createKPIBox(kpiStartY + kpiSpacing * 2);
33615
33804
  doc.setFont("helvetica", "normal");
33616
- doc.text("Average Efficiency:", 25, 97);
33805
+ doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing * 2);
33617
33806
  doc.setFont("helvetica", "bold");
33618
- doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, 97);
33619
- doc.setDrawColor(200, 200, 200);
33620
- doc.line(20, 110, 190, 110);
33621
- doc.setFontSize(16);
33807
+ doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120, kpiStartY + kpiSpacing * 2);
33808
+ doc.setDrawColor(180, 180, 180);
33809
+ doc.setLineWidth(0.8);
33810
+ doc.line(20, 118, 190, 118);
33811
+ const hourlyOverviewStartY = 123;
33812
+ doc.setFontSize(18);
33622
33813
  doc.setFont("helvetica", "bold");
33623
- doc.setTextColor(70, 70, 70);
33624
- doc.text("Hourly Output Overview", 20, 135);
33814
+ doc.setTextColor(40, 40, 40);
33815
+ doc.text("Hourly Output Overview", 20, 133);
33625
33816
  doc.setTextColor(0, 0, 0);
33626
33817
  const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
33627
33818
  const [hours, minutes] = startTimeStr.split(":");
@@ -33658,7 +33849,7 @@ var LinePdfGenerator = ({
33658
33849
  }
33659
33850
  hourEndTime.setSeconds(0);
33660
33851
  hourEndTime.setMilliseconds(0);
33661
- const formatTime4 = (date2) => {
33852
+ const formatTime5 = (date2) => {
33662
33853
  return date2.toLocaleTimeString("en-IN", {
33663
33854
  hour: "2-digit",
33664
33855
  minute: "2-digit",
@@ -33666,7 +33857,7 @@ var LinePdfGenerator = ({
33666
33857
  timeZone: "Asia/Kolkata"
33667
33858
  });
33668
33859
  };
33669
- return `${formatTime4(hourStartTime)} - ${formatTime4(hourEndTime)}`;
33860
+ return `${formatTime5(hourStartTime)} - ${formatTime5(hourEndTime)}`;
33670
33861
  });
33671
33862
  };
33672
33863
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
@@ -33813,86 +34004,94 @@ var LinePdfGenerator = ({
33813
34004
  return Math.round(lineInfo.metrics.current_output / shiftDuration);
33814
34005
  });
33815
34006
  }
34007
+ const tableHeaderY = 143;
34008
+ const tableStartY = 151;
34009
+ const rowSpacing = 8;
34010
+ const bottomPadding = 8;
34011
+ const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
34012
+ const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
34013
+ doc.setFillColor(245, 245, 245);
34014
+ doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
33816
34015
  doc.setFontSize(11);
33817
34016
  doc.setFont("helvetica", "bold");
33818
- doc.setFillColor(245, 245, 245);
33819
- doc.roundedRect(20, 140, 170, 8, 1, 1, "F");
33820
- doc.text("Time Range", 25, 145);
33821
- doc.text("Actual Output", 80, 145);
33822
- doc.text("Target Output", 125, 145);
33823
- doc.text("Status", 170, 145);
33824
- doc.setLineWidth(0.2);
33825
- doc.setDrawColor(220, 220, 220);
33826
- doc.line(20, 148, 190, 148);
34017
+ doc.text("Time Range", 25, tableHeaderY);
34018
+ doc.text("Output", 80, tableHeaderY);
34019
+ doc.text("Target", 125, tableHeaderY);
34020
+ doc.text("Status", 170, tableHeaderY);
34021
+ doc.setLineWidth(0.3);
34022
+ doc.setDrawColor(200, 200, 200);
34023
+ doc.line(20, 146, 190, 146);
33827
34024
  doc.setFont("helvetica", "normal");
33828
- let yPos = 155;
33829
- const rowSpacing = 7;
34025
+ let yPos = tableStartY;
34026
+ const lineDateForTable = new Date(lineInfo.date);
34027
+ const todayForTable = /* @__PURE__ */ new Date();
34028
+ todayForTable.setHours(0, 0, 0, 0);
34029
+ lineDateForTable.setHours(0, 0, 0, 0);
34030
+ const isTodayForTable = lineDateForTable.getTime() === todayForTable.getTime();
34031
+ let currentHour = 24;
34032
+ if (isTodayForTable) {
34033
+ const now2 = /* @__PURE__ */ new Date();
34034
+ const currentTimeIST = new Date(now2.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
34035
+ currentHour = currentTimeIST.getHours();
34036
+ }
33830
34037
  hourlyTimeRanges.forEach((timeRange, index) => {
33831
- if (index % 2 === 0) {
33832
- doc.setFillColor(252, 252, 252);
33833
- doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
33834
- }
33835
- const actualOutput = hourlyActualOutput[index];
33836
- const isOverTarget = actualOutput >= targetOutputPerHour;
34038
+ const actualOutput = hourlyActualOutput[index] || 0;
34039
+ const [startHourStr] = (lineInfo.metrics.shift_start || "6:00").split(":");
34040
+ const startHour = parseInt(startHourStr);
34041
+ const hourNumber = (startHour + index) % 24;
34042
+ const dataCollected = !isTodayForTable || hourNumber < currentHour;
34043
+ const outputStr = dataCollected ? actualOutput.toString() : "TBD";
34044
+ const targetStr = targetOutputPerHour.toString();
33837
34045
  doc.text(timeRange, 25, yPos);
33838
- doc.text(actualOutput.toString(), 80, yPos);
33839
- doc.text(targetOutputPerHour.toString(), 125, yPos);
33840
- if (isOverTarget) {
33841
- doc.setDrawColor(0, 171, 69);
33842
- doc.setLineWidth(0.5);
33843
- const tickX = 170;
33844
- const tickY = yPos - 1;
33845
- doc.line(tickX - 2, tickY, tickX, tickY + 1.5);
33846
- doc.line(tickX, tickY + 1.5, tickX + 2, tickY - 2);
34046
+ doc.text(outputStr, 80, yPos);
34047
+ doc.text(targetStr, 125, yPos);
34048
+ if (!dataCollected) {
34049
+ doc.setTextColor(100, 100, 100);
34050
+ doc.text("-", 170, yPos);
34051
+ } else if (actualOutput >= targetOutputPerHour) {
34052
+ doc.setTextColor(0, 171, 69);
34053
+ doc.setFont("ZapfDingbats", "normal");
34054
+ doc.text("4", 170, yPos);
34055
+ doc.setFont("helvetica", "normal");
33847
34056
  } else {
33848
- doc.setDrawColor(227, 67, 41);
33849
- doc.setLineWidth(0.5);
33850
- const crossX = 170;
33851
- const crossY = yPos - 1;
33852
- doc.line(crossX - 2, crossY - 2, crossX + 2, crossY + 2);
33853
- doc.line(crossX - 2, crossY + 2, crossX + 2, crossY - 2);
34057
+ doc.setTextColor(227, 67, 41);
34058
+ doc.text("\xD7", 170, yPos);
33854
34059
  }
33855
- doc.setDrawColor(220, 220, 220);
33856
- doc.setLineWidth(0.2);
34060
+ doc.setTextColor(0, 0, 0);
33857
34061
  yPos += rowSpacing;
33858
34062
  });
33859
- doc.roundedRect(20, 140, 170, yPos - 140 - 3, 1, 1, "S");
33860
- addFooter(1);
33861
34063
  doc.addPage();
33862
34064
  yPos = addHeaderPage2();
33863
- doc.setFontSize(16);
34065
+ const workspaceSectionStartY = yPos;
34066
+ const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
34067
+ const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
34068
+ const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
34069
+ doc.setFillColor(245, 245, 245);
34070
+ doc.roundedRect(15, workspaceSectionStartY, 180, wsTableHeight, 3, 3, "F");
34071
+ doc.setFontSize(18);
33864
34072
  doc.setFont("helvetica", "bold");
33865
- doc.setTextColor(70, 70, 70);
34073
+ doc.setTextColor(40, 40, 40);
33866
34074
  doc.text("Poorest Performing Workspaces", 20, yPos);
33867
34075
  doc.setTextColor(0, 0, 0);
33868
34076
  yPos += 10;
33869
34077
  doc.setFontSize(11);
33870
34078
  doc.setFont("helvetica", "bold");
33871
- doc.setFillColor(245, 245, 245);
33872
- doc.roundedRect(20, yPos, 170, 8, 1, 1, "F");
33873
34079
  yPos += 5;
33874
34080
  doc.text("Workspace", 25, yPos);
33875
- doc.text("Current/Ideal", 85, yPos);
34081
+ doc.text("Current/Target", 85, yPos);
33876
34082
  doc.text("Efficiency", 145, yPos);
33877
34083
  yPos += 3;
33878
- doc.setLineWidth(0.2);
33879
- doc.setDrawColor(220, 220, 220);
34084
+ doc.setLineWidth(0.3);
34085
+ doc.setDrawColor(200, 200, 200);
33880
34086
  doc.line(20, yPos, 190, yPos);
33881
34087
  doc.setFont("helvetica", "normal");
33882
- const tableStartY = yPos;
33883
34088
  yPos += 7;
33884
- const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
33885
34089
  if (sortedWorkspaces.length === 0) {
33886
34090
  doc.text("No workspace data available", 25, yPos);
33887
34091
  yPos += 10;
33888
34092
  } else {
33889
34093
  sortedWorkspaces.forEach((ws, index) => {
33890
- if (index % 2 === 0) {
33891
- doc.setFillColor(252, 252, 252);
33892
- doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
33893
- }
33894
34094
  const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
33895
- const maxWidth = 55;
33896
34095
  const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
33897
34096
  doc.text(truncatedName, 25, yPos);
33898
34097
  doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
@@ -33900,9 +34099,6 @@ var LinePdfGenerator = ({
33900
34099
  yPos += 8;
33901
34100
  });
33902
34101
  }
33903
- const wsTableHeight = yPos - tableStartY - 3;
33904
- doc.roundedRect(20, tableStartY, 170, wsTableHeight, 1, 1, "S");
33905
- addFooter(2);
33906
34102
  doc.save(`${lineInfo.line_name}_${date.split(",")[0]}.pdf`);
33907
34103
  } catch (error) {
33908
34104
  console.error("PDF generation failed:", error);
@@ -35076,25 +35272,25 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
35076
35272
  doc.setFont("helvetica", "bold");
35077
35273
  doc.setTextColor(50, 50, 50);
35078
35274
  doc.text("OPTIFYE.AI", 20, 15);
35079
- doc.setFontSize(11);
35275
+ doc.setFontSize(9);
35080
35276
  doc.setFont("helvetica", "normal");
35081
- doc.setTextColor(80, 80, 80);
35082
- const reportText = "REAL-TIME PERFORMANCE REPORT";
35083
- const reportTextWidth = doc.getStringUnitWidth(reportText) * 11 / doc.internal.scaleFactor;
35084
- doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
35277
+ doc.setTextColor(100, 100, 100);
35278
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
35279
+ const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
35280
+ doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
35085
35281
  doc.setDrawColor(200, 200, 200);
35086
35282
  doc.setLineWidth(0.5);
35087
35283
  doc.line(20, 20, 190, 20);
35088
35284
  doc.setFillColor(250, 250, 250);
35089
- doc.roundedRect(15, 25, 180, 55, 3, 3, "F");
35285
+ doc.roundedRect(15, 25, 180, 53, 3, 3, "F");
35090
35286
  doc.setFontSize(32);
35091
35287
  doc.setFont("helvetica", "bold");
35092
35288
  doc.setTextColor(0, 0, 0);
35093
- doc.text(lineName, 20, 40);
35289
+ doc.text(lineName, 20, 39);
35094
35290
  doc.setFontSize(22);
35095
35291
  doc.setFont("helvetica", "normal");
35096
35292
  doc.setTextColor(40, 40, 40);
35097
- doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20, 52);
35293
+ doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20, 51);
35098
35294
  doc.setFontSize(13);
35099
35295
  doc.setFont("helvetica", "normal");
35100
35296
  doc.setTextColor(60, 60, 60);
@@ -35105,8 +35301,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
35105
35301
  timeZone: "Asia/Kolkata"
35106
35302
  });
35107
35303
  const shiftType = "Day Shift";
35108
- doc.text(`${date}`, 20, 65);
35109
- doc.text(`${shiftType}`, 20, 73);
35304
+ doc.text(`${date}`, 20, 63);
35305
+ doc.text(`${shiftType}`, 20, 71);
35110
35306
  const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
35111
35307
  hour: "2-digit",
35112
35308
  minute: "2-digit",
@@ -35119,11 +35315,11 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
35119
35315
  });
35120
35316
  doc.setFontSize(12);
35121
35317
  doc.setTextColor(80, 80, 80);
35122
- doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20, 81);
35318
+ doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20, 79);
35123
35319
  doc.setTextColor(0, 0, 0);
35124
35320
  doc.setDrawColor(180, 180, 180);
35125
35321
  doc.setLineWidth(0.8);
35126
- doc.line(20, 90, 190, 90);
35322
+ doc.line(20, 88, 190, 88);
35127
35323
  const createKPIBox = (y) => {
35128
35324
  doc.setFillColor(255, 255, 255);
35129
35325
  doc.roundedRect(22, y - 7, 165, 12, 2, 2, "F");
@@ -35131,66 +35327,72 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
35131
35327
  doc.setLineWidth(0.2);
35132
35328
  doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
35133
35329
  };
35330
+ const perfOverviewStartY = 93;
35134
35331
  doc.setFillColor(245, 245, 245);
35135
- doc.roundedRect(15, 95, 180, 60, 3, 3, "F");
35332
+ doc.roundedRect(15, perfOverviewStartY, 180, 60, 3, 3, "F");
35136
35333
  doc.setFontSize(18);
35137
35334
  doc.setFont("helvetica", "bold");
35138
35335
  doc.setTextColor(40, 40, 40);
35139
- doc.text("Performance Overview", 20, 105);
35336
+ doc.text("Performance Overview", 20, 103);
35140
35337
  doc.setTextColor(0, 0, 0);
35141
- const kpiStartY = 117;
35338
+ const kpiStartY = 115;
35142
35339
  const kpiSpacing = 10;
35143
35340
  createKPIBox(kpiStartY);
35144
35341
  doc.setFontSize(11);
35145
35342
  doc.setFont("helvetica", "normal");
35146
- doc.text("Progress:", 25, kpiStartY);
35343
+ doc.text("Current Output/Target Output:", 25, kpiStartY);
35147
35344
  doc.setFont("helvetica", "bold");
35148
- doc.text(`${(workspace.total_actions / workspace.target_output * 100).toFixed(0)}% of Today's target`, 120, kpiStartY);
35345
+ doc.text(`${workspace.total_actions} / ${workspace.target_output}`, 120, kpiStartY);
35149
35346
  createKPIBox(kpiStartY + kpiSpacing);
35150
35347
  doc.setFont("helvetica", "normal");
35151
- doc.text("Current Output/Ideal Output:", 25, kpiStartY + kpiSpacing);
35348
+ doc.text("Efficiency:", 25, kpiStartY + kpiSpacing);
35152
35349
  doc.setFont("helvetica", "bold");
35153
- doc.text(`${workspace.total_actions} / ${workspace.target_output}`, 120, kpiStartY + kpiSpacing);
35350
+ doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing);
35154
35351
  createKPIBox(kpiStartY + kpiSpacing * 2);
35155
35352
  doc.setFont("helvetica", "normal");
35156
- doc.text("Efficiency:", 25, kpiStartY + kpiSpacing * 2);
35157
- doc.setFont("helvetica", "bold");
35158
- doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing * 2);
35159
- createKPIBox(kpiStartY + kpiSpacing * 3);
35160
- doc.setFont("helvetica", "normal");
35161
- doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 3);
35353
+ doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
35162
35354
  doc.setFont("helvetica", "bold");
35163
- doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 3);
35355
+ doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 2);
35164
35356
  doc.setDrawColor(180, 180, 180);
35165
35357
  doc.setLineWidth(0.8);
35166
- doc.line(20, 170, 190, 170);
35358
+ doc.line(20, 163, 190, 163);
35359
+ const hourlyPerfStartY = 168;
35360
+ const hourlyData = workspace.hourly_action_counts || [];
35361
+ const hourlyTarget = workspace.pph_threshold;
35362
+ const tableStartY = 199;
35363
+ const rowHeight = 8;
35364
+ const bottomPadding = 8;
35365
+ const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
35167
35366
  doc.setFillColor(245, 245, 245);
35168
- doc.roundedRect(15, 175, 180, 85, 3, 3, "F");
35367
+ doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
35169
35368
  doc.setFontSize(18);
35170
35369
  doc.setFont("helvetica", "bold");
35171
35370
  doc.setTextColor(40, 40, 40);
35172
- doc.text("Hourly Performance", 20, 185);
35371
+ doc.text("Hourly Performance", 20, 178);
35173
35372
  doc.setTextColor(0, 0, 0);
35174
35373
  doc.setFontSize(11);
35175
35374
  doc.setFont("helvetica", "bold");
35176
- doc.setFillColor(245, 245, 245);
35177
- doc.roundedRect(20, 177, 170, 8, 1, 1, "F");
35178
- doc.text("Time Range", 25, 182);
35179
- doc.text("Output", 95, 182);
35180
- doc.text("Target", 135, 182);
35181
- doc.text("Status", 170, 182);
35182
- doc.setLineWidth(0.2);
35183
- doc.setDrawColor(220, 220, 220);
35184
- doc.line(20, 185, 190, 185);
35375
+ doc.text("Time Range", 25, 188);
35376
+ doc.text("Output", 95, 188);
35377
+ doc.text("Target", 135, 188);
35378
+ doc.text("Status", 170, 188);
35379
+ doc.setLineWidth(0.3);
35380
+ doc.setDrawColor(200, 200, 200);
35381
+ doc.line(20, 191, 190, 191);
35185
35382
  doc.setFont("helvetica", "normal");
35186
- let yPos = 191;
35187
- const hourlyData = workspace.hourly_action_counts || [];
35188
- const hourlyTarget = workspace.pph_threshold;
35383
+ let yPos = tableStartY;
35384
+ const workspaceDate = new Date(workspace.date);
35385
+ const today = /* @__PURE__ */ new Date();
35386
+ today.setHours(0, 0, 0, 0);
35387
+ workspaceDate.setHours(0, 0, 0, 0);
35388
+ const isToday = workspaceDate.getTime() === today.getTime();
35389
+ let currentHour = 24;
35390
+ if (isToday) {
35391
+ const now2 = /* @__PURE__ */ new Date();
35392
+ const currentTimeIST = new Date(now2.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
35393
+ currentHour = currentTimeIST.getHours();
35394
+ }
35189
35395
  hourlyData.forEach((output, index) => {
35190
- if (index % 2 === 0) {
35191
- doc.setFillColor(252, 252, 252);
35192
- doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
35193
- }
35194
35396
  const startTime = /* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`);
35195
35397
  startTime.setHours(startTime.getHours() + index);
35196
35398
  const endTime = new Date(startTime);
@@ -35202,14 +35404,17 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
35202
35404
  hour: "numeric",
35203
35405
  hour12: true
35204
35406
  })}`;
35205
- const outputStr = output.toString();
35407
+ const hourNumber = startTime.getHours();
35408
+ const dataCollected = !isToday || hourNumber < currentHour;
35409
+ const outputStr = dataCollected ? output.toString() : "TBD";
35206
35410
  const targetStr = hourlyTarget.toString();
35207
- const outputX = 95 + doc.getStringUnitWidth(outputStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
35208
- const targetX = 135 + doc.getStringUnitWidth(targetStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
35209
35411
  doc.text(timeRange, 25, yPos);
35210
- doc.text(outputStr, outputX, yPos);
35211
- doc.text(targetStr, targetX, yPos);
35212
- if (output >= hourlyTarget) {
35412
+ doc.text(outputStr, 95, yPos);
35413
+ doc.text(targetStr, 135, yPos);
35414
+ if (!dataCollected) {
35415
+ doc.setTextColor(100, 100, 100);
35416
+ doc.text("-", 170, yPos);
35417
+ } else if (output >= hourlyTarget) {
35213
35418
  doc.setTextColor(0, 171, 69);
35214
35419
  doc.setFont("ZapfDingbats", "normal");
35215
35420
  doc.text("4", 170, yPos);
@@ -35219,15 +35424,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
35219
35424
  doc.text("\xD7", 170, yPos);
35220
35425
  }
35221
35426
  doc.setTextColor(0, 0, 0);
35222
- yPos += 8;
35427
+ yPos += rowHeight;
35223
35428
  });
35224
- doc.setLineWidth(0.2);
35225
- doc.setDrawColor(220, 220, 220);
35226
- doc.roundedRect(20, 187, 170, yPos - 187 - 3, 1, 1, "S");
35227
- doc.setFontSize(9);
35228
- doc.setTextColor(130, 130, 130);
35229
- const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
35230
- doc.text(generatedText, 20, 280);
35231
35429
  doc.save(`${workspace.workspace_name}_${date.split(",")[0]}.pdf`);
35232
35430
  } catch (error) {
35233
35431
  console.error("PDF generation failed:", error);
@@ -36307,7 +36505,8 @@ var WorkspaceHealthCard = ({
36307
36505
  workspace,
36308
36506
  onClick,
36309
36507
  showDetails = true,
36310
- className = ""
36508
+ className = "",
36509
+ onViewDetails
36311
36510
  }) => {
36312
36511
  const getStatusConfig = () => {
36313
36512
  switch (workspace.status) {
@@ -36360,38 +36559,101 @@ var WorkspaceHealthCard = ({
36360
36559
  onClick(workspace);
36361
36560
  }
36362
36561
  };
36562
+ const handleViewDetails = (event) => {
36563
+ event.stopPropagation();
36564
+ event.preventDefault();
36565
+ if (onViewDetails) {
36566
+ onViewDetails(workspace);
36567
+ }
36568
+ };
36363
36569
  const handleKeyDown = (event) => {
36364
36570
  if (onClick && (event.key === "Enter" || event.key === " ")) {
36365
36571
  event.preventDefault();
36366
36572
  onClick(workspace);
36367
36573
  }
36368
36574
  };
36575
+ const formatDuration3 = (minutes) => {
36576
+ if (!minutes || minutes <= 0) return "0 min";
36577
+ const rounded = Math.max(Math.round(minutes), 0);
36578
+ const days = Math.floor(rounded / 1440);
36579
+ const hours = Math.floor(rounded % 1440 / 60);
36580
+ const mins = rounded % 60;
36581
+ const parts = [];
36582
+ if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
36583
+ if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
36584
+ if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
36585
+ if (!parts.length) {
36586
+ parts.push("1 min");
36587
+ }
36588
+ return parts.join(" ");
36589
+ };
36369
36590
  const formatTimeAgo = (timeString) => {
36370
- return timeString.replace("about ", "").replace(" ago", "");
36591
+ if (!timeString) return "Unknown";
36592
+ const cleaned = timeString.replace("about ", "").trim();
36593
+ if (cleaned.toLowerCase() === "never") return "Never";
36594
+ const minuteMatch = cleaned.match(/(\d+)\s*m/);
36595
+ if (!minuteMatch) {
36596
+ return cleaned;
36597
+ }
36598
+ const minutes = parseInt(minuteMatch[1], 10);
36599
+ if (!Number.isFinite(minutes)) return cleaned;
36600
+ if (minutes < 1) return "Just now";
36601
+ if (minutes < 60) {
36602
+ return `${minutes} min ago`;
36603
+ }
36604
+ const hours = Math.floor(minutes / 60);
36605
+ const remainingMinutes = minutes % 60;
36606
+ if (hours < 24) {
36607
+ const parts2 = [`${hours} hr${hours === 1 ? "" : "s"}`];
36608
+ if (remainingMinutes) {
36609
+ parts2.push(`${remainingMinutes} min`);
36610
+ }
36611
+ return `${parts2.join(" ")} ago`;
36612
+ }
36613
+ const days = Math.floor(hours / 24);
36614
+ const remainingHours = hours % 24;
36615
+ const parts = [`${days} day${days === 1 ? "" : "s"}`];
36616
+ if (remainingHours) {
36617
+ parts.push(`${remainingHours} hr${remainingHours === 1 ? "" : "s"}`);
36618
+ }
36619
+ return `${parts.join(" ")} ago`;
36371
36620
  };
36372
- const formatDowntime = (uptimeDetails) => {
36373
- if (!uptimeDetails) return "";
36621
+ const getDowntimeConfig = (uptimeDetails) => {
36622
+ if (!uptimeDetails) {
36623
+ if (workspace.status === "healthy") {
36624
+ return {
36625
+ text: "0m",
36626
+ className: "text-emerald-600 dark:text-emerald-400",
36627
+ label: "Total Downtime"
36628
+ };
36629
+ }
36630
+ return { text: "--", className: "text-slate-400", label: "Total Downtime" };
36631
+ }
36374
36632
  const downtimeMinutes = Math.max(0, uptimeDetails.expectedMinutes - uptimeDetails.actualMinutes);
36375
- if (downtimeMinutes === 0) return "No downtime";
36376
- if (downtimeMinutes < 1) return "< 1 min downtime";
36377
- if (downtimeMinutes < 60) return `${downtimeMinutes} min downtime`;
36378
- const hours = Math.floor(downtimeMinutes / 60);
36379
- const minutes = downtimeMinutes % 60;
36380
- if (minutes === 0) {
36381
- return `${hours} hr downtime`;
36633
+ if (downtimeMinutes === 0) {
36634
+ return {
36635
+ text: "0m",
36636
+ className: "text-emerald-600 dark:text-emerald-400",
36637
+ label: "Total Downtime"
36638
+ };
36382
36639
  }
36383
- return `${hours} hr ${minutes} min downtime`;
36640
+ return {
36641
+ text: `${formatDuration3(downtimeMinutes)}`,
36642
+ className: downtimeMinutes > 60 ? "text-rose-600 dark:text-rose-400" : "text-amber-600 dark:text-amber-400",
36643
+ label: "Total Downtime"
36644
+ };
36384
36645
  };
36646
+ const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
36385
36647
  return /* @__PURE__ */ jsxRuntime.jsx(
36386
36648
  Card2,
36387
36649
  {
36388
36650
  className: clsx(
36389
- "relative overflow-hidden transition-all duration-300",
36651
+ "relative overflow-hidden transition-all duration-300 h-full flex flex-col",
36390
36652
  "bg-gradient-to-br",
36391
36653
  config.gradient,
36392
36654
  "border",
36393
36655
  config.border,
36394
- "shadow-sm hover:shadow-md",
36656
+ "shadow-sm hover:shadow-lg hover:border-blue-300 dark:hover:border-blue-700",
36395
36657
  onClick && "cursor-pointer hover:scale-[1.01]",
36396
36658
  workspace.isStale && "opacity-90",
36397
36659
  className
@@ -36401,7 +36663,7 @@ var WorkspaceHealthCard = ({
36401
36663
  tabIndex: onClick ? 0 : void 0,
36402
36664
  role: onClick ? "button" : void 0,
36403
36665
  "aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
36404
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
36666
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 flex-grow flex flex-col", children: [
36405
36667
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-start gap-2 sm:gap-3", children: [
36406
36668
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
36407
36669
  /* @__PURE__ */ jsxRuntime.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)}` }),
@@ -36423,21 +36685,20 @@ var WorkspaceHealthCard = ({
36423
36685
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
36424
36686
  ] })
36425
36687
  ] }),
36426
- workspace.uptimePercentage !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
36427
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-3.5 w-3.5 text-gray-400" }),
36428
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap", children: [
36429
- "Uptime today: ",
36430
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
36431
- workspace.uptimePercentage.toFixed(1),
36432
- "%"
36433
- ] })
36434
- ] })
36435
- ] }),
36436
- workspace.uptimeDetails && workspace.uptimeDetails.expectedMinutes > workspace.uptimeDetails.actualMinutes && workspace.uptimePercentage !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
36437
- "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
36438
- 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"
36439
- ), children: formatDowntime(workspace.uptimeDetails) }) })
36440
- ] })
36688
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 pt-3 border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
36689
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: "Total Downtime" }),
36690
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
36691
+ ] }) })
36692
+ ] }),
36693
+ onViewDetails && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-4 mt-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
36694
+ "button",
36695
+ {
36696
+ onClick: handleViewDetails,
36697
+ 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",
36698
+ type: "button",
36699
+ children: "View shift timeline"
36700
+ }
36701
+ ) })
36441
36702
  ] })
36442
36703
  }
36443
36704
  );
@@ -36445,8 +36706,11 @@ var WorkspaceHealthCard = ({
36445
36706
  var CompactWorkspaceHealthCard = ({
36446
36707
  workspace,
36447
36708
  onClick,
36448
- className = ""
36709
+ className = "",
36710
+ onViewDetails
36449
36711
  }) => {
36712
+ const downtimeMinutes = workspace.uptimeDetails ? Math.max(0, workspace.uptimeDetails.expectedMinutes - workspace.uptimeDetails.actualMinutes) : null;
36713
+ const downtimeLabel = downtimeMinutes === null ? "No downtime data" : downtimeMinutes === 0 ? "No downtime" : `${downtimeMinutes} min down`;
36450
36714
  const getStatusConfig = () => {
36451
36715
  switch (workspace.status) {
36452
36716
  case "healthy":
@@ -36486,6 +36750,13 @@ var CompactWorkspaceHealthCard = ({
36486
36750
  onClick(workspace);
36487
36751
  }
36488
36752
  };
36753
+ const handleViewDetails = (event) => {
36754
+ event.stopPropagation();
36755
+ event.preventDefault();
36756
+ if (onViewDetails) {
36757
+ onViewDetails(workspace);
36758
+ }
36759
+ };
36489
36760
  return /* @__PURE__ */ jsxRuntime.jsxs(
36490
36761
  "div",
36491
36762
  {
@@ -36513,14 +36784,20 @@ var CompactWorkspaceHealthCard = ({
36513
36784
  workspace.uptimePercentage.toFixed(1),
36514
36785
  "%"
36515
36786
  ] }),
36516
- workspace.uptimeDetails && workspace.uptimeDetails.expectedMinutes > workspace.uptimeDetails.actualMinutes && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 dark:text-gray-400", children: "\u2022" }),
36517
- workspace.uptimeDetails && workspace.uptimeDetails.expectedMinutes > workspace.uptimeDetails.actualMinutes && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: [
36518
- Math.max(0, workspace.uptimeDetails.expectedMinutes - workspace.uptimeDetails.actualMinutes),
36519
- "m down"
36520
- ] })
36787
+ downtimeMinutes !== null && downtimeMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 dark:text-gray-400", children: "\u2022" }),
36788
+ downtimeLabel && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: downtimeLabel })
36521
36789
  ] }),
36522
36790
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
36523
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
36791
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) }),
36792
+ onViewDetails && /* @__PURE__ */ jsxRuntime.jsx(
36793
+ "button",
36794
+ {
36795
+ onClick: handleViewDetails,
36796
+ className: "rounded-full border border-gray-200 px-2 py-0.5 text-[11px] font-medium text-gray-600 hover:bg-gray-50",
36797
+ type: "button",
36798
+ children: "View"
36799
+ }
36800
+ )
36524
36801
  ] })
36525
36802
  ]
36526
36803
  }
@@ -36529,6 +36806,7 @@ var CompactWorkspaceHealthCard = ({
36529
36806
  var HealthStatusGrid = ({
36530
36807
  workspaces,
36531
36808
  onWorkspaceClick,
36809
+ onWorkspaceViewDetails,
36532
36810
  showFilters = true,
36533
36811
  groupBy: initialGroupBy = "none",
36534
36812
  className = ""
@@ -36716,7 +36994,8 @@ var HealthStatusGrid = ({
36716
36994
  {
36717
36995
  workspace,
36718
36996
  onClick: onWorkspaceClick,
36719
- showDetails: true
36997
+ showDetails: true,
36998
+ onViewDetails: onWorkspaceViewDetails
36720
36999
  },
36721
37000
  workspace.workspace_id
36722
37001
  )) })
@@ -40252,7 +40531,7 @@ var AIAgentView = () => {
40252
40531
  }
40253
40532
  return formattedLines.join("");
40254
40533
  };
40255
- const formatTime4 = (timestamp) => {
40534
+ const formatTime5 = (timestamp) => {
40256
40535
  const date = new Date(timestamp);
40257
40536
  return date.toLocaleTimeString([], {
40258
40537
  hour: "2-digit",
@@ -41516,7 +41795,7 @@ var AIAgentView = () => {
41516
41795
  }
41517
41796
  ),
41518
41797
  /* @__PURE__ */ jsxRuntime.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: [
41519
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime4(message.created_at) }),
41798
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime5(message.created_at) }),
41520
41799
  message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
41521
41800
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
41522
41801
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Axel" })
@@ -44347,8 +44626,7 @@ var KPIsOverviewView = ({
44347
44626
  " Shift"
44348
44627
  ] })
44349
44628
  ] })
44350
- ] }) }),
44351
- /* @__PURE__ */ jsxRuntime.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" })
44629
+ ] }) })
44352
44630
  ] }) }),
44353
44631
  /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 p-3 sm:p-4 md:p-6 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
44354
44632
  LineCard,
@@ -48665,18 +48943,41 @@ var useWorkspaceHealth = (options) => {
48665
48943
  const isFetchingRef = React23.useRef(false);
48666
48944
  const refreshIntervalRef = React23.useRef(null);
48667
48945
  const healthTable = databaseConfig?.tables?.workspace_health || "workspace_health_status";
48668
- const calculateHealthStatus = (workspace) => {
48669
- if (!workspace.last_heartbeat) return "unknown";
48670
- const now2 = /* @__PURE__ */ new Date();
48671
- const lastHeartbeat = new Date(workspace.last_heartbeat);
48672
- const minutesSince = (now2.getTime() - lastHeartbeat.getTime()) / 6e4;
48673
- if (minutesSince < 5) return "healthy";
48674
- if (minutesSince < 15) return "warning";
48675
- return "unhealthy";
48676
- };
48946
+ const computeSummary = React23.useCallback((data) => {
48947
+ const total = data.length;
48948
+ const healthy = data.filter((w) => w.status === "healthy").length;
48949
+ const unhealthy = data.filter((w) => w.status === "unhealthy").length;
48950
+ const warning6 = data.filter((w) => w.status === "warning").length;
48951
+ let uptimePercentage = total > 0 ? healthy / total * 100 : 100;
48952
+ let totalDowntimeMinutes;
48953
+ const withUptime = data.filter(
48954
+ (w) => w.uptimePercentage !== void 0 && w.uptimeDetails !== void 0
48955
+ );
48956
+ if (withUptime.length > 0) {
48957
+ const totalUptime = withUptime.reduce((sum, w) => sum + (w.uptimePercentage || 0), 0);
48958
+ uptimePercentage = totalUptime / withUptime.length;
48959
+ totalDowntimeMinutes = withUptime.reduce((sum, w) => {
48960
+ if (w.uptimeDetails) {
48961
+ return sum + Math.max(0, w.uptimeDetails.expectedMinutes - w.uptimeDetails.actualMinutes);
48962
+ }
48963
+ return sum;
48964
+ }, 0);
48965
+ }
48966
+ return {
48967
+ totalWorkspaces: total,
48968
+ healthyWorkspaces: healthy,
48969
+ unhealthyWorkspaces: unhealthy,
48970
+ warningWorkspaces: warning6,
48971
+ uptimePercentage,
48972
+ totalDowntimeMinutes,
48973
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
48974
+ };
48975
+ }, []);
48677
48976
  const fetchWorkspacesHealth = React23.useCallback(async () => {
48678
- if (!supabase || isFetchingRef.current) return;
48977
+ if (isFetchingRef.current) return;
48679
48978
  if (!options.companyId) {
48979
+ setWorkspaces([]);
48980
+ setSummary(computeSummary([]));
48680
48981
  setLoading(false);
48681
48982
  return;
48682
48983
  }
@@ -48684,39 +48985,12 @@ var useWorkspaceHealth = (options) => {
48684
48985
  isFetchingRef.current = true;
48685
48986
  setLoading(true);
48686
48987
  setError(null);
48687
- let query = supabase.from(healthTable).select("*").eq("company_id", options.companyId);
48688
- if (options.lineId) {
48689
- query = query.eq("line_id", options.lineId);
48690
- }
48691
- query = query.order("workspace_display_name", { ascending: true });
48692
- const { data, error: fetchError } = await query;
48693
- if (fetchError) throw fetchError;
48694
- const healthData = data || [];
48695
- const workspacesWithStatus = healthData.map((ws) => {
48696
- const status = calculateHealthStatus(ws);
48697
- const now2 = /* @__PURE__ */ new Date();
48698
- const lastUpdate = ws.last_heartbeat ? new Date(ws.last_heartbeat) : null;
48699
- const timeSinceLastUpdate = lastUpdate ? `${Math.floor((now2.getTime() - lastUpdate.getTime()) / 6e4)}m ago` : "Never";
48700
- return {
48701
- ...ws,
48702
- status,
48703
- timeSinceLastUpdate,
48704
- isStale: !lastUpdate || now2.getTime() - lastUpdate.getTime() > 15 * 6e4
48705
- };
48988
+ const workspacesWithStatus = await workspaceHealthService.getWorkspaceHealthStatus({
48989
+ lineId: options.lineId,
48990
+ companyId: options.companyId
48706
48991
  });
48707
48992
  setWorkspaces(workspacesWithStatus);
48708
- const total = workspacesWithStatus.length;
48709
- const healthy = workspacesWithStatus.filter((w) => w.status === "healthy").length;
48710
- const unhealthy = workspacesWithStatus.filter((w) => w.status === "unhealthy").length;
48711
- const warning6 = workspacesWithStatus.filter((w) => w.status === "warning").length;
48712
- setSummary({
48713
- totalWorkspaces: total,
48714
- healthyWorkspaces: healthy,
48715
- unhealthyWorkspaces: unhealthy,
48716
- warningWorkspaces: warning6,
48717
- uptimePercentage: total > 0 ? healthy / total * 100 : 100,
48718
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
48719
- });
48993
+ setSummary(computeSummary(workspacesWithStatus));
48720
48994
  } catch (err) {
48721
48995
  console.error("[useWorkspaceHealth] Error fetching workspace health:", err);
48722
48996
  setError({ message: err.message, code: err.code || "FETCH_ERROR" });
@@ -48725,7 +48999,7 @@ var useWorkspaceHealth = (options) => {
48725
48999
  setLoading(false);
48726
49000
  isFetchingRef.current = false;
48727
49001
  }
48728
- }, [supabase, options.companyId, options.lineId, healthTable]);
49002
+ }, [options.companyId, options.lineId, computeSummary]);
48729
49003
  React23.useEffect(() => {
48730
49004
  fetchWorkspacesHealth();
48731
49005
  }, [fetchWorkspacesHealth]);
@@ -48770,6 +49044,395 @@ var useWorkspaceHealth = (options) => {
48770
49044
  refetch: fetchWorkspacesHealth
48771
49045
  };
48772
49046
  };
49047
+ var STATUS_COLORS = {
49048
+ up: "bg-emerald-500",
49049
+ down: "bg-rose-500",
49050
+ pending: "bg-gray-200"
49051
+ };
49052
+ var STATUS_TITLES = {
49053
+ up: "Uptime",
49054
+ down: "Downtime",
49055
+ pending: "Pending"
49056
+ };
49057
+ var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
49058
+ hour: "numeric",
49059
+ minute: "2-digit",
49060
+ hour12: true,
49061
+ timeZone: timezone
49062
+ }).format(date);
49063
+ var formatDuration = (minutes) => {
49064
+ if (minutes < 1) return "<1 min";
49065
+ if (minutes < 60) return `${minutes} min`;
49066
+ const hours = Math.floor(minutes / 60);
49067
+ const remainder = minutes % 60;
49068
+ if (remainder === 0) return `${hours} hr${hours > 1 ? "s" : ""}`;
49069
+ return `${hours} hr ${remainder} min`;
49070
+ };
49071
+ var formatDowntimeLabel = (minutes, includeSuffix = true) => {
49072
+ if (!minutes || minutes <= 0) {
49073
+ return includeSuffix ? "No downtime" : "0 min";
49074
+ }
49075
+ const rounded = Math.max(Math.round(minutes), 0);
49076
+ const days = Math.floor(rounded / 1440);
49077
+ const hours = Math.floor(rounded % 1440 / 60);
49078
+ const mins = rounded % 60;
49079
+ const parts = [];
49080
+ if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
49081
+ if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
49082
+ if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
49083
+ if (!parts.length) {
49084
+ parts.push("1 min");
49085
+ }
49086
+ const label = parts.join(" ");
49087
+ return includeSuffix ? `${label} down` : label;
49088
+ };
49089
+ var UptimeTimelineStrip = ({
49090
+ points,
49091
+ totalMinutes,
49092
+ shiftStart,
49093
+ shiftEnd,
49094
+ timezone,
49095
+ className = "",
49096
+ uptimePercentage = 0,
49097
+ downtimeMinutes = 0
49098
+ }) => {
49099
+ const segments = React23.useMemo(() => {
49100
+ if (!points.length || totalMinutes <= 0) return [];
49101
+ const result = [];
49102
+ let current = {
49103
+ status: points[0].status,
49104
+ length: 1,
49105
+ startMinuteIndex: points[0].minuteIndex,
49106
+ startTimestamp: points[0].timestamp,
49107
+ endTimestamp: points[0].timestamp
49108
+ };
49109
+ for (let i = 1; i < points.length; i++) {
49110
+ const point = points[i];
49111
+ if (point.status === current.status) {
49112
+ current.length += 1;
49113
+ current.endTimestamp = point.timestamp;
49114
+ } else {
49115
+ result.push(current);
49116
+ current = {
49117
+ status: point.status,
49118
+ length: 1,
49119
+ startMinuteIndex: point.minuteIndex,
49120
+ startTimestamp: point.timestamp,
49121
+ endTimestamp: point.timestamp
49122
+ };
49123
+ }
49124
+ }
49125
+ result.push(current);
49126
+ return result;
49127
+ }, [points, totalMinutes]);
49128
+ const markers = React23.useMemo(() => {
49129
+ if (totalMinutes <= 0) return [];
49130
+ const startDate = new Date(shiftStart);
49131
+ const endDate = new Date(shiftEnd);
49132
+ const roundedTotal = Math.max(totalMinutes, 1);
49133
+ const markerInterval = totalMinutes > 360 ? 120 : 60;
49134
+ const markerList = [];
49135
+ for (let minute = 0; minute <= roundedTotal; minute += markerInterval) {
49136
+ const markerMinute = Math.min(minute, roundedTotal);
49137
+ const markerDate = dateFns.addMinutes(startDate, markerMinute);
49138
+ markerList.push({
49139
+ minute: markerMinute,
49140
+ label: formatTime4(markerDate, timezone)
49141
+ });
49142
+ }
49143
+ const endLabel = formatTime4(endDate, timezone);
49144
+ if (!markerList.some((marker) => marker.minute === roundedTotal)) {
49145
+ markerList.push({
49146
+ minute: roundedTotal,
49147
+ label: endLabel
49148
+ });
49149
+ } else {
49150
+ markerList[markerList.length - 1] = {
49151
+ minute: roundedTotal,
49152
+ label: endLabel
49153
+ };
49154
+ }
49155
+ return markerList;
49156
+ }, [shiftStart, shiftEnd, timezone, totalMinutes]);
49157
+ if (!points.length || totalMinutes <= 0) {
49158
+ return /* @__PURE__ */ jsxRuntime.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." });
49159
+ }
49160
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative w-full ${className}`, children: [
49161
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-900 font-semibold", children: [
49162
+ uptimePercentage.toFixed(1),
49163
+ " % uptime ",
49164
+ downtimeMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-600 font-normal", children: [
49165
+ "(",
49166
+ formatDowntimeLabel(downtimeMinutes),
49167
+ ")"
49168
+ ] })
49169
+ ] }) }),
49170
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex h-4 overflow-hidden rounded-lg border border-gray-200 shadow-sm bg-white", children: segments.map((segment, index) => {
49171
+ const startDate = new Date(segment.startTimestamp);
49172
+ const endDate = new Date(segment.endTimestamp);
49173
+ if (segment.length > 1) {
49174
+ endDate.setMinutes(endDate.getMinutes() + 1);
49175
+ }
49176
+ const tooltip = `${STATUS_TITLES[segment.status]} \u2022 ${formatDuration(segment.length)} (${formatTime4(
49177
+ startDate,
49178
+ timezone
49179
+ )} - ${formatTime4(endDate, timezone)})`;
49180
+ return /* @__PURE__ */ jsxRuntime.jsx(
49181
+ "div",
49182
+ {
49183
+ className: `${STATUS_COLORS[segment.status]} transition-all hover:opacity-80 cursor-pointer`,
49184
+ style: { flex: segment.length },
49185
+ title: tooltip
49186
+ },
49187
+ `${segment.status}-${segment.startMinuteIndex}-${index}`
49188
+ );
49189
+ }) }),
49190
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none relative w-full mt-4 min-h-6", children: markers.map((marker) => {
49191
+ const left = totalMinutes > 0 ? marker.minute / totalMinutes * 100 : 0;
49192
+ return /* @__PURE__ */ jsxRuntime.jsxs(
49193
+ "div",
49194
+ {
49195
+ className: "absolute flex -translate-x-1/2 flex-col items-center",
49196
+ style: { left: `${left}%` },
49197
+ children: [
49198
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1.5 h-2 w-px bg-gray-300" }),
49199
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-gray-600 whitespace-nowrap", children: marker.label })
49200
+ ]
49201
+ },
49202
+ `${marker.label}-${marker.minute}`
49203
+ );
49204
+ }) })
49205
+ ] });
49206
+ };
49207
+ var UptimeTimelineStrip_default = UptimeTimelineStrip;
49208
+ var SHORT_INTERRUPT_THRESHOLD_MINUTES = 3;
49209
+ var formatDuration2 = (minutes) => {
49210
+ if (!minutes || minutes <= 0) return "0 min";
49211
+ const rounded = Math.max(Math.round(minutes), 0);
49212
+ const days = Math.floor(rounded / 1440);
49213
+ const hours = Math.floor(rounded % 1440 / 60);
49214
+ const mins = rounded % 60;
49215
+ const parts = [];
49216
+ if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
49217
+ if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
49218
+ if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
49219
+ if (!parts.length) {
49220
+ parts.push("1 min");
49221
+ }
49222
+ return parts.join(" ");
49223
+ };
49224
+ var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
49225
+ if (!minutes || minutes <= 0) {
49226
+ return includeSuffix ? "No downtime" : "0 min";
49227
+ }
49228
+ const label = formatDuration2(minutes);
49229
+ return includeSuffix ? `${label} down` : label;
49230
+ };
49231
+ var formatTimeRange = (start, end, timezone) => {
49232
+ const formatter = new Intl.DateTimeFormat("en-IN", {
49233
+ hour: "numeric",
49234
+ minute: "2-digit",
49235
+ hour12: true,
49236
+ timeZone: timezone
49237
+ });
49238
+ return `${formatter.format(start)} - ${formatter.format(end)}`;
49239
+ };
49240
+ var WorkspaceUptimeDetailModal = ({
49241
+ workspace,
49242
+ isOpen,
49243
+ onClose
49244
+ }) => {
49245
+ const timezone = useAppTimezone() || "UTC";
49246
+ const logsContainerRef = React23.useRef(null);
49247
+ const [showScrollIndicator, setShowScrollIndicator] = React23.useState(false);
49248
+ const {
49249
+ timeline,
49250
+ loading,
49251
+ error,
49252
+ refetch
49253
+ } = useWorkspaceUptimeTimeline({
49254
+ workspaceId: workspace?.workspace_id,
49255
+ companyId: workspace?.company_id,
49256
+ enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
49257
+ refreshInterval: 6e4
49258
+ });
49259
+ React23.useEffect(() => {
49260
+ if (!isOpen || !workspace) return;
49261
+ const handleKeyDown = (event) => {
49262
+ if (event.key === "Escape") {
49263
+ onClose();
49264
+ }
49265
+ };
49266
+ window.addEventListener("keydown", handleKeyDown);
49267
+ return () => {
49268
+ window.removeEventListener("keydown", handleKeyDown);
49269
+ };
49270
+ }, [isOpen, onClose, workspace]);
49271
+ const shiftStart = timeline ? new Date(timeline.shiftStart) : null;
49272
+ const shiftEnd = timeline ? new Date(timeline.shiftEnd) : null;
49273
+ const downtimeSegments = timeline?.downtimeSegments || [];
49274
+ downtimeSegments.length;
49275
+ const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
49276
+ const uptimePercentage = timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? 0;
49277
+ const allInterruptionsSorted = React23.useMemo(
49278
+ () => [...downtimeSegments].sort(
49279
+ (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
49280
+ ),
49281
+ [downtimeSegments]
49282
+ );
49283
+ React23.useEffect(() => {
49284
+ const checkScroll = () => {
49285
+ const container2 = logsContainerRef.current;
49286
+ if (container2) {
49287
+ const hasScroll = container2.scrollHeight > container2.clientHeight;
49288
+ const isAtBottom = container2.scrollHeight - container2.scrollTop <= container2.clientHeight + 10;
49289
+ setShowScrollIndicator(hasScroll && !isAtBottom);
49290
+ }
49291
+ };
49292
+ checkScroll();
49293
+ const container = logsContainerRef.current;
49294
+ if (container) {
49295
+ container.addEventListener("scroll", checkScroll);
49296
+ return () => container.removeEventListener("scroll", checkScroll);
49297
+ }
49298
+ }, [downtimeSegments]);
49299
+ const renderSegment = (segment) => {
49300
+ const start = new Date(segment.startTime);
49301
+ const end = new Date(segment.endTime);
49302
+ const isMajor = segment.durationMinutes >= SHORT_INTERRUPT_THRESHOLD_MINUTES;
49303
+ const containerClasses = isMajor ? "border-rose-200 bg-rose-50" : "border-gray-200 bg-white";
49304
+ return /* @__PURE__ */ jsxRuntime.jsxs(
49305
+ "div",
49306
+ {
49307
+ className: `rounded-lg border px-5 py-3 ${containerClasses}`,
49308
+ children: [
49309
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-gray-900", children: formatTimeRange(start, end, timezone) }),
49310
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-600 mt-1", children: [
49311
+ "Duration: ",
49312
+ formatDowntimeLabel2(segment.durationMinutes, false)
49313
+ ] })
49314
+ ]
49315
+ },
49316
+ `${segment.startMinuteIndex}-${segment.endMinuteIndex}`
49317
+ );
49318
+ };
49319
+ if (!isOpen || !workspace) {
49320
+ return null;
49321
+ }
49322
+ return /* @__PURE__ */ jsxRuntime.jsx(
49323
+ "div",
49324
+ {
49325
+ className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4",
49326
+ onClick: onClose,
49327
+ "aria-modal": "true",
49328
+ role: "dialog",
49329
+ "aria-labelledby": "uptime-detail-title",
49330
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
49331
+ "div",
49332
+ {
49333
+ className: "relative flex w-full max-w-4xl max-h-[90vh] flex-col rounded-2xl bg-white shadow-2xl border border-gray-100",
49334
+ onClick: (event) => event.stopPropagation(),
49335
+ role: "document",
49336
+ "aria-labelledby": "uptime-detail-title",
49337
+ children: [
49338
+ /* @__PURE__ */ jsxRuntime.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: [
49339
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
49340
+ /* @__PURE__ */ jsxRuntime.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)}` }),
49341
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
49342
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-900", children: timeline?.shiftLabel || "Current Shift" }),
49343
+ shiftStart && shiftEnd && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
49344
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
49345
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
49346
+ ] })
49347
+ ] }),
49348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
49349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
49350
+ /* @__PURE__ */ jsxRuntime.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"}` }),
49351
+ /* @__PURE__ */ jsxRuntime.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" })
49352
+ ] }),
49353
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
49354
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500", children: [
49355
+ "Last heartbeat ",
49356
+ workspace.timeSinceLastUpdate
49357
+ ] })
49358
+ ] })
49359
+ ] }),
49360
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
49361
+ /* @__PURE__ */ jsxRuntime.jsxs(
49362
+ "button",
49363
+ {
49364
+ onClick: refetch,
49365
+ disabled: loading,
49366
+ 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",
49367
+ children: [
49368
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }),
49369
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Refresh" })
49370
+ ]
49371
+ }
49372
+ ),
49373
+ /* @__PURE__ */ jsxRuntime.jsx(
49374
+ "button",
49375
+ {
49376
+ onClick: onClose,
49377
+ className: "rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 transition-colors duration-200",
49378
+ "aria-label": "Close uptime details",
49379
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" })
49380
+ }
49381
+ )
49382
+ ] })
49383
+ ] }),
49384
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-8 py-6 space-y-6", children: error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-rose-100 bg-rose-50 p-5 text-sm text-rose-700", children: [
49385
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
49386
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-rose-600/90", children: error.message })
49387
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
49388
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
49389
+ /* @__PURE__ */ jsxRuntime.jsx(
49390
+ UptimeTimelineStrip_default,
49391
+ {
49392
+ points: timeline?.points || [],
49393
+ totalMinutes: timeline?.totalMinutes || 0,
49394
+ shiftStart: timeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
49395
+ shiftEnd: timeline?.shiftEnd || (/* @__PURE__ */ new Date()).toISOString(),
49396
+ timezone,
49397
+ uptimePercentage,
49398
+ downtimeMinutes
49399
+ }
49400
+ ),
49401
+ loading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
49402
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
49403
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Updating timeline\u2026" })
49404
+ ] })
49405
+ ] }),
49406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-4", children: [
49407
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
49408
+ downtimeSegments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600", children: "No downtime events recorded for this shift." }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
49409
+ /* @__PURE__ */ jsxRuntime.jsx(
49410
+ "div",
49411
+ {
49412
+ ref: logsContainerRef,
49413
+ className: "max-h-[400px] overflow-y-auto space-y-2 pr-2",
49414
+ style: {
49415
+ scrollbarWidth: "thin",
49416
+ scrollbarColor: "#CBD5E0 #F7FAFC"
49417
+ },
49418
+ children: allInterruptionsSorted.map((segment) => renderSegment(segment))
49419
+ }
49420
+ ),
49421
+ showScrollIndicator && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-gray-500 animate-bounce", children: [
49422
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" }),
49423
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Scroll for more" }),
49424
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
49425
+ ] }) })
49426
+ ] })
49427
+ ] })
49428
+ ] }) }) })
49429
+ ]
49430
+ }
49431
+ )
49432
+ }
49433
+ );
49434
+ };
49435
+ var WorkspaceUptimeDetailModal_default = WorkspaceUptimeDetailModal;
48773
49436
  var WorkspaceHealthView = ({
48774
49437
  lineId,
48775
49438
  companyId,
@@ -48779,6 +49442,7 @@ var WorkspaceHealthView = ({
48779
49442
  const router$1 = router.useRouter();
48780
49443
  const [groupBy, setGroupBy] = React23.useState("line");
48781
49444
  const timezone = useAppTimezone();
49445
+ const [selectedWorkspace, setSelectedWorkspace] = React23.useState(null);
48782
49446
  const operationalDate = getOperationalDate(timezone || "UTC");
48783
49447
  const currentHour = (/* @__PURE__ */ new Date()).getHours();
48784
49448
  const isNightShift = currentHour >= 18 || currentHour < 6;
@@ -48819,6 +49483,12 @@ var WorkspaceHealthView = ({
48819
49483
  },
48820
49484
  [router$1, onNavigate]
48821
49485
  );
49486
+ const handleViewDetails = React23.useCallback((workspace) => {
49487
+ setSelectedWorkspace(workspace);
49488
+ }, []);
49489
+ const handleCloseDetails = React23.useCallback(() => {
49490
+ setSelectedWorkspace(null);
49491
+ }, []);
48822
49492
  const handleExport = React23.useCallback(() => {
48823
49493
  const csv = [
48824
49494
  ["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
@@ -48876,178 +49546,189 @@ var WorkspaceHealthView = ({
48876
49546
  )
48877
49547
  ] }) }) }) });
48878
49548
  }
48879
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
48880
- /* @__PURE__ */ jsxRuntime.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: [
48881
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm:hidden", children: [
48882
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
48883
- /* @__PURE__ */ jsxRuntime.jsx(
49549
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
49550
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
49551
+ /* @__PURE__ */ jsxRuntime.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: [
49552
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm:hidden", children: [
49553
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
49554
+ /* @__PURE__ */ jsxRuntime.jsx(
49555
+ BackButtonMinimal,
49556
+ {
49557
+ onClick: () => router$1.push("/"),
49558
+ text: "Back",
49559
+ size: "sm",
49560
+ "aria-label": "Navigate back to dashboard"
49561
+ }
49562
+ ),
49563
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
49564
+ /* @__PURE__ */ jsxRuntime.jsx(
49565
+ "button",
49566
+ {
49567
+ onClick: () => {
49568
+ refetch();
49569
+ },
49570
+ className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49571
+ "aria-label": "Refresh",
49572
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
49573
+ }
49574
+ ),
49575
+ /* @__PURE__ */ jsxRuntime.jsx(
49576
+ "button",
49577
+ {
49578
+ onClick: handleExport,
49579
+ className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49580
+ "aria-label": "Export CSV",
49581
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
49582
+ }
49583
+ )
49584
+ ] })
49585
+ ] }),
49586
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
49587
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
49588
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
49589
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
49590
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
49591
+ ] })
49592
+ ] }) })
49593
+ ] }),
49594
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
49595
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
48884
49596
  BackButtonMinimal,
48885
49597
  {
48886
49598
  onClick: () => router$1.push("/"),
48887
49599
  text: "Back",
48888
- size: "sm",
49600
+ size: "default",
48889
49601
  "aria-label": "Navigate back to dashboard"
48890
49602
  }
48891
- ),
48892
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
49603
+ ) }),
49604
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
49605
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: "System Health" }),
49606
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
49607
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
49608
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
49609
+ ] })
49610
+ ] }) }),
49611
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 flex gap-2", children: [
48893
49612
  /* @__PURE__ */ jsxRuntime.jsx(
48894
49613
  "button",
48895
49614
  {
48896
49615
  onClick: () => {
48897
49616
  refetch();
48898
49617
  },
48899
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49618
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48900
49619
  "aria-label": "Refresh",
48901
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
49620
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
48902
49621
  }
48903
49622
  ),
48904
49623
  /* @__PURE__ */ jsxRuntime.jsx(
48905
49624
  "button",
48906
49625
  {
48907
49626
  onClick: handleExport,
48908
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49627
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48909
49628
  "aria-label": "Export CSV",
48910
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
49629
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
48911
49630
  }
48912
49631
  )
48913
- ] })
48914
- ] }),
48915
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
48916
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
48917
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
48918
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
48919
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
49632
+ ] }),
49633
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
49634
+ ] }) }),
49635
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-4", children: [
49636
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm sm:text-base md:text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(LiveTimer, {}) }),
49637
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
49638
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
49639
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
49640
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
49641
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
49642
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: [
49643
+ shiftType,
49644
+ " Shift"
49645
+ ] })
48920
49646
  ] })
48921
49647
  ] }) })
48922
49648
  ] }),
48923
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
48924
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
48925
- BackButtonMinimal,
49649
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
49650
+ summary && /* @__PURE__ */ jsxRuntime.jsxs(
49651
+ motion.div,
48926
49652
  {
48927
- onClick: () => router$1.push("/"),
48928
- text: "Back",
48929
- size: "default",
48930
- "aria-label": "Navigate back to dashboard"
48931
- }
48932
- ) }),
48933
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
48934
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: "System Health" }),
48935
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
48936
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
48937
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
48938
- ] })
48939
- ] }) }),
48940
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 flex gap-2", children: [
48941
- /* @__PURE__ */ jsxRuntime.jsx(
48942
- "button",
48943
- {
48944
- onClick: () => {
48945
- refetch();
48946
- },
48947
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48948
- "aria-label": "Refresh",
48949
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
48950
- }
48951
- ),
48952
- /* @__PURE__ */ jsxRuntime.jsx(
48953
- "button",
48954
- {
48955
- onClick: handleExport,
48956
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
48957
- "aria-label": "Export CSV",
48958
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
48959
- }
48960
- )
48961
- ] }),
48962
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
48963
- ] }) }),
48964
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-4", children: [
48965
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm sm:text-base md:text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(LiveTimer, {}) }),
48966
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
48967
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
48968
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
48969
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
48970
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
48971
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: [
48972
- shiftType,
48973
- " Shift"
48974
- ] })
48975
- ] })
48976
- ] }) })
48977
- ] }),
48978
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
48979
- summary && /* @__PURE__ */ jsxRuntime.jsxs(
48980
- motion.div,
48981
- {
48982
- initial: { opacity: 0, y: 20 },
48983
- animate: { opacity: 1, y: 0 },
48984
- transition: { duration: 0.3, delay: 0.1 },
48985
- className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
48986
- children: [
48987
- /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
48988
- /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "System Availability" }) }),
48989
- /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
48990
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
48991
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
48992
- summary.uptimePercentage.toFixed(1),
48993
- "%"
49653
+ initial: { opacity: 0, y: 20 },
49654
+ animate: { opacity: 1, y: 0 },
49655
+ transition: { duration: 0.3, delay: 0.1 },
49656
+ className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
49657
+ children: [
49658
+ /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
49659
+ /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "System Availability" }) }),
49660
+ /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
49661
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
49662
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
49663
+ summary.uptimePercentage.toFixed(1),
49664
+ "%"
49665
+ ] }),
49666
+ summary.uptimePercentage >= 97 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "h-5 w-5 text-green-500" }) : summary.uptimePercentage >= 90 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5 text-yellow-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingDown, { className: "h-5 w-5 text-red-500" })
48994
49667
  ] }),
48995
- summary.uptimePercentage >= 97 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "h-5 w-5 text-green-500" }) : summary.uptimePercentage >= 90 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5 text-yellow-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingDown, { className: "h-5 w-5 text-red-500" })
48996
- ] }),
48997
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Overall system uptime today" })
48998
- ] })
48999
- ] }),
49000
- /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
49001
- /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49002
- getStatusIcon("healthy"),
49003
- "Healthy"
49004
- ] }) }),
49005
- /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
49006
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.healthyWorkspaces }),
49007
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
49008
- ] })
49009
- ] }),
49010
- /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
49011
- /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49012
- getStatusIcon("warning"),
49013
- "Warning"
49014
- ] }) }),
49015
- /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
49016
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.warningWorkspaces }),
49017
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
49018
- ] })
49019
- ] }),
49020
- /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
49021
- /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49022
- getStatusIcon("unhealthy"),
49023
- "Unhealthy"
49024
- ] }) }),
49025
- /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
49026
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.unhealthyWorkspaces }),
49027
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
49668
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Overall system uptime today" })
49669
+ ] })
49670
+ ] }),
49671
+ /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
49672
+ /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49673
+ getStatusIcon("healthy"),
49674
+ "Healthy"
49675
+ ] }) }),
49676
+ /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
49677
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.healthyWorkspaces }),
49678
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
49679
+ ] })
49680
+ ] }),
49681
+ /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
49682
+ /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49683
+ getStatusIcon("warning"),
49684
+ "Warning"
49685
+ ] }) }),
49686
+ /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
49687
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.warningWorkspaces }),
49688
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
49689
+ ] })
49690
+ ] }),
49691
+ /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
49692
+ /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
49693
+ getStatusIcon("unhealthy"),
49694
+ "Unhealthy"
49695
+ ] }) }),
49696
+ /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
49697
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.unhealthyWorkspaces }),
49698
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
49699
+ ] })
49028
49700
  ] })
49029
- ] })
49030
- ]
49031
- }
49032
- ),
49033
- /* @__PURE__ */ jsxRuntime.jsx(
49034
- motion.div,
49035
- {
49036
- initial: { opacity: 0, y: 20 },
49037
- animate: { opacity: 1, y: 0 },
49038
- transition: { duration: 0.3, delay: 0.2 },
49039
- children: /* @__PURE__ */ jsxRuntime.jsx(
49040
- HealthStatusGrid,
49041
- {
49042
- workspaces,
49043
- onWorkspaceClick: handleWorkspaceClick,
49044
- showFilters: true,
49045
- groupBy
49046
- }
49047
- )
49048
- }
49049
- )
49050
- ] })
49701
+ ]
49702
+ }
49703
+ ),
49704
+ /* @__PURE__ */ jsxRuntime.jsx(
49705
+ motion.div,
49706
+ {
49707
+ initial: { opacity: 0, y: 20 },
49708
+ animate: { opacity: 1, y: 0 },
49709
+ transition: { duration: 0.3, delay: 0.2 },
49710
+ children: /* @__PURE__ */ jsxRuntime.jsx(
49711
+ HealthStatusGrid,
49712
+ {
49713
+ workspaces,
49714
+ onWorkspaceClick: handleWorkspaceClick,
49715
+ onWorkspaceViewDetails: handleViewDetails,
49716
+ showFilters: true,
49717
+ groupBy
49718
+ }
49719
+ )
49720
+ }
49721
+ )
49722
+ ] })
49723
+ ] }),
49724
+ /* @__PURE__ */ jsxRuntime.jsx(
49725
+ WorkspaceUptimeDetailModal_default,
49726
+ {
49727
+ workspace: selectedWorkspace,
49728
+ isOpen: Boolean(selectedWorkspace),
49729
+ onClose: handleCloseDetails
49730
+ }
49731
+ )
49051
49732
  ] });
49052
49733
  };
49053
49734
  var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
@@ -53007,6 +53688,7 @@ exports.useWorkspaceHealthStatus = useWorkspaceHealthStatus;
53007
53688
  exports.useWorkspaceMetrics = useWorkspaceMetrics;
53008
53689
  exports.useWorkspaceNavigation = useWorkspaceNavigation;
53009
53690
  exports.useWorkspaceOperators = useWorkspaceOperators;
53691
+ exports.useWorkspaceUptimeTimeline = useWorkspaceUptimeTimeline;
53010
53692
  exports.userService = userService;
53011
53693
  exports.videoPrefetchManager = videoPrefetchManager;
53012
53694
  exports.videoPreloader = videoPreloader;