@optifye/dashboard-core 6.9.11 → 6.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +120 -12
- package/dist/index.d.mts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +1857 -731
- package/dist/index.mjs +1859 -734
- package/package.json +1 -1
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 (
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
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
|
|
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
|
|
2292
|
+
const enabledByLineAndId = /* @__PURE__ */ new Set();
|
|
2293
|
+
const enabledByLineAndName = /* @__PURE__ */ new Set();
|
|
2209
2294
|
enabledWorkspaces.forEach((w) => {
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
|
2215
|
-
|
|
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
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
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",
|
|
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
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
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
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
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
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2654
|
+
currentDownRun = 0;
|
|
2524
2655
|
}
|
|
2525
|
-
const
|
|
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:
|
|
2528
|
-
actualMinutes,
|
|
2659
|
+
expectedMinutes: completedMinutes,
|
|
2660
|
+
actualMinutes: uptimeMinutes,
|
|
2529
2661
|
percentage,
|
|
2530
2662
|
lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2531
2663
|
});
|
|
@@ -2925,8 +3057,8 @@ var AuthService = class {
|
|
|
2925
3057
|
"Authorization": `Bearer ${accessToken}`,
|
|
2926
3058
|
"Content-Type": "application/json"
|
|
2927
3059
|
},
|
|
2928
|
-
timeout:
|
|
2929
|
-
//
|
|
3060
|
+
timeout: 1e4,
|
|
3061
|
+
// 10 seconds
|
|
2930
3062
|
retries: 1,
|
|
2931
3063
|
silentErrors: false
|
|
2932
3064
|
// We want to know about auth errors
|
|
@@ -2963,8 +3095,8 @@ var AuthService = class {
|
|
|
2963
3095
|
"Authorization": `Bearer ${accessToken}`,
|
|
2964
3096
|
"Content-Type": "application/json"
|
|
2965
3097
|
},
|
|
2966
|
-
timeout:
|
|
2967
|
-
//
|
|
3098
|
+
timeout: 1e4,
|
|
3099
|
+
// 10 seconds
|
|
2968
3100
|
retries: 2,
|
|
2969
3101
|
// More retries for validation
|
|
2970
3102
|
silentErrors: true,
|
|
@@ -2996,8 +3128,8 @@ var AuthService = class {
|
|
|
2996
3128
|
"Authorization": `Bearer ${accessToken}`,
|
|
2997
3129
|
"Content-Type": "application/json"
|
|
2998
3130
|
},
|
|
2999
|
-
timeout:
|
|
3000
|
-
//
|
|
3131
|
+
timeout: 1e4,
|
|
3132
|
+
// 10 seconds
|
|
3001
3133
|
retries: 1,
|
|
3002
3134
|
silentErrors: false
|
|
3003
3135
|
}
|
|
@@ -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
|
|
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:
|
|
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
|
|
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 `${
|
|
24131
|
+
return `${formatTime5(startHour, startMinute)}-${formatTime5(endHour, endMinute)}`;
|
|
23941
24132
|
}, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
|
|
23942
|
-
const
|
|
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
|
|
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 `${
|
|
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:
|
|
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,
|
|
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
|
|
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:
|
|
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:
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
26019
|
+
formatTime5(currentBreak.elapsedMinutes),
|
|
25829
26020
|
" elapsed of ",
|
|
25830
|
-
|
|
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(
|
|
@@ -26131,64 +26322,208 @@ var getSeverityColor = (severity) => {
|
|
|
26131
26322
|
return "bg-gray-500";
|
|
26132
26323
|
}
|
|
26133
26324
|
};
|
|
26134
|
-
var
|
|
26135
|
-
|
|
26325
|
+
var formatTime2 = (seconds) => {
|
|
26326
|
+
if (!seconds || isNaN(seconds)) return "0:00";
|
|
26327
|
+
const h = Math.floor(seconds / 3600);
|
|
26328
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
26329
|
+
const s = Math.floor(seconds % 60);
|
|
26330
|
+
if (h > 0) {
|
|
26331
|
+
return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
26332
|
+
}
|
|
26333
|
+
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
26334
|
+
};
|
|
26335
|
+
var VideoControls = ({
|
|
26136
26336
|
isPlaying,
|
|
26137
|
-
|
|
26337
|
+
currentTime,
|
|
26338
|
+
duration,
|
|
26339
|
+
buffered,
|
|
26340
|
+
showControls,
|
|
26341
|
+
controlsPinned = false,
|
|
26342
|
+
onTogglePinControls,
|
|
26343
|
+
playbackRate = 1,
|
|
26344
|
+
onPlayPause,
|
|
26345
|
+
onSeek,
|
|
26346
|
+
onSeekStart,
|
|
26347
|
+
onSeekEnd,
|
|
26348
|
+
onToggleFullscreen,
|
|
26349
|
+
onPlaybackRateChange,
|
|
26350
|
+
className = ""
|
|
26138
26351
|
}) => {
|
|
26139
|
-
const [
|
|
26140
|
-
const [
|
|
26352
|
+
const [isDragging, setIsDragging] = React23.useState(false);
|
|
26353
|
+
const [dragTime, setDragTime] = React23.useState(0);
|
|
26354
|
+
const [isHoveringProgressBar, setIsHoveringProgressBar] = React23.useState(false);
|
|
26355
|
+
const [showSpeedMenu, setShowSpeedMenu] = React23.useState(false);
|
|
26356
|
+
const speedMenuRef = React23.useRef(null);
|
|
26357
|
+
const progressColor = "#4b5563";
|
|
26358
|
+
const controlsVisible = showControls || controlsPinned;
|
|
26359
|
+
const getPercentage = (current, total) => {
|
|
26360
|
+
if (!total || total === 0) return 0;
|
|
26361
|
+
return Math.min(Math.max(current / total * 100, 0), 100);
|
|
26362
|
+
};
|
|
26363
|
+
const handleSeekChange = (e) => {
|
|
26364
|
+
const newTime = parseFloat(e.target.value);
|
|
26365
|
+
setDragTime(newTime);
|
|
26366
|
+
onSeek(newTime);
|
|
26367
|
+
};
|
|
26368
|
+
const handleSeekStart = () => {
|
|
26369
|
+
setIsDragging(true);
|
|
26370
|
+
setDragTime(currentTime);
|
|
26371
|
+
onSeekStart?.();
|
|
26372
|
+
};
|
|
26373
|
+
const handleSeekEnd = () => {
|
|
26374
|
+
setIsDragging(false);
|
|
26375
|
+
onSeekEnd?.();
|
|
26376
|
+
};
|
|
26141
26377
|
React23.useEffect(() => {
|
|
26142
|
-
|
|
26143
|
-
|
|
26144
|
-
|
|
26145
|
-
|
|
26146
|
-
|
|
26147
|
-
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
26151
|
-
|
|
26152
|
-
|
|
26153
|
-
|
|
26154
|
-
|
|
26155
|
-
|
|
26156
|
-
}
|
|
26157
|
-
}, [show, duration]);
|
|
26158
|
-
if (!isVisible) return null;
|
|
26159
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
26378
|
+
const handleClickOutside = (event) => {
|
|
26379
|
+
if (speedMenuRef.current && !speedMenuRef.current.contains(event.target)) {
|
|
26380
|
+
setShowSpeedMenu(false);
|
|
26381
|
+
}
|
|
26382
|
+
};
|
|
26383
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
26384
|
+
return () => {
|
|
26385
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
26386
|
+
};
|
|
26387
|
+
}, []);
|
|
26388
|
+
const displayTime = isDragging ? dragTime : currentTime;
|
|
26389
|
+
const progressPercent = getPercentage(displayTime, duration);
|
|
26390
|
+
const bufferedPercent = getPercentage(buffered, duration);
|
|
26391
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
26160
26392
|
"div",
|
|
26161
26393
|
{
|
|
26162
|
-
className:
|
|
26163
|
-
style: {
|
|
26164
|
-
|
|
26165
|
-
|
|
26166
|
-
|
|
26167
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
|
|
26168
|
-
// Play icon (triangle)
|
|
26169
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26170
|
-
"svg",
|
|
26171
|
-
{
|
|
26172
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
26173
|
-
viewBox: "0 0 24 24",
|
|
26174
|
-
fill: "white",
|
|
26175
|
-
className: "w-16 h-16",
|
|
26176
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" })
|
|
26177
|
-
}
|
|
26178
|
-
)
|
|
26179
|
-
) : (
|
|
26180
|
-
// Pause icon (two bars)
|
|
26181
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26182
|
-
"svg",
|
|
26394
|
+
className: `absolute bottom-0 left-0 right-0 px-3 pb-3 pt-12 bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-opacity duration-300 ${controlsVisible ? "opacity-100" : "opacity-0 pointer-events-none"} ${className}`,
|
|
26395
|
+
style: { touchAction: "none" },
|
|
26396
|
+
children: [
|
|
26397
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
26398
|
+
"div",
|
|
26183
26399
|
{
|
|
26184
|
-
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
26400
|
+
className: "relative h-1 mb-4 group cursor-pointer",
|
|
26401
|
+
onMouseEnter: () => setIsHoveringProgressBar(true),
|
|
26402
|
+
onMouseLeave: () => setIsHoveringProgressBar(false),
|
|
26403
|
+
children: [
|
|
26404
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -top-2 -bottom-2 left-0 right-0 z-20" }),
|
|
26405
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 left-0 right-0 bottom-0 bg-white/20 rounded-full overflow-hidden z-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
26406
|
+
"div",
|
|
26407
|
+
{
|
|
26408
|
+
className: "absolute top-0 left-0 bottom-0 bg-white/40 transition-all duration-200",
|
|
26409
|
+
style: { width: `${bufferedPercent}%` }
|
|
26410
|
+
}
|
|
26411
|
+
) }),
|
|
26412
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26413
|
+
"div",
|
|
26414
|
+
{
|
|
26415
|
+
className: "absolute top-0 left-0 bottom-0 bg-[#007bff] transition-all duration-75 z-10",
|
|
26416
|
+
style: { width: `${progressPercent}%`, backgroundColor: progressColor },
|
|
26417
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
26418
|
+
"div",
|
|
26419
|
+
{
|
|
26420
|
+
className: `absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 w-3 h-3 rounded-full shadow transform transition-transform duration-200 ${isHoveringProgressBar || isDragging ? "scale-100" : "scale-0"}`,
|
|
26421
|
+
style: { backgroundColor: progressColor }
|
|
26422
|
+
}
|
|
26423
|
+
)
|
|
26424
|
+
}
|
|
26425
|
+
),
|
|
26426
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26427
|
+
"input",
|
|
26428
|
+
{
|
|
26429
|
+
type: "range",
|
|
26430
|
+
min: "0",
|
|
26431
|
+
max: duration || 100,
|
|
26432
|
+
step: "0.1",
|
|
26433
|
+
value: displayTime,
|
|
26434
|
+
onChange: handleSeekChange,
|
|
26435
|
+
onMouseDown: handleSeekStart,
|
|
26436
|
+
onMouseUp: handleSeekEnd,
|
|
26437
|
+
onTouchStart: handleSeekStart,
|
|
26438
|
+
onTouchEnd: handleSeekEnd,
|
|
26439
|
+
className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-30 margin-0 padding-0"
|
|
26440
|
+
}
|
|
26441
|
+
)
|
|
26442
|
+
]
|
|
26189
26443
|
}
|
|
26190
|
-
)
|
|
26191
|
-
|
|
26444
|
+
),
|
|
26445
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
|
|
26446
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
|
|
26447
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26448
|
+
"button",
|
|
26449
|
+
{
|
|
26450
|
+
onClick: (e) => {
|
|
26451
|
+
e.stopPropagation();
|
|
26452
|
+
onPlayPause();
|
|
26453
|
+
},
|
|
26454
|
+
className: "hover:text-[#007bff] transition-colors focus:outline-none",
|
|
26455
|
+
"aria-label": isPlaying ? "Pause" : "Play",
|
|
26456
|
+
children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" }) })
|
|
26457
|
+
}
|
|
26458
|
+
),
|
|
26459
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs font-medium font-sans", children: [
|
|
26460
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime2(displayTime) }),
|
|
26461
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mx-1 text-white/70", children: "/" }),
|
|
26462
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/70", children: formatTime2(duration) })
|
|
26463
|
+
] })
|
|
26464
|
+
] }),
|
|
26465
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
|
|
26466
|
+
onTogglePinControls && /* @__PURE__ */ jsxRuntime.jsx(
|
|
26467
|
+
"button",
|
|
26468
|
+
{
|
|
26469
|
+
onClick: (e) => {
|
|
26470
|
+
e.stopPropagation();
|
|
26471
|
+
onTogglePinControls();
|
|
26472
|
+
},
|
|
26473
|
+
className: `transition-colors focus:outline-none ${controlsPinned ? "text-[#007bff]" : "hover:text-[#007bff]"}`,
|
|
26474
|
+
"aria-label": controlsPinned ? "Unpin controls" : "Pin controls",
|
|
26475
|
+
title: controlsPinned ? "Unpin controls" : "Pin controls",
|
|
26476
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: controlsPinned ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 3h6l-1 7h3v2h-4.5l-.5 4.5-2 1L10 12H6v-2h3z" }) : /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 3h6l-1 7h3v2h-4v5l-2 1-1-6H6v-2h3z" }) })
|
|
26477
|
+
}
|
|
26478
|
+
),
|
|
26479
|
+
onPlaybackRateChange && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", ref: speedMenuRef, children: [
|
|
26480
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
26481
|
+
"button",
|
|
26482
|
+
{
|
|
26483
|
+
onClick: (e) => {
|
|
26484
|
+
e.stopPropagation();
|
|
26485
|
+
setShowSpeedMenu(!showSpeedMenu);
|
|
26486
|
+
},
|
|
26487
|
+
className: "text-xs font-medium hover:text-[#007bff] transition-colors focus:outline-none min-w-[32px]",
|
|
26488
|
+
"aria-label": "Playback Speed",
|
|
26489
|
+
children: [
|
|
26490
|
+
playbackRate,
|
|
26491
|
+
"x"
|
|
26492
|
+
]
|
|
26493
|
+
}
|
|
26494
|
+
),
|
|
26495
|
+
showSpeedMenu && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full right-0 mb-2 bg-black/90 text-white rounded shadow-lg overflow-hidden z-50 min-w-[80px]", children: [0.5, 1, 1.5, 2, 2.5, 3, 4, 5].map((rate) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
26496
|
+
"button",
|
|
26497
|
+
{
|
|
26498
|
+
onClick: (e) => {
|
|
26499
|
+
e.stopPropagation();
|
|
26500
|
+
onPlaybackRateChange(rate);
|
|
26501
|
+
setShowSpeedMenu(false);
|
|
26502
|
+
},
|
|
26503
|
+
className: `block w-full text-left px-4 py-2 text-xs hover:bg-white/20 transition-colors ${playbackRate === rate ? "text-[#007bff] font-bold" : ""}`,
|
|
26504
|
+
children: [
|
|
26505
|
+
rate,
|
|
26506
|
+
"x"
|
|
26507
|
+
]
|
|
26508
|
+
},
|
|
26509
|
+
rate
|
|
26510
|
+
)) })
|
|
26511
|
+
] }),
|
|
26512
|
+
onToggleFullscreen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
26513
|
+
"button",
|
|
26514
|
+
{
|
|
26515
|
+
onClick: (e) => {
|
|
26516
|
+
e.stopPropagation();
|
|
26517
|
+
onToggleFullscreen();
|
|
26518
|
+
},
|
|
26519
|
+
className: "hover:text-[#007bff] transition-colors focus:outline-none",
|
|
26520
|
+
"aria-label": "Toggle Fullscreen",
|
|
26521
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
|
|
26522
|
+
}
|
|
26523
|
+
)
|
|
26524
|
+
] })
|
|
26525
|
+
] })
|
|
26526
|
+
]
|
|
26192
26527
|
}
|
|
26193
26528
|
);
|
|
26194
26529
|
};
|
|
@@ -26316,9 +26651,15 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26316
26651
|
const blobUrlRef = React23.useRef(null);
|
|
26317
26652
|
const [isReady, setIsReady] = React23.useState(false);
|
|
26318
26653
|
const [isLoading, setIsLoading] = React23.useState(true);
|
|
26319
|
-
const [
|
|
26320
|
-
const [
|
|
26321
|
-
const
|
|
26654
|
+
const [showControls, setShowControls] = React23.useState(true);
|
|
26655
|
+
const [controlsPinned, setControlsPinned] = React23.useState(false);
|
|
26656
|
+
const [isPlaying, setIsPlaying] = React23.useState(false);
|
|
26657
|
+
const [currentTime, setCurrentTime] = React23.useState(0);
|
|
26658
|
+
const [duration, setDuration] = React23.useState(0);
|
|
26659
|
+
const [buffered, setBuffered] = React23.useState(0);
|
|
26660
|
+
const [playbackRate, setPlaybackRate] = React23.useState(1);
|
|
26661
|
+
const userSeekingRef = React23.useRef(false);
|
|
26662
|
+
const controlsTimeoutRef = React23.useRef(null);
|
|
26322
26663
|
const eventCallbacksRef = React23.useRef({
|
|
26323
26664
|
onReady,
|
|
26324
26665
|
onPlay,
|
|
@@ -26497,8 +26838,6 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26497
26838
|
}
|
|
26498
26839
|
});
|
|
26499
26840
|
hls.on(Hls3.Events.FRAG_LOADING, () => {
|
|
26500
|
-
setIsLoading(true);
|
|
26501
|
-
eventCallbacksRef.current.onLoadingChange?.(true);
|
|
26502
26841
|
});
|
|
26503
26842
|
hls.on(Hls3.Events.FRAG_LOADED, () => {
|
|
26504
26843
|
setIsLoading(false);
|
|
@@ -26552,25 +26891,52 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26552
26891
|
const handleCanPlay = () => {
|
|
26553
26892
|
if (!hlsRef.current) {
|
|
26554
26893
|
setIsReady(true);
|
|
26555
|
-
onReady?.(player);
|
|
26556
26894
|
}
|
|
26895
|
+
setIsLoading(false);
|
|
26896
|
+
eventCallbacksRef.current.onLoadingChange?.(false);
|
|
26897
|
+
onReady?.(player);
|
|
26898
|
+
};
|
|
26899
|
+
const handlePlay = () => {
|
|
26900
|
+
setIsPlaying(true);
|
|
26901
|
+
eventCallbacksRef.current.onPlay?.(player);
|
|
26902
|
+
};
|
|
26903
|
+
const handlePause = () => {
|
|
26904
|
+
if (userSeekingRef.current && videoRef.current) {
|
|
26905
|
+
videoRef.current.play().catch((err) => console.warn("Auto-resume after seek pause failed:", err));
|
|
26906
|
+
return;
|
|
26907
|
+
}
|
|
26908
|
+
setIsPlaying(false);
|
|
26909
|
+
eventCallbacksRef.current.onPause?.(player);
|
|
26557
26910
|
};
|
|
26558
|
-
const handlePlay = () => eventCallbacksRef.current.onPlay?.(player);
|
|
26559
|
-
const handlePause = () => eventCallbacksRef.current.onPause?.(player);
|
|
26560
26911
|
const handlePlaying = () => {
|
|
26561
26912
|
setIsLoading(false);
|
|
26913
|
+
setIsPlaying(true);
|
|
26562
26914
|
eventCallbacksRef.current.onLoadingChange?.(false);
|
|
26563
26915
|
eventCallbacksRef.current.onPlaying?.(player);
|
|
26564
26916
|
};
|
|
26565
26917
|
const handleTimeUpdate = () => {
|
|
26566
26918
|
const currentTime2 = video.currentTime || 0;
|
|
26919
|
+
setCurrentTime(currentTime2);
|
|
26920
|
+
if (video.buffered.length > 0) {
|
|
26921
|
+
for (let i = 0; i < video.buffered.length; i++) {
|
|
26922
|
+
if (video.buffered.start(i) <= currentTime2 && video.buffered.end(i) >= currentTime2) {
|
|
26923
|
+
setBuffered(video.buffered.end(i));
|
|
26924
|
+
break;
|
|
26925
|
+
}
|
|
26926
|
+
}
|
|
26927
|
+
}
|
|
26567
26928
|
eventCallbacksRef.current.onTimeUpdate?.(player, currentTime2);
|
|
26568
26929
|
};
|
|
26569
26930
|
const handleDurationChange = () => {
|
|
26570
26931
|
const duration2 = video.duration || 0;
|
|
26932
|
+
setDuration(duration2);
|
|
26571
26933
|
eventCallbacksRef.current.onDurationChange?.(player, duration2);
|
|
26572
26934
|
};
|
|
26573
|
-
const handleEnded = () =>
|
|
26935
|
+
const handleEnded = () => {
|
|
26936
|
+
setIsPlaying(false);
|
|
26937
|
+
userSeekingRef.current = false;
|
|
26938
|
+
eventCallbacksRef.current.onEnded?.(player);
|
|
26939
|
+
};
|
|
26574
26940
|
const handleLoadStart = () => {
|
|
26575
26941
|
setIsLoading(true);
|
|
26576
26942
|
eventCallbacksRef.current.onLoadingChange?.(true);
|
|
@@ -26586,8 +26952,19 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26586
26952
|
setIsLoading(true);
|
|
26587
26953
|
eventCallbacksRef.current.onLoadingChange?.(true);
|
|
26588
26954
|
};
|
|
26589
|
-
const handleSeeking = () =>
|
|
26590
|
-
|
|
26955
|
+
const handleSeeking = () => {
|
|
26956
|
+
userSeekingRef.current = true;
|
|
26957
|
+
eventCallbacksRef.current.onSeeking?.(player);
|
|
26958
|
+
};
|
|
26959
|
+
const handleSeeked = () => {
|
|
26960
|
+
setIsLoading(false);
|
|
26961
|
+
eventCallbacksRef.current.onLoadingChange?.(false);
|
|
26962
|
+
if (videoRef.current) {
|
|
26963
|
+
videoRef.current.play().catch((err) => console.warn("Resume playback after seek failed:", err));
|
|
26964
|
+
}
|
|
26965
|
+
userSeekingRef.current = false;
|
|
26966
|
+
eventCallbacksRef.current.onSeeked?.(player);
|
|
26967
|
+
};
|
|
26591
26968
|
const handleError = () => {
|
|
26592
26969
|
const error = video.error;
|
|
26593
26970
|
if (error) {
|
|
@@ -26600,6 +26977,10 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26600
26977
|
eventCallbacksRef.current.onError?.(player, errorInfo);
|
|
26601
26978
|
}
|
|
26602
26979
|
};
|
|
26980
|
+
const handlePlaybackRateChange2 = (e) => {
|
|
26981
|
+
const target = e.target;
|
|
26982
|
+
setPlaybackRate(target.playbackRate);
|
|
26983
|
+
};
|
|
26603
26984
|
video.addEventListener("canplay", handleCanPlay);
|
|
26604
26985
|
video.addEventListener("play", handlePlay);
|
|
26605
26986
|
video.addEventListener("pause", handlePause);
|
|
@@ -26614,6 +26995,7 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26614
26995
|
video.addEventListener("seeking", handleSeeking);
|
|
26615
26996
|
video.addEventListener("seeked", handleSeeked);
|
|
26616
26997
|
video.addEventListener("error", handleError);
|
|
26998
|
+
video.addEventListener("ratechange", handlePlaybackRateChange2);
|
|
26617
26999
|
return () => {
|
|
26618
27000
|
video.removeEventListener("canplay", handleCanPlay);
|
|
26619
27001
|
video.removeEventListener("play", handlePlay);
|
|
@@ -26629,6 +27011,7 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26629
27011
|
video.removeEventListener("seeking", handleSeeking);
|
|
26630
27012
|
video.removeEventListener("seeked", handleSeeked);
|
|
26631
27013
|
video.removeEventListener("error", handleError);
|
|
27014
|
+
video.removeEventListener("ratechange", handlePlaybackRateChange2);
|
|
26632
27015
|
};
|
|
26633
27016
|
}, [
|
|
26634
27017
|
src,
|
|
@@ -26657,20 +27040,46 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26657
27040
|
}
|
|
26658
27041
|
}
|
|
26659
27042
|
}, [autoplay]);
|
|
27043
|
+
const resetControlsTimeout = React23.useCallback(() => {
|
|
27044
|
+
if (controlsPinned) {
|
|
27045
|
+
setShowControls(true);
|
|
27046
|
+
return;
|
|
27047
|
+
}
|
|
27048
|
+
setShowControls(true);
|
|
27049
|
+
if (controlsTimeoutRef.current) {
|
|
27050
|
+
clearTimeout(controlsTimeoutRef.current);
|
|
27051
|
+
}
|
|
27052
|
+
if (isPlaying) {
|
|
27053
|
+
controlsTimeoutRef.current = setTimeout(() => {
|
|
27054
|
+
setShowControls(false);
|
|
27055
|
+
}, 3e3);
|
|
27056
|
+
}
|
|
27057
|
+
}, [isPlaying, controlsPinned]);
|
|
27058
|
+
const handleMouseMove = React23.useCallback(() => {
|
|
27059
|
+
resetControlsTimeout();
|
|
27060
|
+
}, [resetControlsTimeout]);
|
|
27061
|
+
React23.useEffect(() => {
|
|
27062
|
+
resetControlsTimeout();
|
|
27063
|
+
return () => {
|
|
27064
|
+
if (controlsTimeoutRef.current) {
|
|
27065
|
+
clearTimeout(controlsTimeoutRef.current);
|
|
27066
|
+
}
|
|
27067
|
+
};
|
|
27068
|
+
}, [isPlaying, resetControlsTimeout]);
|
|
26660
27069
|
const play = React23.useCallback(() => {
|
|
26661
27070
|
return videoRef.current?.play();
|
|
26662
27071
|
}, []);
|
|
26663
27072
|
const pause = React23.useCallback(() => {
|
|
26664
27073
|
videoRef.current?.pause();
|
|
26665
27074
|
}, []);
|
|
26666
|
-
const
|
|
27075
|
+
const currentTimeProp = React23.useCallback((time2) => {
|
|
26667
27076
|
if (time2 !== void 0 && videoRef.current) {
|
|
26668
27077
|
videoRef.current.currentTime = time2;
|
|
26669
27078
|
return time2;
|
|
26670
27079
|
}
|
|
26671
27080
|
return videoRef.current?.currentTime || 0;
|
|
26672
27081
|
}, []);
|
|
26673
|
-
const
|
|
27082
|
+
const durationProp = React23.useCallback(() => {
|
|
26674
27083
|
return videoRef.current?.duration || 0;
|
|
26675
27084
|
}, []);
|
|
26676
27085
|
const paused = React23.useCallback(() => {
|
|
@@ -26683,95 +27092,144 @@ var HlsVideoPlayer = React23.forwardRef(({
|
|
|
26683
27092
|
}
|
|
26684
27093
|
return videoRef.current?.muted ?? false;
|
|
26685
27094
|
}, []);
|
|
26686
|
-
const
|
|
27095
|
+
const volumeProp = React23.useCallback((level) => {
|
|
26687
27096
|
if (level !== void 0 && videoRef.current) {
|
|
26688
27097
|
videoRef.current.volume = level;
|
|
26689
27098
|
return level;
|
|
26690
27099
|
}
|
|
26691
27100
|
return videoRef.current?.volume ?? 1;
|
|
26692
27101
|
}, []);
|
|
26693
|
-
const
|
|
27102
|
+
const playbackRateProp = React23.useCallback((rate) => {
|
|
26694
27103
|
if (rate !== void 0 && videoRef.current) {
|
|
26695
27104
|
videoRef.current.playbackRate = rate;
|
|
26696
27105
|
return rate;
|
|
26697
27106
|
}
|
|
26698
27107
|
return videoRef.current?.playbackRate ?? 1;
|
|
26699
27108
|
}, []);
|
|
27109
|
+
const handleTogglePlay = React23.useCallback(() => {
|
|
27110
|
+
if (videoRef.current) {
|
|
27111
|
+
if (videoRef.current.paused) {
|
|
27112
|
+
videoRef.current.play();
|
|
27113
|
+
} else {
|
|
27114
|
+
videoRef.current.pause();
|
|
27115
|
+
}
|
|
27116
|
+
}
|
|
27117
|
+
}, []);
|
|
27118
|
+
const handleSeek = React23.useCallback((time2) => {
|
|
27119
|
+
if (videoRef.current) {
|
|
27120
|
+
videoRef.current.currentTime = time2;
|
|
27121
|
+
videoRef.current.play().catch((err) => console.warn("Resume playback failed during seek:", err));
|
|
27122
|
+
}
|
|
27123
|
+
}, []);
|
|
27124
|
+
const handleSeekStart = React23.useCallback(() => {
|
|
27125
|
+
userSeekingRef.current = true;
|
|
27126
|
+
}, []);
|
|
27127
|
+
const handleSeekEnd = React23.useCallback(() => {
|
|
27128
|
+
if (videoRef.current) {
|
|
27129
|
+
videoRef.current.play().catch((err) => console.warn("Resume playback failed after seek:", err));
|
|
27130
|
+
}
|
|
27131
|
+
}, []);
|
|
27132
|
+
const handlePlaybackRateChange = React23.useCallback((rate) => {
|
|
27133
|
+
if (videoRef.current) {
|
|
27134
|
+
videoRef.current.playbackRate = rate;
|
|
27135
|
+
}
|
|
27136
|
+
}, []);
|
|
27137
|
+
const handleToggleFullscreen = React23.useCallback(() => {
|
|
27138
|
+
if (videoContainerRef.current) {
|
|
27139
|
+
if (!document.fullscreenElement) {
|
|
27140
|
+
videoContainerRef.current.requestFullscreen();
|
|
27141
|
+
} else {
|
|
27142
|
+
document.exitFullscreen();
|
|
27143
|
+
}
|
|
27144
|
+
}
|
|
27145
|
+
}, []);
|
|
26700
27146
|
React23.useImperativeHandle(ref, () => ({
|
|
26701
27147
|
hls: hlsRef.current,
|
|
26702
27148
|
video: videoRef.current,
|
|
26703
27149
|
play,
|
|
26704
27150
|
pause,
|
|
26705
|
-
currentTime,
|
|
26706
|
-
duration,
|
|
27151
|
+
currentTime: currentTimeProp,
|
|
27152
|
+
duration: durationProp,
|
|
26707
27153
|
paused,
|
|
26708
27154
|
mute,
|
|
26709
|
-
volume,
|
|
26710
|
-
playbackRate,
|
|
27155
|
+
volume: volumeProp,
|
|
27156
|
+
playbackRate: playbackRateProp,
|
|
26711
27157
|
dispose,
|
|
26712
27158
|
isReady,
|
|
26713
27159
|
// For backward compatibility with Video.js API
|
|
26714
27160
|
player: playerLikeObject()
|
|
26715
|
-
}), [play, pause,
|
|
26716
|
-
const
|
|
26717
|
-
if (!onClick
|
|
26718
|
-
|
|
26719
|
-
|
|
26720
|
-
|
|
26721
|
-
|
|
26722
|
-
|
|
26723
|
-
|
|
26724
|
-
|
|
26725
|
-
|
|
26726
|
-
|
|
26727
|
-
|
|
26728
|
-
|
|
26729
|
-
|
|
26730
|
-
|
|
26731
|
-
|
|
26732
|
-
|
|
26733
|
-
|
|
26734
|
-
"video",
|
|
27161
|
+
}), [play, pause, currentTimeProp, durationProp, paused, mute, volumeProp, playbackRateProp, dispose, isReady, playerLikeObject]);
|
|
27162
|
+
const handleContainerClick = React23.useCallback(() => {
|
|
27163
|
+
if (!onClick && !controls) {
|
|
27164
|
+
handleTogglePlay();
|
|
27165
|
+
}
|
|
27166
|
+
if (onClick) {
|
|
27167
|
+
onClick();
|
|
27168
|
+
}
|
|
27169
|
+
}, [onClick, controls, handleTogglePlay]);
|
|
27170
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
27171
|
+
"div",
|
|
27172
|
+
{
|
|
27173
|
+
className: `hls-video-player-wrapper ${className} group`,
|
|
27174
|
+
style: { position: "relative", width: "100%", height: "100%" },
|
|
27175
|
+
onMouseMove: handleMouseMove,
|
|
27176
|
+
onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
|
|
27177
|
+
children: [
|
|
27178
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
27179
|
+
"div",
|
|
26735
27180
|
{
|
|
26736
|
-
|
|
26737
|
-
|
|
26738
|
-
|
|
26739
|
-
|
|
26740
|
-
|
|
26741
|
-
|
|
26742
|
-
|
|
26743
|
-
|
|
26744
|
-
|
|
27181
|
+
className: "hls-video-player-container",
|
|
27182
|
+
ref: videoContainerRef,
|
|
27183
|
+
onClick: handleContainerClick,
|
|
27184
|
+
children: [
|
|
27185
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
27186
|
+
"video",
|
|
27187
|
+
{
|
|
27188
|
+
ref: videoRef,
|
|
27189
|
+
className: "hls-video-element",
|
|
27190
|
+
poster,
|
|
27191
|
+
controls: false,
|
|
27192
|
+
loop,
|
|
27193
|
+
muted,
|
|
27194
|
+
playsInline,
|
|
27195
|
+
autoPlay: autoplay,
|
|
27196
|
+
preload: "metadata"
|
|
27197
|
+
}
|
|
27198
|
+
),
|
|
27199
|
+
controls && /* @__PURE__ */ jsxRuntime.jsx(
|
|
27200
|
+
VideoControls,
|
|
27201
|
+
{
|
|
27202
|
+
isPlaying,
|
|
27203
|
+
currentTime,
|
|
27204
|
+
duration,
|
|
27205
|
+
buffered,
|
|
27206
|
+
showControls: controlsPinned || showControls || !isPlaying,
|
|
27207
|
+
controlsPinned,
|
|
27208
|
+
playbackRate,
|
|
27209
|
+
onPlayPause: handleTogglePlay,
|
|
27210
|
+
onSeek: handleSeek,
|
|
27211
|
+
onSeekStart: handleSeekStart,
|
|
27212
|
+
onSeekEnd: handleSeekEnd,
|
|
27213
|
+
onPlaybackRateChange: handlePlaybackRateChange,
|
|
27214
|
+
onTogglePinControls: () => setControlsPinned((prev) => {
|
|
27215
|
+
const next = !prev;
|
|
27216
|
+
if (next) {
|
|
27217
|
+
setShowControls(true);
|
|
27218
|
+
} else {
|
|
27219
|
+
resetControlsTimeout();
|
|
27220
|
+
}
|
|
27221
|
+
return next;
|
|
27222
|
+
}),
|
|
27223
|
+
onToggleFullscreen: handleToggleFullscreen
|
|
27224
|
+
}
|
|
27225
|
+
)
|
|
27226
|
+
]
|
|
26745
27227
|
}
|
|
26746
|
-
)
|
|
26747
|
-
|
|
26748
|
-
|
|
26749
|
-
|
|
26750
|
-
|
|
26751
|
-
"div",
|
|
26752
|
-
{
|
|
26753
|
-
onClick: handleClickWithIndicator,
|
|
26754
|
-
style: {
|
|
26755
|
-
position: "absolute",
|
|
26756
|
-
top: 0,
|
|
26757
|
-
left: 0,
|
|
26758
|
-
right: 0,
|
|
26759
|
-
bottom: 0,
|
|
26760
|
-
zIndex: 1,
|
|
26761
|
-
cursor: "pointer"
|
|
26762
|
-
},
|
|
26763
|
-
"aria-label": "Click to play/pause"
|
|
26764
|
-
}
|
|
26765
|
-
),
|
|
26766
|
-
onClick && !controls && /* @__PURE__ */ jsxRuntime.jsx(
|
|
26767
|
-
PlayPauseIndicator,
|
|
26768
|
-
{
|
|
26769
|
-
show: showIndicator,
|
|
26770
|
-
isPlaying: indicatorIsPlaying
|
|
26771
|
-
},
|
|
26772
|
-
indicatorKeyRef.current
|
|
26773
|
-
)
|
|
26774
|
-
] });
|
|
27228
|
+
),
|
|
27229
|
+
isLoading && !externalLoadingControl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) })
|
|
27230
|
+
]
|
|
27231
|
+
}
|
|
27232
|
+
);
|
|
26775
27233
|
});
|
|
26776
27234
|
HlsVideoPlayer.displayName = "HlsVideoPlayer";
|
|
26777
27235
|
var VideoPlayer = HlsVideoPlayer;
|
|
@@ -26779,6 +27237,7 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
26779
27237
|
crop,
|
|
26780
27238
|
debug = false,
|
|
26781
27239
|
onClick,
|
|
27240
|
+
controls = true,
|
|
26782
27241
|
...videoProps
|
|
26783
27242
|
}, ref) => {
|
|
26784
27243
|
const {
|
|
@@ -26800,9 +27259,15 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
26800
27259
|
const [isVideoReady, setIsVideoReady] = React23.useState(false);
|
|
26801
27260
|
const [canvasDimensions, setCanvasDimensions] = React23.useState({ width: 0, height: 0 });
|
|
26802
27261
|
const [isProcessing, setIsProcessing] = React23.useState(false);
|
|
26803
|
-
const [
|
|
26804
|
-
const [
|
|
26805
|
-
const
|
|
27262
|
+
const [showControls, setShowControls] = React23.useState(true);
|
|
27263
|
+
const [isPlaying, setIsPlaying] = React23.useState(false);
|
|
27264
|
+
const [currentTime, setCurrentTime] = React23.useState(0);
|
|
27265
|
+
const [duration, setDuration] = React23.useState(0);
|
|
27266
|
+
const [buffered, setBuffered] = React23.useState(0);
|
|
27267
|
+
const [playbackRate, setPlaybackRate] = React23.useState(1);
|
|
27268
|
+
const controlsTimeoutRef = React23.useRef(null);
|
|
27269
|
+
const userSeekingRef = React23.useRef(false);
|
|
27270
|
+
const [controlsPinned, setControlsPinned] = React23.useState(false);
|
|
26806
27271
|
const stopCanvasRendering = React23.useCallback(() => {
|
|
26807
27272
|
if (animationFrameRef.current) {
|
|
26808
27273
|
cancelAnimationFrame(animationFrameRef.current);
|
|
@@ -26887,7 +27352,7 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
26887
27352
|
const canvas = canvasRef.current;
|
|
26888
27353
|
const video = videoElementRef.current;
|
|
26889
27354
|
const ctx = canvas.getContext("2d");
|
|
26890
|
-
if (!ctx || video.
|
|
27355
|
+
if (!ctx || video.readyState < 2) {
|
|
26891
27356
|
return;
|
|
26892
27357
|
}
|
|
26893
27358
|
const videoWidth = video.videoWidth;
|
|
@@ -26914,7 +27379,9 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
26914
27379
|
canvas.height
|
|
26915
27380
|
// Destination (full canvas)
|
|
26916
27381
|
);
|
|
26917
|
-
|
|
27382
|
+
if (!video.paused && !video.ended) {
|
|
27383
|
+
animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
|
|
27384
|
+
}
|
|
26918
27385
|
}, [crop]);
|
|
26919
27386
|
const handleVideoReady = React23.useCallback((player) => {
|
|
26920
27387
|
console.log("[CroppedHlsVideoPlayer] Video player ready");
|
|
@@ -26922,11 +27389,15 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
26922
27389
|
if (videoEl) {
|
|
26923
27390
|
videoElementRef.current = videoEl;
|
|
26924
27391
|
setIsVideoReady(true);
|
|
27392
|
+
if (videoEl.readyState >= 2) {
|
|
27393
|
+
renderFrameToCanvas();
|
|
27394
|
+
}
|
|
26925
27395
|
}
|
|
26926
27396
|
onReadyProp?.(player);
|
|
26927
|
-
}, [onReadyProp]);
|
|
27397
|
+
}, [onReadyProp, renderFrameToCanvas]);
|
|
26928
27398
|
const handleVideoPlay = React23.useCallback((player) => {
|
|
26929
27399
|
console.log("[CroppedHlsVideoPlayer] Video playing, starting canvas rendering");
|
|
27400
|
+
setIsPlaying(true);
|
|
26930
27401
|
if (crop && canvasRef.current) {
|
|
26931
27402
|
setIsProcessing(true);
|
|
26932
27403
|
renderFrameToCanvas();
|
|
@@ -26934,43 +27405,40 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
26934
27405
|
onPlayProp?.(player);
|
|
26935
27406
|
}, [crop, renderFrameToCanvas, onPlayProp]);
|
|
26936
27407
|
const handleVideoPause = React23.useCallback((player) => {
|
|
26937
|
-
console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering
|
|
27408
|
+
console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering (keeping last frame)");
|
|
27409
|
+
if (userSeekingRef.current && hiddenVideoRef.current) {
|
|
27410
|
+
hiddenVideoRef.current.play()?.catch(() => {
|
|
27411
|
+
});
|
|
27412
|
+
return;
|
|
27413
|
+
}
|
|
26938
27414
|
stopCanvasRendering();
|
|
26939
27415
|
setIsProcessing(false);
|
|
26940
|
-
|
|
26941
|
-
|
|
26942
|
-
if (ctx) {
|
|
26943
|
-
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
26944
|
-
ctx.fillStyle = "black";
|
|
26945
|
-
ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
26946
|
-
}
|
|
26947
|
-
}
|
|
27416
|
+
setIsPlaying(false);
|
|
27417
|
+
renderFrameToCanvas();
|
|
26948
27418
|
onPauseProp?.(player);
|
|
26949
|
-
}, [stopCanvasRendering, onPauseProp]);
|
|
27419
|
+
}, [stopCanvasRendering, onPauseProp, renderFrameToCanvas]);
|
|
26950
27420
|
const handleVideoEnded = React23.useCallback((player) => {
|
|
26951
|
-
console.log("[CroppedHlsVideoPlayer] Video ended,
|
|
27421
|
+
console.log("[CroppedHlsVideoPlayer] Video ended, stopping canvas rendering (keeping last frame)");
|
|
26952
27422
|
stopCanvasRendering();
|
|
26953
27423
|
setIsProcessing(false);
|
|
26954
|
-
|
|
26955
|
-
|
|
26956
|
-
if (ctx) {
|
|
26957
|
-
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
26958
|
-
ctx.fillStyle = "black";
|
|
26959
|
-
ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
26960
|
-
}
|
|
26961
|
-
}
|
|
27424
|
+
setIsPlaying(false);
|
|
27425
|
+
userSeekingRef.current = false;
|
|
26962
27426
|
onEndedProp?.(player);
|
|
26963
27427
|
}, [stopCanvasRendering, onEndedProp]);
|
|
26964
27428
|
const handleSeeking = React23.useCallback((player) => {
|
|
26965
27429
|
console.log("[CroppedHlsVideoPlayer] Video seeking");
|
|
26966
|
-
|
|
27430
|
+
userSeekingRef.current = true;
|
|
27431
|
+
if (crop) {
|
|
26967
27432
|
renderFrameToCanvas();
|
|
26968
27433
|
}
|
|
26969
27434
|
onSeekingProp?.(player);
|
|
26970
27435
|
}, [crop, renderFrameToCanvas, onSeekingProp]);
|
|
26971
27436
|
const handleSeeked = React23.useCallback((player) => {
|
|
26972
27437
|
console.log("[CroppedHlsVideoPlayer] Video seeked");
|
|
26973
|
-
|
|
27438
|
+
hiddenVideoRef.current?.play()?.catch(() => {
|
|
27439
|
+
});
|
|
27440
|
+
userSeekingRef.current = false;
|
|
27441
|
+
if (crop) {
|
|
26974
27442
|
renderFrameToCanvas();
|
|
26975
27443
|
}
|
|
26976
27444
|
onSeekedProp?.(player);
|
|
@@ -26978,8 +27446,29 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
26978
27446
|
const handleLoadedMetadata = React23.useCallback((player) => {
|
|
26979
27447
|
console.log("[CroppedHlsVideoPlayer] Video metadata loaded");
|
|
26980
27448
|
calculateCanvasDimensions();
|
|
27449
|
+
if (hiddenVideoRef.current?.video) {
|
|
27450
|
+
setDuration(hiddenVideoRef.current.video.duration || 0);
|
|
27451
|
+
}
|
|
27452
|
+
requestAnimationFrame(() => renderFrameToCanvas());
|
|
26981
27453
|
onLoadedMetadataProp?.(player);
|
|
26982
|
-
}, [calculateCanvasDimensions, onLoadedMetadataProp]);
|
|
27454
|
+
}, [calculateCanvasDimensions, onLoadedMetadataProp, renderFrameToCanvas]);
|
|
27455
|
+
const handleTimeUpdate = React23.useCallback((player, time2) => {
|
|
27456
|
+
setCurrentTime(time2);
|
|
27457
|
+
if (hiddenVideoRef.current?.video && hiddenVideoRef.current.video.buffered.length > 0) {
|
|
27458
|
+
const video = hiddenVideoRef.current.video;
|
|
27459
|
+
for (let i = 0; i < video.buffered.length; i++) {
|
|
27460
|
+
if (video.buffered.start(i) <= time2 && video.buffered.end(i) >= time2) {
|
|
27461
|
+
setBuffered(video.buffered.end(i));
|
|
27462
|
+
break;
|
|
27463
|
+
}
|
|
27464
|
+
}
|
|
27465
|
+
}
|
|
27466
|
+
videoProps.onTimeUpdate?.(player, time2);
|
|
27467
|
+
}, [videoProps.onTimeUpdate]);
|
|
27468
|
+
const handleDurationChange = React23.useCallback((player, dur) => {
|
|
27469
|
+
setDuration(dur);
|
|
27470
|
+
videoProps.onDurationChange?.(player, dur);
|
|
27471
|
+
}, [videoProps.onDurationChange]);
|
|
26983
27472
|
React23.useEffect(() => {
|
|
26984
27473
|
calculateCanvasDimensions();
|
|
26985
27474
|
const handleResize = () => {
|
|
@@ -27007,33 +27496,97 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
27007
27496
|
stopCanvasRendering();
|
|
27008
27497
|
};
|
|
27009
27498
|
}, [stopCanvasRendering]);
|
|
27499
|
+
const resetControlsTimeout = React23.useCallback(() => {
|
|
27500
|
+
if (controlsPinned) {
|
|
27501
|
+
setShowControls(true);
|
|
27502
|
+
return;
|
|
27503
|
+
}
|
|
27504
|
+
setShowControls(true);
|
|
27505
|
+
if (controlsTimeoutRef.current) {
|
|
27506
|
+
clearTimeout(controlsTimeoutRef.current);
|
|
27507
|
+
}
|
|
27508
|
+
if (isPlaying) {
|
|
27509
|
+
controlsTimeoutRef.current = setTimeout(() => {
|
|
27510
|
+
setShowControls(false);
|
|
27511
|
+
}, 3e3);
|
|
27512
|
+
}
|
|
27513
|
+
}, [isPlaying, controlsPinned]);
|
|
27514
|
+
const handleMouseMove = React23.useCallback(() => {
|
|
27515
|
+
resetControlsTimeout();
|
|
27516
|
+
}, [resetControlsTimeout]);
|
|
27517
|
+
React23.useEffect(() => {
|
|
27518
|
+
resetControlsTimeout();
|
|
27519
|
+
return () => {
|
|
27520
|
+
if (controlsTimeoutRef.current) {
|
|
27521
|
+
clearTimeout(controlsTimeoutRef.current);
|
|
27522
|
+
}
|
|
27523
|
+
};
|
|
27524
|
+
}, [isPlaying, resetControlsTimeout]);
|
|
27525
|
+
const handleTogglePlay = React23.useCallback(() => {
|
|
27526
|
+
if (hiddenVideoRef.current?.video) {
|
|
27527
|
+
if (hiddenVideoRef.current.video.paused) {
|
|
27528
|
+
hiddenVideoRef.current.play();
|
|
27529
|
+
} else {
|
|
27530
|
+
hiddenVideoRef.current.pause();
|
|
27531
|
+
}
|
|
27532
|
+
}
|
|
27533
|
+
}, []);
|
|
27534
|
+
const handleSeek = React23.useCallback((time2) => {
|
|
27535
|
+
if (hiddenVideoRef.current) {
|
|
27536
|
+
hiddenVideoRef.current.currentTime(time2);
|
|
27537
|
+
hiddenVideoRef.current.play()?.catch(() => {
|
|
27538
|
+
});
|
|
27539
|
+
setTimeout(() => renderFrameToCanvas(), 50);
|
|
27540
|
+
}
|
|
27541
|
+
}, [renderFrameToCanvas]);
|
|
27542
|
+
const handleSeekStart = React23.useCallback(() => {
|
|
27543
|
+
userSeekingRef.current = true;
|
|
27544
|
+
}, []);
|
|
27545
|
+
const handleSeekEnd = React23.useCallback(() => {
|
|
27546
|
+
if (hiddenVideoRef.current) {
|
|
27547
|
+
hiddenVideoRef.current.play()?.catch(() => {
|
|
27548
|
+
});
|
|
27549
|
+
}
|
|
27550
|
+
}, []);
|
|
27551
|
+
const handlePlaybackRateChange = React23.useCallback((rate) => {
|
|
27552
|
+
if (hiddenVideoRef.current) {
|
|
27553
|
+
hiddenVideoRef.current.playbackRate(rate);
|
|
27554
|
+
setPlaybackRate(rate);
|
|
27555
|
+
}
|
|
27556
|
+
}, []);
|
|
27557
|
+
const handleToggleFullscreen = React23.useCallback(() => {
|
|
27558
|
+
if (videoContainerRef.current) {
|
|
27559
|
+
if (!document.fullscreenElement) {
|
|
27560
|
+
videoContainerRef.current.requestFullscreen();
|
|
27561
|
+
} else {
|
|
27562
|
+
document.exitFullscreen();
|
|
27563
|
+
}
|
|
27564
|
+
}
|
|
27565
|
+
}, []);
|
|
27010
27566
|
if (!crop) {
|
|
27011
|
-
return /* @__PURE__ */ jsxRuntime.jsx(HlsVideoPlayer, { ref, ...videoProps, onClick });
|
|
27012
|
-
}
|
|
27013
|
-
const
|
|
27014
|
-
if (!onClick
|
|
27015
|
-
|
|
27016
|
-
|
|
27017
|
-
|
|
27018
|
-
setShowIndicator(false);
|
|
27019
|
-
setTimeout(() => {
|
|
27020
|
-
indicatorKeyRef.current += 1;
|
|
27021
|
-
setShowIndicator(true);
|
|
27022
|
-
}, 0);
|
|
27023
|
-
onClick();
|
|
27567
|
+
return /* @__PURE__ */ jsxRuntime.jsx(HlsVideoPlayer, { ref, ...videoProps, onClick, controls });
|
|
27568
|
+
}
|
|
27569
|
+
const handleClick = () => {
|
|
27570
|
+
if (!onClick && !controls) {
|
|
27571
|
+
handleTogglePlay();
|
|
27572
|
+
}
|
|
27573
|
+
if (onClick) onClick();
|
|
27024
27574
|
};
|
|
27025
27575
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
27026
27576
|
"div",
|
|
27027
27577
|
{
|
|
27028
27578
|
ref: videoContainerRef,
|
|
27029
|
-
className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""}
|
|
27030
|
-
onClick:
|
|
27579
|
+
className: `relative w-full h-full flex items-center justify-center bg-black group ${inheritedClassName} ${onClick || controls ? "cursor-pointer" : ""}`,
|
|
27580
|
+
onClick: handleClick,
|
|
27581
|
+
onMouseMove: handleMouseMove,
|
|
27582
|
+
onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
|
|
27031
27583
|
children: [
|
|
27032
27584
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
27033
27585
|
HlsVideoPlayer,
|
|
27034
27586
|
{
|
|
27035
27587
|
ref: hiddenVideoRef,
|
|
27036
27588
|
...videoProps,
|
|
27589
|
+
controls: false,
|
|
27037
27590
|
onReady: handleVideoReady,
|
|
27038
27591
|
onPlay: handleVideoPlay,
|
|
27039
27592
|
onPause: handleVideoPause,
|
|
@@ -27043,7 +27596,9 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
27043
27596
|
onLoadedMetadata: handleLoadedMetadata,
|
|
27044
27597
|
onLoadedData: videoProps.onLoadedData,
|
|
27045
27598
|
onPlaying: videoProps.onPlaying,
|
|
27046
|
-
onLoadingChange: videoProps.onLoadingChange
|
|
27599
|
+
onLoadingChange: videoProps.onLoadingChange,
|
|
27600
|
+
onTimeUpdate: handleTimeUpdate,
|
|
27601
|
+
onDurationChange: handleDurationChange
|
|
27047
27602
|
}
|
|
27048
27603
|
) }),
|
|
27049
27604
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -27060,8 +27615,8 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
27060
27615
|
}
|
|
27061
27616
|
}
|
|
27062
27617
|
),
|
|
27063
|
-
!isVideoReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
|
|
27064
|
-
debug && isVideoReady && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
|
|
27618
|
+
!isVideoReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
|
|
27619
|
+
debug && isVideoReady && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono pointer-events-none z-20", children: [
|
|
27065
27620
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
27066
27621
|
"Crop: ",
|
|
27067
27622
|
crop.x,
|
|
@@ -27085,13 +27640,32 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
|
|
|
27085
27640
|
isProcessing ? "Yes" : "No"
|
|
27086
27641
|
] })
|
|
27087
27642
|
] }),
|
|
27088
|
-
|
|
27089
|
-
|
|
27643
|
+
controls && isVideoReady && /* @__PURE__ */ jsxRuntime.jsx(
|
|
27644
|
+
VideoControls,
|
|
27090
27645
|
{
|
|
27091
|
-
|
|
27092
|
-
|
|
27093
|
-
|
|
27094
|
-
|
|
27646
|
+
isPlaying,
|
|
27647
|
+
currentTime,
|
|
27648
|
+
duration,
|
|
27649
|
+
buffered,
|
|
27650
|
+
showControls: controlsPinned || showControls || !isPlaying,
|
|
27651
|
+
controlsPinned,
|
|
27652
|
+
playbackRate,
|
|
27653
|
+
onPlayPause: handleTogglePlay,
|
|
27654
|
+
onSeek: handleSeek,
|
|
27655
|
+
onSeekStart: handleSeekStart,
|
|
27656
|
+
onSeekEnd: handleSeekEnd,
|
|
27657
|
+
onPlaybackRateChange: handlePlaybackRateChange,
|
|
27658
|
+
onTogglePinControls: () => setControlsPinned((prev) => {
|
|
27659
|
+
const next = !prev;
|
|
27660
|
+
if (next) {
|
|
27661
|
+
setShowControls(true);
|
|
27662
|
+
} else {
|
|
27663
|
+
resetControlsTimeout();
|
|
27664
|
+
}
|
|
27665
|
+
return next;
|
|
27666
|
+
}),
|
|
27667
|
+
onToggleFullscreen: handleToggleFullscreen
|
|
27668
|
+
}
|
|
27095
27669
|
)
|
|
27096
27670
|
]
|
|
27097
27671
|
}
|
|
@@ -27630,6 +28204,67 @@ var SilentErrorBoundary = class extends React23__namespace.default.Component {
|
|
|
27630
28204
|
] }) });
|
|
27631
28205
|
}
|
|
27632
28206
|
};
|
|
28207
|
+
var PlayPauseIndicator = ({
|
|
28208
|
+
show,
|
|
28209
|
+
isPlaying,
|
|
28210
|
+
duration = 600
|
|
28211
|
+
}) => {
|
|
28212
|
+
const [isVisible, setIsVisible] = React23.useState(false);
|
|
28213
|
+
const [isFading, setIsFading] = React23.useState(false);
|
|
28214
|
+
React23.useEffect(() => {
|
|
28215
|
+
if (show) {
|
|
28216
|
+
setIsVisible(true);
|
|
28217
|
+
setIsFading(false);
|
|
28218
|
+
const fadeTimer = setTimeout(() => {
|
|
28219
|
+
setIsFading(true);
|
|
28220
|
+
}, 100);
|
|
28221
|
+
const hideTimer = setTimeout(() => {
|
|
28222
|
+
setIsVisible(false);
|
|
28223
|
+
setIsFading(false);
|
|
28224
|
+
}, duration);
|
|
28225
|
+
return () => {
|
|
28226
|
+
clearTimeout(fadeTimer);
|
|
28227
|
+
clearTimeout(hideTimer);
|
|
28228
|
+
};
|
|
28229
|
+
}
|
|
28230
|
+
}, [show, duration]);
|
|
28231
|
+
if (!isVisible) return null;
|
|
28232
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28233
|
+
"div",
|
|
28234
|
+
{
|
|
28235
|
+
className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
|
|
28236
|
+
style: {
|
|
28237
|
+
opacity: isFading ? 0 : 1,
|
|
28238
|
+
transition: `opacity ${duration - 100}ms ease-out`
|
|
28239
|
+
},
|
|
28240
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
|
|
28241
|
+
// Play icon (triangle)
|
|
28242
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28243
|
+
"svg",
|
|
28244
|
+
{
|
|
28245
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
28246
|
+
viewBox: "0 0 24 24",
|
|
28247
|
+
fill: "white",
|
|
28248
|
+
className: "w-16 h-16",
|
|
28249
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" })
|
|
28250
|
+
}
|
|
28251
|
+
)
|
|
28252
|
+
) : (
|
|
28253
|
+
// Pause icon (two bars)
|
|
28254
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28255
|
+
"svg",
|
|
28256
|
+
{
|
|
28257
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
28258
|
+
viewBox: "0 0 24 24",
|
|
28259
|
+
fill: "white",
|
|
28260
|
+
className: "w-16 h-16",
|
|
28261
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
|
|
28262
|
+
}
|
|
28263
|
+
)
|
|
28264
|
+
) })
|
|
28265
|
+
}
|
|
28266
|
+
);
|
|
28267
|
+
};
|
|
27633
28268
|
var BackButton = ({
|
|
27634
28269
|
onClick,
|
|
27635
28270
|
text = "Back",
|
|
@@ -31049,7 +31684,7 @@ function DiagnosisVideoModal({
|
|
|
31049
31684
|
}
|
|
31050
31685
|
loadClip();
|
|
31051
31686
|
}, [clipId, supabase, transformPlaylistUrls]);
|
|
31052
|
-
const
|
|
31687
|
+
const formatTime5 = (seconds) => {
|
|
31053
31688
|
const mins = Math.floor(seconds / 60);
|
|
31054
31689
|
const secs = Math.floor(seconds % 60);
|
|
31055
31690
|
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
@@ -31241,9 +31876,9 @@ function DiagnosisVideoModal({
|
|
|
31241
31876
|
}
|
|
31242
31877
|
),
|
|
31243
31878
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium", children: [
|
|
31244
|
-
|
|
31879
|
+
formatTime5(currentTime),
|
|
31245
31880
|
" / ",
|
|
31246
|
-
|
|
31881
|
+
formatTime5(duration)
|
|
31247
31882
|
] }),
|
|
31248
31883
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
31249
31884
|
"input",
|
|
@@ -33063,45 +33698,35 @@ var LinePdfGenerator = ({
|
|
|
33063
33698
|
const doc = new jsPDF.jsPDF();
|
|
33064
33699
|
const pageHeight = doc.internal.pageSize.height;
|
|
33065
33700
|
const addHeaderPage1 = () => {
|
|
33066
|
-
doc.setFontSize(
|
|
33701
|
+
doc.setFontSize(14);
|
|
33067
33702
|
doc.setFont("helvetica", "bold");
|
|
33068
|
-
doc.setTextColor(
|
|
33703
|
+
doc.setTextColor(50, 50, 50);
|
|
33069
33704
|
doc.text("OPTIFYE.AI", 20, 15);
|
|
33070
|
-
doc.setFontSize(
|
|
33705
|
+
doc.setFontSize(9);
|
|
33071
33706
|
doc.setFont("helvetica", "normal");
|
|
33072
33707
|
doc.setTextColor(100, 100, 100);
|
|
33073
|
-
const
|
|
33074
|
-
const
|
|
33075
|
-
doc.text(
|
|
33076
|
-
doc.setDrawColor(
|
|
33077
|
-
doc.setLineWidth(0.
|
|
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);
|
|
33078
33713
|
doc.line(20, 20, 190, 20);
|
|
33079
33714
|
};
|
|
33080
33715
|
const addHeaderPage2 = () => {
|
|
33081
|
-
doc.setFontSize(
|
|
33716
|
+
doc.setFontSize(14);
|
|
33082
33717
|
doc.setFont("helvetica", "bold");
|
|
33083
|
-
doc.setTextColor(
|
|
33718
|
+
doc.setTextColor(50, 50, 50);
|
|
33084
33719
|
doc.text("OPTIFYE.AI", 20, 15);
|
|
33085
|
-
doc.setFontSize(
|
|
33720
|
+
doc.setFontSize(9);
|
|
33086
33721
|
doc.setFont("helvetica", "normal");
|
|
33087
33722
|
doc.setTextColor(100, 100, 100);
|
|
33088
|
-
const reportText = "REAL-TIME PERFORMANCE REPORT";
|
|
33089
|
-
const reportTextWidth = doc.getStringUnitWidth(reportText) * 10 / doc.internal.scaleFactor;
|
|
33090
|
-
doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
|
|
33091
|
-
doc.text("Page 2", doc.internal.pageSize.width - 30, pageHeight - 15);
|
|
33092
|
-
doc.setDrawColor(220, 220, 220);
|
|
33093
|
-
doc.setLineWidth(0.3);
|
|
33094
|
-
doc.line(20, 20, 190, 20);
|
|
33095
|
-
return 35;
|
|
33096
|
-
};
|
|
33097
|
-
const addFooter = (pageNum) => {
|
|
33098
|
-
doc.setFontSize(9);
|
|
33099
|
-
doc.setTextColor(130, 130, 130);
|
|
33100
33723
|
const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
|
|
33101
|
-
doc.
|
|
33102
|
-
|
|
33103
|
-
|
|
33104
|
-
|
|
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;
|
|
33105
33730
|
};
|
|
33106
33731
|
addHeaderPage1();
|
|
33107
33732
|
doc.setFontSize(26);
|
|
@@ -33147,37 +33772,47 @@ var LinePdfGenerator = ({
|
|
|
33147
33772
|
doc.setDrawColor(200, 200, 200);
|
|
33148
33773
|
doc.setLineWidth(0.5);
|
|
33149
33774
|
doc.line(20, 53, 190, 53);
|
|
33150
|
-
|
|
33775
|
+
const perfOverviewStartY = 58;
|
|
33776
|
+
doc.setFillColor(245, 245, 245);
|
|
33777
|
+
doc.roundedRect(15, perfOverviewStartY, 180, 50, 3, 3, "F");
|
|
33778
|
+
doc.setFontSize(18);
|
|
33151
33779
|
doc.setFont("helvetica", "bold");
|
|
33152
|
-
doc.setTextColor(
|
|
33153
|
-
doc.text("Line Performance Overview", 20,
|
|
33780
|
+
doc.setTextColor(40, 40, 40);
|
|
33781
|
+
doc.text("Line Performance Overview", 20, 68);
|
|
33154
33782
|
doc.setTextColor(0, 0, 0);
|
|
33155
33783
|
const createKPIBox = (y) => {
|
|
33156
|
-
doc.setFillColor(
|
|
33157
|
-
doc.roundedRect(22, y - 7, 165, 12,
|
|
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");
|
|
33158
33789
|
};
|
|
33159
|
-
|
|
33160
|
-
|
|
33790
|
+
const kpiStartY = 80;
|
|
33791
|
+
const kpiSpacing = 10;
|
|
33792
|
+
createKPIBox(kpiStartY);
|
|
33793
|
+
doc.setFontSize(11);
|
|
33161
33794
|
doc.setFont("helvetica", "normal");
|
|
33162
|
-
doc.text("Output:", 25,
|
|
33795
|
+
doc.text("Output:", 25, kpiStartY);
|
|
33163
33796
|
doc.setFont("helvetica", "bold");
|
|
33164
|
-
doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120,
|
|
33165
|
-
createKPIBox(
|
|
33797
|
+
doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
|
|
33798
|
+
createKPIBox(kpiStartY + kpiSpacing);
|
|
33166
33799
|
doc.setFont("helvetica", "normal");
|
|
33167
|
-
doc.text("Underperforming Workspaces:", 25,
|
|
33800
|
+
doc.text("Underperforming Workspaces:", 25, kpiStartY + kpiSpacing);
|
|
33168
33801
|
doc.setFont("helvetica", "bold");
|
|
33169
|
-
doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120,
|
|
33170
|
-
createKPIBox(
|
|
33802
|
+
doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120, kpiStartY + kpiSpacing);
|
|
33803
|
+
createKPIBox(kpiStartY + kpiSpacing * 2);
|
|
33171
33804
|
doc.setFont("helvetica", "normal");
|
|
33172
|
-
doc.text("Average Efficiency:", 25,
|
|
33805
|
+
doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing * 2);
|
|
33173
33806
|
doc.setFont("helvetica", "bold");
|
|
33174
|
-
doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120,
|
|
33175
|
-
doc.setDrawColor(
|
|
33176
|
-
doc.
|
|
33177
|
-
doc.
|
|
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);
|
|
33178
33813
|
doc.setFont("helvetica", "bold");
|
|
33179
|
-
doc.setTextColor(
|
|
33180
|
-
doc.text("Hourly Output Overview", 20,
|
|
33814
|
+
doc.setTextColor(40, 40, 40);
|
|
33815
|
+
doc.text("Hourly Output Overview", 20, 133);
|
|
33181
33816
|
doc.setTextColor(0, 0, 0);
|
|
33182
33817
|
const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
|
|
33183
33818
|
const [hours, minutes] = startTimeStr.split(":");
|
|
@@ -33214,7 +33849,7 @@ var LinePdfGenerator = ({
|
|
|
33214
33849
|
}
|
|
33215
33850
|
hourEndTime.setSeconds(0);
|
|
33216
33851
|
hourEndTime.setMilliseconds(0);
|
|
33217
|
-
const
|
|
33852
|
+
const formatTime5 = (date2) => {
|
|
33218
33853
|
return date2.toLocaleTimeString("en-IN", {
|
|
33219
33854
|
hour: "2-digit",
|
|
33220
33855
|
minute: "2-digit",
|
|
@@ -33222,7 +33857,7 @@ var LinePdfGenerator = ({
|
|
|
33222
33857
|
timeZone: "Asia/Kolkata"
|
|
33223
33858
|
});
|
|
33224
33859
|
};
|
|
33225
|
-
return `${
|
|
33860
|
+
return `${formatTime5(hourStartTime)} - ${formatTime5(hourEndTime)}`;
|
|
33226
33861
|
});
|
|
33227
33862
|
};
|
|
33228
33863
|
const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
|
|
@@ -33369,86 +34004,94 @@ var LinePdfGenerator = ({
|
|
|
33369
34004
|
return Math.round(lineInfo.metrics.current_output / shiftDuration);
|
|
33370
34005
|
});
|
|
33371
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");
|
|
33372
34015
|
doc.setFontSize(11);
|
|
33373
34016
|
doc.setFont("helvetica", "bold");
|
|
33374
|
-
doc.
|
|
33375
|
-
doc.
|
|
33376
|
-
doc.text("
|
|
33377
|
-
doc.text("
|
|
33378
|
-
doc.
|
|
33379
|
-
doc.
|
|
33380
|
-
doc.
|
|
33381
|
-
doc.setDrawColor(220, 220, 220);
|
|
33382
|
-
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);
|
|
33383
34024
|
doc.setFont("helvetica", "normal");
|
|
33384
|
-
let yPos =
|
|
33385
|
-
const
|
|
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
|
+
}
|
|
33386
34037
|
hourlyTimeRanges.forEach((timeRange, index) => {
|
|
33387
|
-
|
|
33388
|
-
|
|
33389
|
-
|
|
33390
|
-
|
|
33391
|
-
const
|
|
33392
|
-
const
|
|
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();
|
|
33393
34045
|
doc.text(timeRange, 25, yPos);
|
|
33394
|
-
doc.text(
|
|
33395
|
-
doc.text(
|
|
33396
|
-
if (
|
|
33397
|
-
doc.
|
|
33398
|
-
doc.
|
|
33399
|
-
|
|
33400
|
-
|
|
33401
|
-
doc.
|
|
33402
|
-
doc.
|
|
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");
|
|
33403
34056
|
} else {
|
|
33404
|
-
doc.
|
|
33405
|
-
doc.
|
|
33406
|
-
const crossX = 170;
|
|
33407
|
-
const crossY = yPos - 1;
|
|
33408
|
-
doc.line(crossX - 2, crossY - 2, crossX + 2, crossY + 2);
|
|
33409
|
-
doc.line(crossX - 2, crossY + 2, crossX + 2, crossY - 2);
|
|
34057
|
+
doc.setTextColor(227, 67, 41);
|
|
34058
|
+
doc.text("\xD7", 170, yPos);
|
|
33410
34059
|
}
|
|
33411
|
-
doc.
|
|
33412
|
-
doc.setLineWidth(0.2);
|
|
34060
|
+
doc.setTextColor(0, 0, 0);
|
|
33413
34061
|
yPos += rowSpacing;
|
|
33414
34062
|
});
|
|
33415
|
-
doc.roundedRect(20, 140, 170, yPos - 140 - 3, 1, 1, "S");
|
|
33416
|
-
addFooter(1);
|
|
33417
34063
|
doc.addPage();
|
|
33418
34064
|
yPos = addHeaderPage2();
|
|
33419
|
-
|
|
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);
|
|
33420
34072
|
doc.setFont("helvetica", "bold");
|
|
33421
|
-
doc.setTextColor(
|
|
34073
|
+
doc.setTextColor(40, 40, 40);
|
|
33422
34074
|
doc.text("Poorest Performing Workspaces", 20, yPos);
|
|
33423
34075
|
doc.setTextColor(0, 0, 0);
|
|
33424
34076
|
yPos += 10;
|
|
33425
34077
|
doc.setFontSize(11);
|
|
33426
34078
|
doc.setFont("helvetica", "bold");
|
|
33427
|
-
doc.setFillColor(245, 245, 245);
|
|
33428
|
-
doc.roundedRect(20, yPos, 170, 8, 1, 1, "F");
|
|
33429
34079
|
yPos += 5;
|
|
33430
34080
|
doc.text("Workspace", 25, yPos);
|
|
33431
|
-
doc.text("Current/
|
|
34081
|
+
doc.text("Current/Target", 85, yPos);
|
|
33432
34082
|
doc.text("Efficiency", 145, yPos);
|
|
33433
34083
|
yPos += 3;
|
|
33434
|
-
doc.setLineWidth(0.
|
|
33435
|
-
doc.setDrawColor(
|
|
34084
|
+
doc.setLineWidth(0.3);
|
|
34085
|
+
doc.setDrawColor(200, 200, 200);
|
|
33436
34086
|
doc.line(20, yPos, 190, yPos);
|
|
33437
34087
|
doc.setFont("helvetica", "normal");
|
|
33438
|
-
const tableStartY = yPos;
|
|
33439
34088
|
yPos += 7;
|
|
33440
|
-
const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
|
|
33441
34089
|
if (sortedWorkspaces.length === 0) {
|
|
33442
34090
|
doc.text("No workspace data available", 25, yPos);
|
|
33443
34091
|
yPos += 10;
|
|
33444
34092
|
} else {
|
|
33445
34093
|
sortedWorkspaces.forEach((ws, index) => {
|
|
33446
|
-
if (index % 2 === 0) {
|
|
33447
|
-
doc.setFillColor(252, 252, 252);
|
|
33448
|
-
doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
|
|
33449
|
-
}
|
|
33450
34094
|
const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
|
|
33451
|
-
const maxWidth = 55;
|
|
33452
34095
|
const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
|
|
33453
34096
|
doc.text(truncatedName, 25, yPos);
|
|
33454
34097
|
doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
|
|
@@ -33456,9 +34099,6 @@ var LinePdfGenerator = ({
|
|
|
33456
34099
|
yPos += 8;
|
|
33457
34100
|
});
|
|
33458
34101
|
}
|
|
33459
|
-
const wsTableHeight = yPos - tableStartY - 3;
|
|
33460
|
-
doc.roundedRect(20, tableStartY, 170, wsTableHeight, 1, 1, "S");
|
|
33461
|
-
addFooter(2);
|
|
33462
34102
|
doc.save(`${lineInfo.line_name}_${date.split(",")[0]}.pdf`);
|
|
33463
34103
|
} catch (error) {
|
|
33464
34104
|
console.error("PDF generation failed:", error);
|
|
@@ -34632,25 +35272,25 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
34632
35272
|
doc.setFont("helvetica", "bold");
|
|
34633
35273
|
doc.setTextColor(50, 50, 50);
|
|
34634
35274
|
doc.text("OPTIFYE.AI", 20, 15);
|
|
34635
|
-
doc.setFontSize(
|
|
35275
|
+
doc.setFontSize(9);
|
|
34636
35276
|
doc.setFont("helvetica", "normal");
|
|
34637
|
-
doc.setTextColor(
|
|
34638
|
-
const
|
|
34639
|
-
const
|
|
34640
|
-
doc.text(
|
|
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);
|
|
34641
35281
|
doc.setDrawColor(200, 200, 200);
|
|
34642
35282
|
doc.setLineWidth(0.5);
|
|
34643
35283
|
doc.line(20, 20, 190, 20);
|
|
34644
35284
|
doc.setFillColor(250, 250, 250);
|
|
34645
|
-
doc.roundedRect(15, 25, 180,
|
|
35285
|
+
doc.roundedRect(15, 25, 180, 53, 3, 3, "F");
|
|
34646
35286
|
doc.setFontSize(32);
|
|
34647
35287
|
doc.setFont("helvetica", "bold");
|
|
34648
35288
|
doc.setTextColor(0, 0, 0);
|
|
34649
|
-
doc.text(lineName, 20,
|
|
35289
|
+
doc.text(lineName, 20, 39);
|
|
34650
35290
|
doc.setFontSize(22);
|
|
34651
35291
|
doc.setFont("helvetica", "normal");
|
|
34652
35292
|
doc.setTextColor(40, 40, 40);
|
|
34653
|
-
doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20,
|
|
35293
|
+
doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20, 51);
|
|
34654
35294
|
doc.setFontSize(13);
|
|
34655
35295
|
doc.setFont("helvetica", "normal");
|
|
34656
35296
|
doc.setTextColor(60, 60, 60);
|
|
@@ -34661,8 +35301,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
34661
35301
|
timeZone: "Asia/Kolkata"
|
|
34662
35302
|
});
|
|
34663
35303
|
const shiftType = "Day Shift";
|
|
34664
|
-
doc.text(`${date}`, 20,
|
|
34665
|
-
doc.text(`${shiftType}`, 20,
|
|
35304
|
+
doc.text(`${date}`, 20, 63);
|
|
35305
|
+
doc.text(`${shiftType}`, 20, 71);
|
|
34666
35306
|
const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
|
|
34667
35307
|
hour: "2-digit",
|
|
34668
35308
|
minute: "2-digit",
|
|
@@ -34675,11 +35315,11 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
34675
35315
|
});
|
|
34676
35316
|
doc.setFontSize(12);
|
|
34677
35317
|
doc.setTextColor(80, 80, 80);
|
|
34678
|
-
doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20,
|
|
35318
|
+
doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20, 79);
|
|
34679
35319
|
doc.setTextColor(0, 0, 0);
|
|
34680
35320
|
doc.setDrawColor(180, 180, 180);
|
|
34681
35321
|
doc.setLineWidth(0.8);
|
|
34682
|
-
doc.line(20,
|
|
35322
|
+
doc.line(20, 88, 190, 88);
|
|
34683
35323
|
const createKPIBox = (y) => {
|
|
34684
35324
|
doc.setFillColor(255, 255, 255);
|
|
34685
35325
|
doc.roundedRect(22, y - 7, 165, 12, 2, 2, "F");
|
|
@@ -34687,66 +35327,72 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
34687
35327
|
doc.setLineWidth(0.2);
|
|
34688
35328
|
doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
|
|
34689
35329
|
};
|
|
35330
|
+
const perfOverviewStartY = 93;
|
|
34690
35331
|
doc.setFillColor(245, 245, 245);
|
|
34691
|
-
doc.roundedRect(15,
|
|
35332
|
+
doc.roundedRect(15, perfOverviewStartY, 180, 60, 3, 3, "F");
|
|
34692
35333
|
doc.setFontSize(18);
|
|
34693
35334
|
doc.setFont("helvetica", "bold");
|
|
34694
35335
|
doc.setTextColor(40, 40, 40);
|
|
34695
|
-
doc.text("Performance Overview", 20,
|
|
35336
|
+
doc.text("Performance Overview", 20, 103);
|
|
34696
35337
|
doc.setTextColor(0, 0, 0);
|
|
34697
|
-
const kpiStartY =
|
|
35338
|
+
const kpiStartY = 115;
|
|
34698
35339
|
const kpiSpacing = 10;
|
|
34699
35340
|
createKPIBox(kpiStartY);
|
|
34700
35341
|
doc.setFontSize(11);
|
|
34701
35342
|
doc.setFont("helvetica", "normal");
|
|
34702
|
-
doc.text("
|
|
35343
|
+
doc.text("Current Output/Target Output:", 25, kpiStartY);
|
|
34703
35344
|
doc.setFont("helvetica", "bold");
|
|
34704
|
-
doc.text(`${
|
|
35345
|
+
doc.text(`${workspace.total_actions} / ${workspace.target_output}`, 120, kpiStartY);
|
|
34705
35346
|
createKPIBox(kpiStartY + kpiSpacing);
|
|
34706
35347
|
doc.setFont("helvetica", "normal");
|
|
34707
|
-
doc.text("
|
|
35348
|
+
doc.text("Efficiency:", 25, kpiStartY + kpiSpacing);
|
|
34708
35349
|
doc.setFont("helvetica", "bold");
|
|
34709
|
-
doc.text(`${workspace.
|
|
35350
|
+
doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing);
|
|
34710
35351
|
createKPIBox(kpiStartY + kpiSpacing * 2);
|
|
34711
35352
|
doc.setFont("helvetica", "normal");
|
|
34712
|
-
doc.text("
|
|
34713
|
-
doc.setFont("helvetica", "bold");
|
|
34714
|
-
doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing * 2);
|
|
34715
|
-
createKPIBox(kpiStartY + kpiSpacing * 3);
|
|
34716
|
-
doc.setFont("helvetica", "normal");
|
|
34717
|
-
doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 3);
|
|
35353
|
+
doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
|
|
34718
35354
|
doc.setFont("helvetica", "bold");
|
|
34719
|
-
doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing *
|
|
35355
|
+
doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 2);
|
|
34720
35356
|
doc.setDrawColor(180, 180, 180);
|
|
34721
35357
|
doc.setLineWidth(0.8);
|
|
34722
|
-
doc.line(20,
|
|
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;
|
|
34723
35366
|
doc.setFillColor(245, 245, 245);
|
|
34724
|
-
doc.roundedRect(15,
|
|
35367
|
+
doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
|
|
34725
35368
|
doc.setFontSize(18);
|
|
34726
35369
|
doc.setFont("helvetica", "bold");
|
|
34727
35370
|
doc.setTextColor(40, 40, 40);
|
|
34728
|
-
doc.text("Hourly Performance", 20,
|
|
35371
|
+
doc.text("Hourly Performance", 20, 178);
|
|
34729
35372
|
doc.setTextColor(0, 0, 0);
|
|
34730
35373
|
doc.setFontSize(11);
|
|
34731
35374
|
doc.setFont("helvetica", "bold");
|
|
34732
|
-
doc.
|
|
34733
|
-
doc.
|
|
34734
|
-
doc.text("
|
|
34735
|
-
doc.text("
|
|
34736
|
-
doc.
|
|
34737
|
-
doc.
|
|
34738
|
-
doc.
|
|
34739
|
-
doc.setDrawColor(220, 220, 220);
|
|
34740
|
-
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);
|
|
34741
35382
|
doc.setFont("helvetica", "normal");
|
|
34742
|
-
let yPos =
|
|
34743
|
-
const
|
|
34744
|
-
const
|
|
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
|
+
}
|
|
34745
35395
|
hourlyData.forEach((output, index) => {
|
|
34746
|
-
if (index % 2 === 0) {
|
|
34747
|
-
doc.setFillColor(252, 252, 252);
|
|
34748
|
-
doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
|
|
34749
|
-
}
|
|
34750
35396
|
const startTime = /* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`);
|
|
34751
35397
|
startTime.setHours(startTime.getHours() + index);
|
|
34752
35398
|
const endTime = new Date(startTime);
|
|
@@ -34758,14 +35404,17 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
34758
35404
|
hour: "numeric",
|
|
34759
35405
|
hour12: true
|
|
34760
35406
|
})}`;
|
|
34761
|
-
const
|
|
35407
|
+
const hourNumber = startTime.getHours();
|
|
35408
|
+
const dataCollected = !isToday || hourNumber < currentHour;
|
|
35409
|
+
const outputStr = dataCollected ? output.toString() : "TBD";
|
|
34762
35410
|
const targetStr = hourlyTarget.toString();
|
|
34763
|
-
const outputX = 95 + doc.getStringUnitWidth(outputStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
|
|
34764
|
-
const targetX = 135 + doc.getStringUnitWidth(targetStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
|
|
34765
35411
|
doc.text(timeRange, 25, yPos);
|
|
34766
|
-
doc.text(outputStr,
|
|
34767
|
-
doc.text(targetStr,
|
|
34768
|
-
if (
|
|
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) {
|
|
34769
35418
|
doc.setTextColor(0, 171, 69);
|
|
34770
35419
|
doc.setFont("ZapfDingbats", "normal");
|
|
34771
35420
|
doc.text("4", 170, yPos);
|
|
@@ -34775,15 +35424,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
34775
35424
|
doc.text("\xD7", 170, yPos);
|
|
34776
35425
|
}
|
|
34777
35426
|
doc.setTextColor(0, 0, 0);
|
|
34778
|
-
yPos +=
|
|
35427
|
+
yPos += rowHeight;
|
|
34779
35428
|
});
|
|
34780
|
-
doc.setLineWidth(0.2);
|
|
34781
|
-
doc.setDrawColor(220, 220, 220);
|
|
34782
|
-
doc.roundedRect(20, 187, 170, yPos - 187 - 3, 1, 1, "S");
|
|
34783
|
-
doc.setFontSize(9);
|
|
34784
|
-
doc.setTextColor(130, 130, 130);
|
|
34785
|
-
const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
|
|
34786
|
-
doc.text(generatedText, 20, 280);
|
|
34787
35429
|
doc.save(`${workspace.workspace_name}_${date.split(",")[0]}.pdf`);
|
|
34788
35430
|
} catch (error) {
|
|
34789
35431
|
console.error("PDF generation failed:", error);
|
|
@@ -35863,7 +36505,8 @@ var WorkspaceHealthCard = ({
|
|
|
35863
36505
|
workspace,
|
|
35864
36506
|
onClick,
|
|
35865
36507
|
showDetails = true,
|
|
35866
|
-
className = ""
|
|
36508
|
+
className = "",
|
|
36509
|
+
onViewDetails
|
|
35867
36510
|
}) => {
|
|
35868
36511
|
const getStatusConfig = () => {
|
|
35869
36512
|
switch (workspace.status) {
|
|
@@ -35916,38 +36559,101 @@ var WorkspaceHealthCard = ({
|
|
|
35916
36559
|
onClick(workspace);
|
|
35917
36560
|
}
|
|
35918
36561
|
};
|
|
36562
|
+
const handleViewDetails = (event) => {
|
|
36563
|
+
event.stopPropagation();
|
|
36564
|
+
event.preventDefault();
|
|
36565
|
+
if (onViewDetails) {
|
|
36566
|
+
onViewDetails(workspace);
|
|
36567
|
+
}
|
|
36568
|
+
};
|
|
35919
36569
|
const handleKeyDown = (event) => {
|
|
35920
36570
|
if (onClick && (event.key === "Enter" || event.key === " ")) {
|
|
35921
36571
|
event.preventDefault();
|
|
35922
36572
|
onClick(workspace);
|
|
35923
36573
|
}
|
|
35924
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
|
+
};
|
|
35925
36590
|
const formatTimeAgo = (timeString) => {
|
|
35926
|
-
|
|
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`;
|
|
35927
36620
|
};
|
|
35928
|
-
const
|
|
35929
|
-
if (!uptimeDetails)
|
|
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
|
+
}
|
|
35930
36632
|
const downtimeMinutes = Math.max(0, uptimeDetails.expectedMinutes - uptimeDetails.actualMinutes);
|
|
35931
|
-
if (downtimeMinutes === 0)
|
|
35932
|
-
|
|
35933
|
-
|
|
35934
|
-
|
|
35935
|
-
|
|
35936
|
-
|
|
35937
|
-
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
|
+
};
|
|
35938
36639
|
}
|
|
35939
|
-
return
|
|
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
|
+
};
|
|
35940
36645
|
};
|
|
36646
|
+
const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
|
|
35941
36647
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
35942
36648
|
Card2,
|
|
35943
36649
|
{
|
|
35944
36650
|
className: clsx(
|
|
35945
|
-
"relative overflow-hidden transition-all duration-300",
|
|
36651
|
+
"relative overflow-hidden transition-all duration-300 h-full flex flex-col",
|
|
35946
36652
|
"bg-gradient-to-br",
|
|
35947
36653
|
config.gradient,
|
|
35948
36654
|
"border",
|
|
35949
36655
|
config.border,
|
|
35950
|
-
"shadow-sm hover:shadow-
|
|
36656
|
+
"shadow-sm hover:shadow-lg hover:border-blue-300 dark:hover:border-blue-700",
|
|
35951
36657
|
onClick && "cursor-pointer hover:scale-[1.01]",
|
|
35952
36658
|
workspace.isStale && "opacity-90",
|
|
35953
36659
|
className
|
|
@@ -35957,7 +36663,7 @@ var WorkspaceHealthCard = ({
|
|
|
35957
36663
|
tabIndex: onClick ? 0 : void 0,
|
|
35958
36664
|
role: onClick ? "button" : void 0,
|
|
35959
36665
|
"aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
|
|
35960
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
|
|
36666
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 flex-grow flex flex-col", children: [
|
|
35961
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: [
|
|
35962
36668
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
35963
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)}` }),
|
|
@@ -35979,21 +36685,20 @@ var WorkspaceHealthCard = ({
|
|
|
35979
36685
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
|
|
35980
36686
|
] })
|
|
35981
36687
|
] }),
|
|
35982
|
-
|
|
35983
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
35984
|
-
/* @__PURE__ */ jsxRuntime.
|
|
35985
|
-
|
|
35986
|
-
|
|
35987
|
-
|
|
35988
|
-
|
|
35989
|
-
|
|
35990
|
-
|
|
35991
|
-
|
|
35992
|
-
|
|
35993
|
-
"
|
|
35994
|
-
|
|
35995
|
-
|
|
35996
|
-
] })
|
|
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
|
+
) })
|
|
35997
36702
|
] })
|
|
35998
36703
|
}
|
|
35999
36704
|
);
|
|
@@ -36001,8 +36706,11 @@ var WorkspaceHealthCard = ({
|
|
|
36001
36706
|
var CompactWorkspaceHealthCard = ({
|
|
36002
36707
|
workspace,
|
|
36003
36708
|
onClick,
|
|
36004
|
-
className = ""
|
|
36709
|
+
className = "",
|
|
36710
|
+
onViewDetails
|
|
36005
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`;
|
|
36006
36714
|
const getStatusConfig = () => {
|
|
36007
36715
|
switch (workspace.status) {
|
|
36008
36716
|
case "healthy":
|
|
@@ -36042,6 +36750,13 @@ var CompactWorkspaceHealthCard = ({
|
|
|
36042
36750
|
onClick(workspace);
|
|
36043
36751
|
}
|
|
36044
36752
|
};
|
|
36753
|
+
const handleViewDetails = (event) => {
|
|
36754
|
+
event.stopPropagation();
|
|
36755
|
+
event.preventDefault();
|
|
36756
|
+
if (onViewDetails) {
|
|
36757
|
+
onViewDetails(workspace);
|
|
36758
|
+
}
|
|
36759
|
+
};
|
|
36045
36760
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
36046
36761
|
"div",
|
|
36047
36762
|
{
|
|
@@ -36069,14 +36784,20 @@ var CompactWorkspaceHealthCard = ({
|
|
|
36069
36784
|
workspace.uptimePercentage.toFixed(1),
|
|
36070
36785
|
"%"
|
|
36071
36786
|
] }),
|
|
36072
|
-
|
|
36073
|
-
|
|
36074
|
-
Math.max(0, workspace.uptimeDetails.expectedMinutes - workspace.uptimeDetails.actualMinutes),
|
|
36075
|
-
"m down"
|
|
36076
|
-
] })
|
|
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 })
|
|
36077
36789
|
] }),
|
|
36078
36790
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
|
|
36079
|
-
/* @__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
|
+
)
|
|
36080
36801
|
] })
|
|
36081
36802
|
]
|
|
36082
36803
|
}
|
|
@@ -36085,6 +36806,7 @@ var CompactWorkspaceHealthCard = ({
|
|
|
36085
36806
|
var HealthStatusGrid = ({
|
|
36086
36807
|
workspaces,
|
|
36087
36808
|
onWorkspaceClick,
|
|
36809
|
+
onWorkspaceViewDetails,
|
|
36088
36810
|
showFilters = true,
|
|
36089
36811
|
groupBy: initialGroupBy = "none",
|
|
36090
36812
|
className = ""
|
|
@@ -36272,7 +36994,8 @@ var HealthStatusGrid = ({
|
|
|
36272
36994
|
{
|
|
36273
36995
|
workspace,
|
|
36274
36996
|
onClick: onWorkspaceClick,
|
|
36275
|
-
showDetails: true
|
|
36997
|
+
showDetails: true,
|
|
36998
|
+
onViewDetails: onWorkspaceViewDetails
|
|
36276
36999
|
},
|
|
36277
37000
|
workspace.workspace_id
|
|
36278
37001
|
)) })
|
|
@@ -39808,7 +40531,7 @@ var AIAgentView = () => {
|
|
|
39808
40531
|
}
|
|
39809
40532
|
return formattedLines.join("");
|
|
39810
40533
|
};
|
|
39811
|
-
const
|
|
40534
|
+
const formatTime5 = (timestamp) => {
|
|
39812
40535
|
const date = new Date(timestamp);
|
|
39813
40536
|
return date.toLocaleTimeString([], {
|
|
39814
40537
|
hour: "2-digit",
|
|
@@ -41072,7 +41795,7 @@ var AIAgentView = () => {
|
|
|
41072
41795
|
}
|
|
41073
41796
|
),
|
|
41074
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: [
|
|
41075
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
41798
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime5(message.created_at) }),
|
|
41076
41799
|
message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
41077
41800
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
|
|
41078
41801
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Axel" })
|
|
@@ -43903,8 +44626,7 @@ var KPIsOverviewView = ({
|
|
|
43903
44626
|
" Shift"
|
|
43904
44627
|
] })
|
|
43905
44628
|
] })
|
|
43906
|
-
] }) })
|
|
43907
|
-
/* @__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
|
+
] }) })
|
|
43908
44630
|
] }) }),
|
|
43909
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(
|
|
43910
44632
|
LineCard,
|
|
@@ -48221,18 +48943,41 @@ var useWorkspaceHealth = (options) => {
|
|
|
48221
48943
|
const isFetchingRef = React23.useRef(false);
|
|
48222
48944
|
const refreshIntervalRef = React23.useRef(null);
|
|
48223
48945
|
const healthTable = databaseConfig?.tables?.workspace_health || "workspace_health_status";
|
|
48224
|
-
const
|
|
48225
|
-
|
|
48226
|
-
const
|
|
48227
|
-
const
|
|
48228
|
-
const
|
|
48229
|
-
|
|
48230
|
-
|
|
48231
|
-
|
|
48232
|
-
|
|
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
|
+
}, []);
|
|
48233
48976
|
const fetchWorkspacesHealth = React23.useCallback(async () => {
|
|
48234
|
-
if (
|
|
48977
|
+
if (isFetchingRef.current) return;
|
|
48235
48978
|
if (!options.companyId) {
|
|
48979
|
+
setWorkspaces([]);
|
|
48980
|
+
setSummary(computeSummary([]));
|
|
48236
48981
|
setLoading(false);
|
|
48237
48982
|
return;
|
|
48238
48983
|
}
|
|
@@ -48240,39 +48985,12 @@ var useWorkspaceHealth = (options) => {
|
|
|
48240
48985
|
isFetchingRef.current = true;
|
|
48241
48986
|
setLoading(true);
|
|
48242
48987
|
setError(null);
|
|
48243
|
-
|
|
48244
|
-
|
|
48245
|
-
|
|
48246
|
-
}
|
|
48247
|
-
query = query.order("workspace_display_name", { ascending: true });
|
|
48248
|
-
const { data, error: fetchError } = await query;
|
|
48249
|
-
if (fetchError) throw fetchError;
|
|
48250
|
-
const healthData = data || [];
|
|
48251
|
-
const workspacesWithStatus = healthData.map((ws) => {
|
|
48252
|
-
const status = calculateHealthStatus(ws);
|
|
48253
|
-
const now2 = /* @__PURE__ */ new Date();
|
|
48254
|
-
const lastUpdate = ws.last_heartbeat ? new Date(ws.last_heartbeat) : null;
|
|
48255
|
-
const timeSinceLastUpdate = lastUpdate ? `${Math.floor((now2.getTime() - lastUpdate.getTime()) / 6e4)}m ago` : "Never";
|
|
48256
|
-
return {
|
|
48257
|
-
...ws,
|
|
48258
|
-
status,
|
|
48259
|
-
timeSinceLastUpdate,
|
|
48260
|
-
isStale: !lastUpdate || now2.getTime() - lastUpdate.getTime() > 15 * 6e4
|
|
48261
|
-
};
|
|
48988
|
+
const workspacesWithStatus = await workspaceHealthService.getWorkspaceHealthStatus({
|
|
48989
|
+
lineId: options.lineId,
|
|
48990
|
+
companyId: options.companyId
|
|
48262
48991
|
});
|
|
48263
48992
|
setWorkspaces(workspacesWithStatus);
|
|
48264
|
-
|
|
48265
|
-
const healthy = workspacesWithStatus.filter((w) => w.status === "healthy").length;
|
|
48266
|
-
const unhealthy = workspacesWithStatus.filter((w) => w.status === "unhealthy").length;
|
|
48267
|
-
const warning6 = workspacesWithStatus.filter((w) => w.status === "warning").length;
|
|
48268
|
-
setSummary({
|
|
48269
|
-
totalWorkspaces: total,
|
|
48270
|
-
healthyWorkspaces: healthy,
|
|
48271
|
-
unhealthyWorkspaces: unhealthy,
|
|
48272
|
-
warningWorkspaces: warning6,
|
|
48273
|
-
uptimePercentage: total > 0 ? healthy / total * 100 : 100,
|
|
48274
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
48275
|
-
});
|
|
48993
|
+
setSummary(computeSummary(workspacesWithStatus));
|
|
48276
48994
|
} catch (err) {
|
|
48277
48995
|
console.error("[useWorkspaceHealth] Error fetching workspace health:", err);
|
|
48278
48996
|
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
@@ -48281,7 +48999,7 @@ var useWorkspaceHealth = (options) => {
|
|
|
48281
48999
|
setLoading(false);
|
|
48282
49000
|
isFetchingRef.current = false;
|
|
48283
49001
|
}
|
|
48284
|
-
}, [
|
|
49002
|
+
}, [options.companyId, options.lineId, computeSummary]);
|
|
48285
49003
|
React23.useEffect(() => {
|
|
48286
49004
|
fetchWorkspacesHealth();
|
|
48287
49005
|
}, [fetchWorkspacesHealth]);
|
|
@@ -48326,6 +49044,395 @@ var useWorkspaceHealth = (options) => {
|
|
|
48326
49044
|
refetch: fetchWorkspacesHealth
|
|
48327
49045
|
};
|
|
48328
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;
|
|
48329
49436
|
var WorkspaceHealthView = ({
|
|
48330
49437
|
lineId,
|
|
48331
49438
|
companyId,
|
|
@@ -48335,6 +49442,7 @@ var WorkspaceHealthView = ({
|
|
|
48335
49442
|
const router$1 = router.useRouter();
|
|
48336
49443
|
const [groupBy, setGroupBy] = React23.useState("line");
|
|
48337
49444
|
const timezone = useAppTimezone();
|
|
49445
|
+
const [selectedWorkspace, setSelectedWorkspace] = React23.useState(null);
|
|
48338
49446
|
const operationalDate = getOperationalDate(timezone || "UTC");
|
|
48339
49447
|
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
48340
49448
|
const isNightShift = currentHour >= 18 || currentHour < 6;
|
|
@@ -48375,6 +49483,12 @@ var WorkspaceHealthView = ({
|
|
|
48375
49483
|
},
|
|
48376
49484
|
[router$1, onNavigate]
|
|
48377
49485
|
);
|
|
49486
|
+
const handleViewDetails = React23.useCallback((workspace) => {
|
|
49487
|
+
setSelectedWorkspace(workspace);
|
|
49488
|
+
}, []);
|
|
49489
|
+
const handleCloseDetails = React23.useCallback(() => {
|
|
49490
|
+
setSelectedWorkspace(null);
|
|
49491
|
+
}, []);
|
|
48378
49492
|
const handleExport = React23.useCallback(() => {
|
|
48379
49493
|
const csv = [
|
|
48380
49494
|
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
@@ -48432,178 +49546,189 @@ var WorkspaceHealthView = ({
|
|
|
48432
49546
|
)
|
|
48433
49547
|
] }) }) }) });
|
|
48434
49548
|
}
|
|
48435
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48436
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
48437
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
48438
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
48439
|
-
/* @__PURE__ */ jsxRuntime.
|
|
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(
|
|
48440
49596
|
BackButtonMinimal,
|
|
48441
49597
|
{
|
|
48442
49598
|
onClick: () => router$1.push("/"),
|
|
48443
49599
|
text: "Back",
|
|
48444
|
-
size: "
|
|
49600
|
+
size: "default",
|
|
48445
49601
|
"aria-label": "Navigate back to dashboard"
|
|
48446
49602
|
}
|
|
48447
|
-
),
|
|
48448
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-
|
|
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: [
|
|
48449
49612
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48450
49613
|
"button",
|
|
48451
49614
|
{
|
|
48452
49615
|
onClick: () => {
|
|
48453
49616
|
refetch();
|
|
48454
49617
|
},
|
|
48455
|
-
className: "p-
|
|
49618
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48456
49619
|
"aria-label": "Refresh",
|
|
48457
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-
|
|
49620
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
48458
49621
|
}
|
|
48459
49622
|
),
|
|
48460
49623
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48461
49624
|
"button",
|
|
48462
49625
|
{
|
|
48463
49626
|
onClick: handleExport,
|
|
48464
|
-
className: "p-
|
|
49627
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48465
49628
|
"aria-label": "Export CSV",
|
|
48466
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-
|
|
49629
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
48467
49630
|
}
|
|
48468
49631
|
)
|
|
48469
|
-
] })
|
|
48470
|
-
|
|
48471
|
-
|
|
48472
|
-
|
|
48473
|
-
/* @__PURE__ */ jsxRuntime.
|
|
48474
|
-
|
|
48475
|
-
|
|
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
|
+
] })
|
|
48476
49646
|
] })
|
|
48477
49647
|
] }) })
|
|
48478
49648
|
] }),
|
|
48479
|
-
/* @__PURE__ */ jsxRuntime.
|
|
48480
|
-
|
|
48481
|
-
|
|
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,
|
|
48482
49652
|
{
|
|
48483
|
-
|
|
48484
|
-
|
|
48485
|
-
|
|
48486
|
-
"
|
|
48487
|
-
|
|
48488
|
-
|
|
48489
|
-
|
|
48490
|
-
|
|
48491
|
-
|
|
48492
|
-
|
|
48493
|
-
|
|
48494
|
-
|
|
48495
|
-
|
|
48496
|
-
|
|
48497
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48498
|
-
"button",
|
|
48499
|
-
{
|
|
48500
|
-
onClick: () => {
|
|
48501
|
-
refetch();
|
|
48502
|
-
},
|
|
48503
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48504
|
-
"aria-label": "Refresh",
|
|
48505
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
48506
|
-
}
|
|
48507
|
-
),
|
|
48508
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48509
|
-
"button",
|
|
48510
|
-
{
|
|
48511
|
-
onClick: handleExport,
|
|
48512
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48513
|
-
"aria-label": "Export CSV",
|
|
48514
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
48515
|
-
}
|
|
48516
|
-
)
|
|
48517
|
-
] }),
|
|
48518
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
48519
|
-
] }) }),
|
|
48520
|
-
/* @__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: [
|
|
48521
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm sm:text-base md:text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(LiveTimer, {}) }),
|
|
48522
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
|
|
48523
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
|
|
48524
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
|
|
48525
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
|
|
48526
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
|
|
48527
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: [
|
|
48528
|
-
shiftType,
|
|
48529
|
-
" Shift"
|
|
48530
|
-
] })
|
|
48531
|
-
] })
|
|
48532
|
-
] }) })
|
|
48533
|
-
] }),
|
|
48534
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
48535
|
-
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48536
|
-
motion.div,
|
|
48537
|
-
{
|
|
48538
|
-
initial: { opacity: 0, y: 20 },
|
|
48539
|
-
animate: { opacity: 1, y: 0 },
|
|
48540
|
-
transition: { duration: 0.3, delay: 0.1 },
|
|
48541
|
-
className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
|
|
48542
|
-
children: [
|
|
48543
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
|
|
48544
|
-
/* @__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" }) }),
|
|
48545
|
-
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
48546
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
48547
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
|
|
48548
|
-
summary.uptimePercentage.toFixed(1),
|
|
48549
|
-
"%"
|
|
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" })
|
|
48550
49667
|
] }),
|
|
48551
|
-
|
|
48552
|
-
] })
|
|
48553
|
-
|
|
48554
|
-
|
|
48555
|
-
|
|
48556
|
-
|
|
48557
|
-
|
|
48558
|
-
|
|
48559
|
-
|
|
48560
|
-
|
|
48561
|
-
|
|
48562
|
-
|
|
48563
|
-
|
|
48564
|
-
|
|
48565
|
-
|
|
48566
|
-
|
|
48567
|
-
|
|
48568
|
-
|
|
48569
|
-
|
|
48570
|
-
|
|
48571
|
-
|
|
48572
|
-
|
|
48573
|
-
|
|
48574
|
-
|
|
48575
|
-
|
|
48576
|
-
|
|
48577
|
-
|
|
48578
|
-
|
|
48579
|
-
|
|
48580
|
-
|
|
48581
|
-
|
|
48582
|
-
|
|
48583
|
-
/* @__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
|
+
] })
|
|
48584
49700
|
] })
|
|
48585
|
-
]
|
|
48586
|
-
|
|
48587
|
-
|
|
48588
|
-
|
|
48589
|
-
|
|
48590
|
-
|
|
48591
|
-
|
|
48592
|
-
|
|
48593
|
-
|
|
48594
|
-
|
|
48595
|
-
|
|
48596
|
-
|
|
48597
|
-
|
|
48598
|
-
|
|
48599
|
-
|
|
48600
|
-
|
|
48601
|
-
|
|
48602
|
-
|
|
48603
|
-
|
|
48604
|
-
|
|
48605
|
-
|
|
48606
|
-
|
|
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
|
+
)
|
|
48607
49732
|
] });
|
|
48608
49733
|
};
|
|
48609
49734
|
var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
|
|
@@ -52563,6 +53688,7 @@ exports.useWorkspaceHealthStatus = useWorkspaceHealthStatus;
|
|
|
52563
53688
|
exports.useWorkspaceMetrics = useWorkspaceMetrics;
|
|
52564
53689
|
exports.useWorkspaceNavigation = useWorkspaceNavigation;
|
|
52565
53690
|
exports.useWorkspaceOperators = useWorkspaceOperators;
|
|
53691
|
+
exports.useWorkspaceUptimeTimeline = useWorkspaceUptimeTimeline;
|
|
52566
53692
|
exports.userService = userService;
|
|
52567
53693
|
exports.videoPrefetchManager = videoPrefetchManager;
|
|
52568
53694
|
exports.videoPreloader = videoPreloader;
|