@optifye/dashboard-core 6.9.12 → 6.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +61 -12
- package/dist/index.d.mts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +1227 -545
- package/dist/index.mjs +1229 -548
- 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
|
});
|
|
@@ -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(
|
|
@@ -31493,7 +31684,7 @@ function DiagnosisVideoModal({
|
|
|
31493
31684
|
}
|
|
31494
31685
|
loadClip();
|
|
31495
31686
|
}, [clipId, supabase, transformPlaylistUrls]);
|
|
31496
|
-
const
|
|
31687
|
+
const formatTime5 = (seconds) => {
|
|
31497
31688
|
const mins = Math.floor(seconds / 60);
|
|
31498
31689
|
const secs = Math.floor(seconds % 60);
|
|
31499
31690
|
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
@@ -31685,9 +31876,9 @@ function DiagnosisVideoModal({
|
|
|
31685
31876
|
}
|
|
31686
31877
|
),
|
|
31687
31878
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium", children: [
|
|
31688
|
-
|
|
31879
|
+
formatTime5(currentTime),
|
|
31689
31880
|
" / ",
|
|
31690
|
-
|
|
31881
|
+
formatTime5(duration)
|
|
31691
31882
|
] }),
|
|
31692
31883
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
31693
31884
|
"input",
|
|
@@ -33507,45 +33698,35 @@ var LinePdfGenerator = ({
|
|
|
33507
33698
|
const doc = new jsPDF.jsPDF();
|
|
33508
33699
|
const pageHeight = doc.internal.pageSize.height;
|
|
33509
33700
|
const addHeaderPage1 = () => {
|
|
33510
|
-
doc.setFontSize(
|
|
33701
|
+
doc.setFontSize(14);
|
|
33511
33702
|
doc.setFont("helvetica", "bold");
|
|
33512
|
-
doc.setTextColor(
|
|
33703
|
+
doc.setTextColor(50, 50, 50);
|
|
33513
33704
|
doc.text("OPTIFYE.AI", 20, 15);
|
|
33514
|
-
doc.setFontSize(
|
|
33705
|
+
doc.setFontSize(9);
|
|
33515
33706
|
doc.setFont("helvetica", "normal");
|
|
33516
33707
|
doc.setTextColor(100, 100, 100);
|
|
33517
|
-
const
|
|
33518
|
-
const
|
|
33519
|
-
doc.text(
|
|
33520
|
-
doc.setDrawColor(
|
|
33521
|
-
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);
|
|
33522
33713
|
doc.line(20, 20, 190, 20);
|
|
33523
33714
|
};
|
|
33524
33715
|
const addHeaderPage2 = () => {
|
|
33525
|
-
doc.setFontSize(
|
|
33716
|
+
doc.setFontSize(14);
|
|
33526
33717
|
doc.setFont("helvetica", "bold");
|
|
33527
|
-
doc.setTextColor(
|
|
33718
|
+
doc.setTextColor(50, 50, 50);
|
|
33528
33719
|
doc.text("OPTIFYE.AI", 20, 15);
|
|
33529
|
-
doc.setFontSize(
|
|
33720
|
+
doc.setFontSize(9);
|
|
33530
33721
|
doc.setFont("helvetica", "normal");
|
|
33531
33722
|
doc.setTextColor(100, 100, 100);
|
|
33532
|
-
const reportText = "REAL-TIME PERFORMANCE REPORT";
|
|
33533
|
-
const reportTextWidth = doc.getStringUnitWidth(reportText) * 10 / doc.internal.scaleFactor;
|
|
33534
|
-
doc.text(reportText, doc.internal.pageSize.width - 20 - reportTextWidth, 15);
|
|
33535
|
-
doc.text("Page 2", doc.internal.pageSize.width - 30, pageHeight - 15);
|
|
33536
|
-
doc.setDrawColor(220, 220, 220);
|
|
33537
|
-
doc.setLineWidth(0.3);
|
|
33538
|
-
doc.line(20, 20, 190, 20);
|
|
33539
|
-
return 35;
|
|
33540
|
-
};
|
|
33541
|
-
const addFooter = (pageNum) => {
|
|
33542
|
-
doc.setFontSize(9);
|
|
33543
|
-
doc.setTextColor(130, 130, 130);
|
|
33544
33723
|
const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
|
|
33545
|
-
doc.
|
|
33546
|
-
|
|
33547
|
-
|
|
33548
|
-
|
|
33724
|
+
const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
|
|
33725
|
+
doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
|
|
33726
|
+
doc.setDrawColor(200, 200, 200);
|
|
33727
|
+
doc.setLineWidth(0.5);
|
|
33728
|
+
doc.line(20, 20, 190, 20);
|
|
33729
|
+
return 30;
|
|
33549
33730
|
};
|
|
33550
33731
|
addHeaderPage1();
|
|
33551
33732
|
doc.setFontSize(26);
|
|
@@ -33591,37 +33772,47 @@ var LinePdfGenerator = ({
|
|
|
33591
33772
|
doc.setDrawColor(200, 200, 200);
|
|
33592
33773
|
doc.setLineWidth(0.5);
|
|
33593
33774
|
doc.line(20, 53, 190, 53);
|
|
33594
|
-
|
|
33775
|
+
const perfOverviewStartY = 58;
|
|
33776
|
+
doc.setFillColor(245, 245, 245);
|
|
33777
|
+
doc.roundedRect(15, perfOverviewStartY, 180, 50, 3, 3, "F");
|
|
33778
|
+
doc.setFontSize(18);
|
|
33595
33779
|
doc.setFont("helvetica", "bold");
|
|
33596
|
-
doc.setTextColor(
|
|
33597
|
-
doc.text("Line Performance Overview", 20,
|
|
33780
|
+
doc.setTextColor(40, 40, 40);
|
|
33781
|
+
doc.text("Line Performance Overview", 20, 68);
|
|
33598
33782
|
doc.setTextColor(0, 0, 0);
|
|
33599
33783
|
const createKPIBox = (y) => {
|
|
33600
|
-
doc.setFillColor(
|
|
33601
|
-
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");
|
|
33602
33789
|
};
|
|
33603
|
-
|
|
33604
|
-
|
|
33790
|
+
const kpiStartY = 80;
|
|
33791
|
+
const kpiSpacing = 10;
|
|
33792
|
+
createKPIBox(kpiStartY);
|
|
33793
|
+
doc.setFontSize(11);
|
|
33605
33794
|
doc.setFont("helvetica", "normal");
|
|
33606
|
-
doc.text("Output:", 25,
|
|
33795
|
+
doc.text("Output:", 25, kpiStartY);
|
|
33607
33796
|
doc.setFont("helvetica", "bold");
|
|
33608
|
-
doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120,
|
|
33609
|
-
createKPIBox(
|
|
33797
|
+
doc.text(`${lineInfo.metrics.current_output} / ${lineInfo.metrics.line_threshold}`, 120, kpiStartY);
|
|
33798
|
+
createKPIBox(kpiStartY + kpiSpacing);
|
|
33610
33799
|
doc.setFont("helvetica", "normal");
|
|
33611
|
-
doc.text("Underperforming Workspaces:", 25,
|
|
33800
|
+
doc.text("Underperforming Workspaces:", 25, kpiStartY + kpiSpacing);
|
|
33612
33801
|
doc.setFont("helvetica", "bold");
|
|
33613
|
-
doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120,
|
|
33614
|
-
createKPIBox(
|
|
33802
|
+
doc.text(`${lineInfo.metrics.underperforming_workspaces} / ${lineInfo.metrics.total_workspaces}`, 120, kpiStartY + kpiSpacing);
|
|
33803
|
+
createKPIBox(kpiStartY + kpiSpacing * 2);
|
|
33615
33804
|
doc.setFont("helvetica", "normal");
|
|
33616
|
-
doc.text("Average Efficiency:", 25,
|
|
33805
|
+
doc.text("Average Efficiency:", 25, kpiStartY + kpiSpacing * 2);
|
|
33617
33806
|
doc.setFont("helvetica", "bold");
|
|
33618
|
-
doc.text(`${lineInfo.metrics.avg_efficiency.toFixed(1)}%`, 120,
|
|
33619
|
-
doc.setDrawColor(
|
|
33620
|
-
doc.
|
|
33621
|
-
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);
|
|
33622
33813
|
doc.setFont("helvetica", "bold");
|
|
33623
|
-
doc.setTextColor(
|
|
33624
|
-
doc.text("Hourly Output Overview", 20,
|
|
33814
|
+
doc.setTextColor(40, 40, 40);
|
|
33815
|
+
doc.text("Hourly Output Overview", 20, 133);
|
|
33625
33816
|
doc.setTextColor(0, 0, 0);
|
|
33626
33817
|
const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
|
|
33627
33818
|
const [hours, minutes] = startTimeStr.split(":");
|
|
@@ -33658,7 +33849,7 @@ var LinePdfGenerator = ({
|
|
|
33658
33849
|
}
|
|
33659
33850
|
hourEndTime.setSeconds(0);
|
|
33660
33851
|
hourEndTime.setMilliseconds(0);
|
|
33661
|
-
const
|
|
33852
|
+
const formatTime5 = (date2) => {
|
|
33662
33853
|
return date2.toLocaleTimeString("en-IN", {
|
|
33663
33854
|
hour: "2-digit",
|
|
33664
33855
|
minute: "2-digit",
|
|
@@ -33666,7 +33857,7 @@ var LinePdfGenerator = ({
|
|
|
33666
33857
|
timeZone: "Asia/Kolkata"
|
|
33667
33858
|
});
|
|
33668
33859
|
};
|
|
33669
|
-
return `${
|
|
33860
|
+
return `${formatTime5(hourStartTime)} - ${formatTime5(hourEndTime)}`;
|
|
33670
33861
|
});
|
|
33671
33862
|
};
|
|
33672
33863
|
const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
|
|
@@ -33813,86 +34004,94 @@ var LinePdfGenerator = ({
|
|
|
33813
34004
|
return Math.round(lineInfo.metrics.current_output / shiftDuration);
|
|
33814
34005
|
});
|
|
33815
34006
|
}
|
|
34007
|
+
const tableHeaderY = 143;
|
|
34008
|
+
const tableStartY = 151;
|
|
34009
|
+
const rowSpacing = 8;
|
|
34010
|
+
const bottomPadding = 8;
|
|
34011
|
+
const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
|
|
34012
|
+
const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
|
|
34013
|
+
doc.setFillColor(245, 245, 245);
|
|
34014
|
+
doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
|
|
33816
34015
|
doc.setFontSize(11);
|
|
33817
34016
|
doc.setFont("helvetica", "bold");
|
|
33818
|
-
doc.
|
|
33819
|
-
doc.
|
|
33820
|
-
doc.text("
|
|
33821
|
-
doc.text("
|
|
33822
|
-
doc.
|
|
33823
|
-
doc.
|
|
33824
|
-
doc.
|
|
33825
|
-
doc.setDrawColor(220, 220, 220);
|
|
33826
|
-
doc.line(20, 148, 190, 148);
|
|
34017
|
+
doc.text("Time Range", 25, tableHeaderY);
|
|
34018
|
+
doc.text("Output", 80, tableHeaderY);
|
|
34019
|
+
doc.text("Target", 125, tableHeaderY);
|
|
34020
|
+
doc.text("Status", 170, tableHeaderY);
|
|
34021
|
+
doc.setLineWidth(0.3);
|
|
34022
|
+
doc.setDrawColor(200, 200, 200);
|
|
34023
|
+
doc.line(20, 146, 190, 146);
|
|
33827
34024
|
doc.setFont("helvetica", "normal");
|
|
33828
|
-
let yPos =
|
|
33829
|
-
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
|
+
}
|
|
33830
34037
|
hourlyTimeRanges.forEach((timeRange, index) => {
|
|
33831
|
-
|
|
33832
|
-
|
|
33833
|
-
|
|
33834
|
-
|
|
33835
|
-
const
|
|
33836
|
-
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();
|
|
33837
34045
|
doc.text(timeRange, 25, yPos);
|
|
33838
|
-
doc.text(
|
|
33839
|
-
doc.text(
|
|
33840
|
-
if (
|
|
33841
|
-
doc.
|
|
33842
|
-
doc.
|
|
33843
|
-
|
|
33844
|
-
|
|
33845
|
-
doc.
|
|
33846
|
-
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");
|
|
33847
34056
|
} else {
|
|
33848
|
-
doc.
|
|
33849
|
-
doc.
|
|
33850
|
-
const crossX = 170;
|
|
33851
|
-
const crossY = yPos - 1;
|
|
33852
|
-
doc.line(crossX - 2, crossY - 2, crossX + 2, crossY + 2);
|
|
33853
|
-
doc.line(crossX - 2, crossY + 2, crossX + 2, crossY - 2);
|
|
34057
|
+
doc.setTextColor(227, 67, 41);
|
|
34058
|
+
doc.text("\xD7", 170, yPos);
|
|
33854
34059
|
}
|
|
33855
|
-
doc.
|
|
33856
|
-
doc.setLineWidth(0.2);
|
|
34060
|
+
doc.setTextColor(0, 0, 0);
|
|
33857
34061
|
yPos += rowSpacing;
|
|
33858
34062
|
});
|
|
33859
|
-
doc.roundedRect(20, 140, 170, yPos - 140 - 3, 1, 1, "S");
|
|
33860
|
-
addFooter(1);
|
|
33861
34063
|
doc.addPage();
|
|
33862
34064
|
yPos = addHeaderPage2();
|
|
33863
|
-
|
|
34065
|
+
const workspaceSectionStartY = yPos;
|
|
34066
|
+
const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
|
|
34067
|
+
const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
|
|
34068
|
+
const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
|
|
34069
|
+
doc.setFillColor(245, 245, 245);
|
|
34070
|
+
doc.roundedRect(15, workspaceSectionStartY, 180, wsTableHeight, 3, 3, "F");
|
|
34071
|
+
doc.setFontSize(18);
|
|
33864
34072
|
doc.setFont("helvetica", "bold");
|
|
33865
|
-
doc.setTextColor(
|
|
34073
|
+
doc.setTextColor(40, 40, 40);
|
|
33866
34074
|
doc.text("Poorest Performing Workspaces", 20, yPos);
|
|
33867
34075
|
doc.setTextColor(0, 0, 0);
|
|
33868
34076
|
yPos += 10;
|
|
33869
34077
|
doc.setFontSize(11);
|
|
33870
34078
|
doc.setFont("helvetica", "bold");
|
|
33871
|
-
doc.setFillColor(245, 245, 245);
|
|
33872
|
-
doc.roundedRect(20, yPos, 170, 8, 1, 1, "F");
|
|
33873
34079
|
yPos += 5;
|
|
33874
34080
|
doc.text("Workspace", 25, yPos);
|
|
33875
|
-
doc.text("Current/
|
|
34081
|
+
doc.text("Current/Target", 85, yPos);
|
|
33876
34082
|
doc.text("Efficiency", 145, yPos);
|
|
33877
34083
|
yPos += 3;
|
|
33878
|
-
doc.setLineWidth(0.
|
|
33879
|
-
doc.setDrawColor(
|
|
34084
|
+
doc.setLineWidth(0.3);
|
|
34085
|
+
doc.setDrawColor(200, 200, 200);
|
|
33880
34086
|
doc.line(20, yPos, 190, yPos);
|
|
33881
34087
|
doc.setFont("helvetica", "normal");
|
|
33882
|
-
const tableStartY = yPos;
|
|
33883
34088
|
yPos += 7;
|
|
33884
|
-
const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 10);
|
|
33885
34089
|
if (sortedWorkspaces.length === 0) {
|
|
33886
34090
|
doc.text("No workspace data available", 25, yPos);
|
|
33887
34091
|
yPos += 10;
|
|
33888
34092
|
} else {
|
|
33889
34093
|
sortedWorkspaces.forEach((ws, index) => {
|
|
33890
|
-
if (index % 2 === 0) {
|
|
33891
|
-
doc.setFillColor(252, 252, 252);
|
|
33892
|
-
doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
|
|
33893
|
-
}
|
|
33894
34094
|
const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
|
|
33895
|
-
const maxWidth = 55;
|
|
33896
34095
|
const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
|
|
33897
34096
|
doc.text(truncatedName, 25, yPos);
|
|
33898
34097
|
doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
|
|
@@ -33900,9 +34099,6 @@ var LinePdfGenerator = ({
|
|
|
33900
34099
|
yPos += 8;
|
|
33901
34100
|
});
|
|
33902
34101
|
}
|
|
33903
|
-
const wsTableHeight = yPos - tableStartY - 3;
|
|
33904
|
-
doc.roundedRect(20, tableStartY, 170, wsTableHeight, 1, 1, "S");
|
|
33905
|
-
addFooter(2);
|
|
33906
34102
|
doc.save(`${lineInfo.line_name}_${date.split(",")[0]}.pdf`);
|
|
33907
34103
|
} catch (error) {
|
|
33908
34104
|
console.error("PDF generation failed:", error);
|
|
@@ -35076,25 +35272,25 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
35076
35272
|
doc.setFont("helvetica", "bold");
|
|
35077
35273
|
doc.setTextColor(50, 50, 50);
|
|
35078
35274
|
doc.text("OPTIFYE.AI", 20, 15);
|
|
35079
|
-
doc.setFontSize(
|
|
35275
|
+
doc.setFontSize(9);
|
|
35080
35276
|
doc.setFont("helvetica", "normal");
|
|
35081
|
-
doc.setTextColor(
|
|
35082
|
-
const
|
|
35083
|
-
const
|
|
35084
|
-
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);
|
|
35085
35281
|
doc.setDrawColor(200, 200, 200);
|
|
35086
35282
|
doc.setLineWidth(0.5);
|
|
35087
35283
|
doc.line(20, 20, 190, 20);
|
|
35088
35284
|
doc.setFillColor(250, 250, 250);
|
|
35089
|
-
doc.roundedRect(15, 25, 180,
|
|
35285
|
+
doc.roundedRect(15, 25, 180, 53, 3, 3, "F");
|
|
35090
35286
|
doc.setFontSize(32);
|
|
35091
35287
|
doc.setFont("helvetica", "bold");
|
|
35092
35288
|
doc.setTextColor(0, 0, 0);
|
|
35093
|
-
doc.text(lineName, 20,
|
|
35289
|
+
doc.text(lineName, 20, 39);
|
|
35094
35290
|
doc.setFontSize(22);
|
|
35095
35291
|
doc.setFont("helvetica", "normal");
|
|
35096
35292
|
doc.setTextColor(40, 40, 40);
|
|
35097
|
-
doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20,
|
|
35293
|
+
doc.text(getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id), 20, 51);
|
|
35098
35294
|
doc.setFontSize(13);
|
|
35099
35295
|
doc.setFont("helvetica", "normal");
|
|
35100
35296
|
doc.setTextColor(60, 60, 60);
|
|
@@ -35105,8 +35301,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
35105
35301
|
timeZone: "Asia/Kolkata"
|
|
35106
35302
|
});
|
|
35107
35303
|
const shiftType = "Day Shift";
|
|
35108
|
-
doc.text(`${date}`, 20,
|
|
35109
|
-
doc.text(`${shiftType}`, 20,
|
|
35304
|
+
doc.text(`${date}`, 20, 63);
|
|
35305
|
+
doc.text(`${shiftType}`, 20, 71);
|
|
35110
35306
|
const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
|
|
35111
35307
|
hour: "2-digit",
|
|
35112
35308
|
minute: "2-digit",
|
|
@@ -35119,11 +35315,11 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
35119
35315
|
});
|
|
35120
35316
|
doc.setFontSize(12);
|
|
35121
35317
|
doc.setTextColor(80, 80, 80);
|
|
35122
|
-
doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20,
|
|
35318
|
+
doc.text(`Report Period: ${shiftStartTime} - ${currentTime}`, 20, 79);
|
|
35123
35319
|
doc.setTextColor(0, 0, 0);
|
|
35124
35320
|
doc.setDrawColor(180, 180, 180);
|
|
35125
35321
|
doc.setLineWidth(0.8);
|
|
35126
|
-
doc.line(20,
|
|
35322
|
+
doc.line(20, 88, 190, 88);
|
|
35127
35323
|
const createKPIBox = (y) => {
|
|
35128
35324
|
doc.setFillColor(255, 255, 255);
|
|
35129
35325
|
doc.roundedRect(22, y - 7, 165, 12, 2, 2, "F");
|
|
@@ -35131,66 +35327,72 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
35131
35327
|
doc.setLineWidth(0.2);
|
|
35132
35328
|
doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
|
|
35133
35329
|
};
|
|
35330
|
+
const perfOverviewStartY = 93;
|
|
35134
35331
|
doc.setFillColor(245, 245, 245);
|
|
35135
|
-
doc.roundedRect(15,
|
|
35332
|
+
doc.roundedRect(15, perfOverviewStartY, 180, 60, 3, 3, "F");
|
|
35136
35333
|
doc.setFontSize(18);
|
|
35137
35334
|
doc.setFont("helvetica", "bold");
|
|
35138
35335
|
doc.setTextColor(40, 40, 40);
|
|
35139
|
-
doc.text("Performance Overview", 20,
|
|
35336
|
+
doc.text("Performance Overview", 20, 103);
|
|
35140
35337
|
doc.setTextColor(0, 0, 0);
|
|
35141
|
-
const kpiStartY =
|
|
35338
|
+
const kpiStartY = 115;
|
|
35142
35339
|
const kpiSpacing = 10;
|
|
35143
35340
|
createKPIBox(kpiStartY);
|
|
35144
35341
|
doc.setFontSize(11);
|
|
35145
35342
|
doc.setFont("helvetica", "normal");
|
|
35146
|
-
doc.text("
|
|
35343
|
+
doc.text("Current Output/Target Output:", 25, kpiStartY);
|
|
35147
35344
|
doc.setFont("helvetica", "bold");
|
|
35148
|
-
doc.text(`${
|
|
35345
|
+
doc.text(`${workspace.total_actions} / ${workspace.target_output}`, 120, kpiStartY);
|
|
35149
35346
|
createKPIBox(kpiStartY + kpiSpacing);
|
|
35150
35347
|
doc.setFont("helvetica", "normal");
|
|
35151
|
-
doc.text("
|
|
35348
|
+
doc.text("Efficiency:", 25, kpiStartY + kpiSpacing);
|
|
35152
35349
|
doc.setFont("helvetica", "bold");
|
|
35153
|
-
doc.text(`${workspace.
|
|
35350
|
+
doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing);
|
|
35154
35351
|
createKPIBox(kpiStartY + kpiSpacing * 2);
|
|
35155
35352
|
doc.setFont("helvetica", "normal");
|
|
35156
|
-
doc.text("
|
|
35157
|
-
doc.setFont("helvetica", "bold");
|
|
35158
|
-
doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}% (Target: 80%)`, 120, kpiStartY + kpiSpacing * 2);
|
|
35159
|
-
createKPIBox(kpiStartY + kpiSpacing * 3);
|
|
35160
|
-
doc.setFont("helvetica", "normal");
|
|
35161
|
-
doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 3);
|
|
35353
|
+
doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
|
|
35162
35354
|
doc.setFont("helvetica", "bold");
|
|
35163
|
-
doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing *
|
|
35355
|
+
doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 2);
|
|
35164
35356
|
doc.setDrawColor(180, 180, 180);
|
|
35165
35357
|
doc.setLineWidth(0.8);
|
|
35166
|
-
doc.line(20,
|
|
35358
|
+
doc.line(20, 163, 190, 163);
|
|
35359
|
+
const hourlyPerfStartY = 168;
|
|
35360
|
+
const hourlyData = workspace.hourly_action_counts || [];
|
|
35361
|
+
const hourlyTarget = workspace.pph_threshold;
|
|
35362
|
+
const tableStartY = 199;
|
|
35363
|
+
const rowHeight = 8;
|
|
35364
|
+
const bottomPadding = 8;
|
|
35365
|
+
const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
|
|
35167
35366
|
doc.setFillColor(245, 245, 245);
|
|
35168
|
-
doc.roundedRect(15,
|
|
35367
|
+
doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
|
|
35169
35368
|
doc.setFontSize(18);
|
|
35170
35369
|
doc.setFont("helvetica", "bold");
|
|
35171
35370
|
doc.setTextColor(40, 40, 40);
|
|
35172
|
-
doc.text("Hourly Performance", 20,
|
|
35371
|
+
doc.text("Hourly Performance", 20, 178);
|
|
35173
35372
|
doc.setTextColor(0, 0, 0);
|
|
35174
35373
|
doc.setFontSize(11);
|
|
35175
35374
|
doc.setFont("helvetica", "bold");
|
|
35176
|
-
doc.
|
|
35177
|
-
doc.
|
|
35178
|
-
doc.text("
|
|
35179
|
-
doc.text("
|
|
35180
|
-
doc.
|
|
35181
|
-
doc.
|
|
35182
|
-
doc.
|
|
35183
|
-
doc.setDrawColor(220, 220, 220);
|
|
35184
|
-
doc.line(20, 185, 190, 185);
|
|
35375
|
+
doc.text("Time Range", 25, 188);
|
|
35376
|
+
doc.text("Output", 95, 188);
|
|
35377
|
+
doc.text("Target", 135, 188);
|
|
35378
|
+
doc.text("Status", 170, 188);
|
|
35379
|
+
doc.setLineWidth(0.3);
|
|
35380
|
+
doc.setDrawColor(200, 200, 200);
|
|
35381
|
+
doc.line(20, 191, 190, 191);
|
|
35185
35382
|
doc.setFont("helvetica", "normal");
|
|
35186
|
-
let yPos =
|
|
35187
|
-
const
|
|
35188
|
-
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
|
+
}
|
|
35189
35395
|
hourlyData.forEach((output, index) => {
|
|
35190
|
-
if (index % 2 === 0) {
|
|
35191
|
-
doc.setFillColor(252, 252, 252);
|
|
35192
|
-
doc.roundedRect(20, yPos - 5, 170, 8, 1, 1, "F");
|
|
35193
|
-
}
|
|
35194
35396
|
const startTime = /* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`);
|
|
35195
35397
|
startTime.setHours(startTime.getHours() + index);
|
|
35196
35398
|
const endTime = new Date(startTime);
|
|
@@ -35202,14 +35404,17 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
35202
35404
|
hour: "numeric",
|
|
35203
35405
|
hour12: true
|
|
35204
35406
|
})}`;
|
|
35205
|
-
const
|
|
35407
|
+
const hourNumber = startTime.getHours();
|
|
35408
|
+
const dataCollected = !isToday || hourNumber < currentHour;
|
|
35409
|
+
const outputStr = dataCollected ? output.toString() : "TBD";
|
|
35206
35410
|
const targetStr = hourlyTarget.toString();
|
|
35207
|
-
const outputX = 95 + doc.getStringUnitWidth(outputStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
|
|
35208
|
-
const targetX = 135 + doc.getStringUnitWidth(targetStr) * doc.getFontSize() / (2 * doc.internal.scaleFactor);
|
|
35209
35411
|
doc.text(timeRange, 25, yPos);
|
|
35210
|
-
doc.text(outputStr,
|
|
35211
|
-
doc.text(targetStr,
|
|
35212
|
-
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) {
|
|
35213
35418
|
doc.setTextColor(0, 171, 69);
|
|
35214
35419
|
doc.setFont("ZapfDingbats", "normal");
|
|
35215
35420
|
doc.text("4", 170, yPos);
|
|
@@ -35219,15 +35424,8 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
35219
35424
|
doc.text("\xD7", 170, yPos);
|
|
35220
35425
|
}
|
|
35221
35426
|
doc.setTextColor(0, 0, 0);
|
|
35222
|
-
yPos +=
|
|
35427
|
+
yPos += rowHeight;
|
|
35223
35428
|
});
|
|
35224
|
-
doc.setLineWidth(0.2);
|
|
35225
|
-
doc.setDrawColor(220, 220, 220);
|
|
35226
|
-
doc.roundedRect(20, 187, 170, yPos - 187 - 3, 1, 1, "S");
|
|
35227
|
-
doc.setFontSize(9);
|
|
35228
|
-
doc.setTextColor(130, 130, 130);
|
|
35229
|
-
const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
|
|
35230
|
-
doc.text(generatedText, 20, 280);
|
|
35231
35429
|
doc.save(`${workspace.workspace_name}_${date.split(",")[0]}.pdf`);
|
|
35232
35430
|
} catch (error) {
|
|
35233
35431
|
console.error("PDF generation failed:", error);
|
|
@@ -36307,7 +36505,8 @@ var WorkspaceHealthCard = ({
|
|
|
36307
36505
|
workspace,
|
|
36308
36506
|
onClick,
|
|
36309
36507
|
showDetails = true,
|
|
36310
|
-
className = ""
|
|
36508
|
+
className = "",
|
|
36509
|
+
onViewDetails
|
|
36311
36510
|
}) => {
|
|
36312
36511
|
const getStatusConfig = () => {
|
|
36313
36512
|
switch (workspace.status) {
|
|
@@ -36360,38 +36559,101 @@ var WorkspaceHealthCard = ({
|
|
|
36360
36559
|
onClick(workspace);
|
|
36361
36560
|
}
|
|
36362
36561
|
};
|
|
36562
|
+
const handleViewDetails = (event) => {
|
|
36563
|
+
event.stopPropagation();
|
|
36564
|
+
event.preventDefault();
|
|
36565
|
+
if (onViewDetails) {
|
|
36566
|
+
onViewDetails(workspace);
|
|
36567
|
+
}
|
|
36568
|
+
};
|
|
36363
36569
|
const handleKeyDown = (event) => {
|
|
36364
36570
|
if (onClick && (event.key === "Enter" || event.key === " ")) {
|
|
36365
36571
|
event.preventDefault();
|
|
36366
36572
|
onClick(workspace);
|
|
36367
36573
|
}
|
|
36368
36574
|
};
|
|
36575
|
+
const formatDuration3 = (minutes) => {
|
|
36576
|
+
if (!minutes || minutes <= 0) return "0 min";
|
|
36577
|
+
const rounded = Math.max(Math.round(minutes), 0);
|
|
36578
|
+
const days = Math.floor(rounded / 1440);
|
|
36579
|
+
const hours = Math.floor(rounded % 1440 / 60);
|
|
36580
|
+
const mins = rounded % 60;
|
|
36581
|
+
const parts = [];
|
|
36582
|
+
if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
|
|
36583
|
+
if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
|
|
36584
|
+
if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
|
|
36585
|
+
if (!parts.length) {
|
|
36586
|
+
parts.push("1 min");
|
|
36587
|
+
}
|
|
36588
|
+
return parts.join(" ");
|
|
36589
|
+
};
|
|
36369
36590
|
const formatTimeAgo = (timeString) => {
|
|
36370
|
-
|
|
36591
|
+
if (!timeString) return "Unknown";
|
|
36592
|
+
const cleaned = timeString.replace("about ", "").trim();
|
|
36593
|
+
if (cleaned.toLowerCase() === "never") return "Never";
|
|
36594
|
+
const minuteMatch = cleaned.match(/(\d+)\s*m/);
|
|
36595
|
+
if (!minuteMatch) {
|
|
36596
|
+
return cleaned;
|
|
36597
|
+
}
|
|
36598
|
+
const minutes = parseInt(minuteMatch[1], 10);
|
|
36599
|
+
if (!Number.isFinite(minutes)) return cleaned;
|
|
36600
|
+
if (minutes < 1) return "Just now";
|
|
36601
|
+
if (minutes < 60) {
|
|
36602
|
+
return `${minutes} min ago`;
|
|
36603
|
+
}
|
|
36604
|
+
const hours = Math.floor(minutes / 60);
|
|
36605
|
+
const remainingMinutes = minutes % 60;
|
|
36606
|
+
if (hours < 24) {
|
|
36607
|
+
const parts2 = [`${hours} hr${hours === 1 ? "" : "s"}`];
|
|
36608
|
+
if (remainingMinutes) {
|
|
36609
|
+
parts2.push(`${remainingMinutes} min`);
|
|
36610
|
+
}
|
|
36611
|
+
return `${parts2.join(" ")} ago`;
|
|
36612
|
+
}
|
|
36613
|
+
const days = Math.floor(hours / 24);
|
|
36614
|
+
const remainingHours = hours % 24;
|
|
36615
|
+
const parts = [`${days} day${days === 1 ? "" : "s"}`];
|
|
36616
|
+
if (remainingHours) {
|
|
36617
|
+
parts.push(`${remainingHours} hr${remainingHours === 1 ? "" : "s"}`);
|
|
36618
|
+
}
|
|
36619
|
+
return `${parts.join(" ")} ago`;
|
|
36371
36620
|
};
|
|
36372
|
-
const
|
|
36373
|
-
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
|
+
}
|
|
36374
36632
|
const downtimeMinutes = Math.max(0, uptimeDetails.expectedMinutes - uptimeDetails.actualMinutes);
|
|
36375
|
-
if (downtimeMinutes === 0)
|
|
36376
|
-
|
|
36377
|
-
|
|
36378
|
-
|
|
36379
|
-
|
|
36380
|
-
|
|
36381
|
-
return `${hours} hr downtime`;
|
|
36633
|
+
if (downtimeMinutes === 0) {
|
|
36634
|
+
return {
|
|
36635
|
+
text: "0m",
|
|
36636
|
+
className: "text-emerald-600 dark:text-emerald-400",
|
|
36637
|
+
label: "Total Downtime"
|
|
36638
|
+
};
|
|
36382
36639
|
}
|
|
36383
|
-
return
|
|
36640
|
+
return {
|
|
36641
|
+
text: `${formatDuration3(downtimeMinutes)}`,
|
|
36642
|
+
className: downtimeMinutes > 60 ? "text-rose-600 dark:text-rose-400" : "text-amber-600 dark:text-amber-400",
|
|
36643
|
+
label: "Total Downtime"
|
|
36644
|
+
};
|
|
36384
36645
|
};
|
|
36646
|
+
const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
|
|
36385
36647
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
36386
36648
|
Card2,
|
|
36387
36649
|
{
|
|
36388
36650
|
className: clsx(
|
|
36389
|
-
"relative overflow-hidden transition-all duration-300",
|
|
36651
|
+
"relative overflow-hidden transition-all duration-300 h-full flex flex-col",
|
|
36390
36652
|
"bg-gradient-to-br",
|
|
36391
36653
|
config.gradient,
|
|
36392
36654
|
"border",
|
|
36393
36655
|
config.border,
|
|
36394
|
-
"shadow-sm hover:shadow-
|
|
36656
|
+
"shadow-sm hover:shadow-lg hover:border-blue-300 dark:hover:border-blue-700",
|
|
36395
36657
|
onClick && "cursor-pointer hover:scale-[1.01]",
|
|
36396
36658
|
workspace.isStale && "opacity-90",
|
|
36397
36659
|
className
|
|
@@ -36401,7 +36663,7 @@ var WorkspaceHealthCard = ({
|
|
|
36401
36663
|
tabIndex: onClick ? 0 : void 0,
|
|
36402
36664
|
role: onClick ? "button" : void 0,
|
|
36403
36665
|
"aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
|
|
36404
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
|
|
36666
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 flex-grow flex flex-col", children: [
|
|
36405
36667
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-start gap-2 sm:gap-3", children: [
|
|
36406
36668
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
36407
36669
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1 break-words", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 8)}` }),
|
|
@@ -36423,21 +36685,20 @@ var WorkspaceHealthCard = ({
|
|
|
36423
36685
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
|
|
36424
36686
|
] })
|
|
36425
36687
|
] }),
|
|
36426
|
-
|
|
36427
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
36428
|
-
/* @__PURE__ */ jsxRuntime.
|
|
36429
|
-
|
|
36430
|
-
|
|
36431
|
-
|
|
36432
|
-
|
|
36433
|
-
|
|
36434
|
-
|
|
36435
|
-
|
|
36436
|
-
|
|
36437
|
-
"
|
|
36438
|
-
|
|
36439
|
-
|
|
36440
|
-
] })
|
|
36688
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 pt-3 border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
36689
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: "Total Downtime" }),
|
|
36690
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
|
|
36691
|
+
] }) })
|
|
36692
|
+
] }),
|
|
36693
|
+
onViewDetails && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-4 mt-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
36694
|
+
"button",
|
|
36695
|
+
{
|
|
36696
|
+
onClick: handleViewDetails,
|
|
36697
|
+
className: "w-full inline-flex items-center justify-center rounded-lg border border-slate-200/60 bg-white/60 px-3 py-2 text-sm font-medium text-slate-700 shadow-sm backdrop-blur-sm transition-all hover:bg-white/90 hover:text-slate-900 dark:bg-white/10 dark:text-slate-200 dark:border-white/10 dark:hover:bg-white/20",
|
|
36698
|
+
type: "button",
|
|
36699
|
+
children: "View shift timeline"
|
|
36700
|
+
}
|
|
36701
|
+
) })
|
|
36441
36702
|
] })
|
|
36442
36703
|
}
|
|
36443
36704
|
);
|
|
@@ -36445,8 +36706,11 @@ var WorkspaceHealthCard = ({
|
|
|
36445
36706
|
var CompactWorkspaceHealthCard = ({
|
|
36446
36707
|
workspace,
|
|
36447
36708
|
onClick,
|
|
36448
|
-
className = ""
|
|
36709
|
+
className = "",
|
|
36710
|
+
onViewDetails
|
|
36449
36711
|
}) => {
|
|
36712
|
+
const downtimeMinutes = workspace.uptimeDetails ? Math.max(0, workspace.uptimeDetails.expectedMinutes - workspace.uptimeDetails.actualMinutes) : null;
|
|
36713
|
+
const downtimeLabel = downtimeMinutes === null ? "No downtime data" : downtimeMinutes === 0 ? "No downtime" : `${downtimeMinutes} min down`;
|
|
36450
36714
|
const getStatusConfig = () => {
|
|
36451
36715
|
switch (workspace.status) {
|
|
36452
36716
|
case "healthy":
|
|
@@ -36486,6 +36750,13 @@ var CompactWorkspaceHealthCard = ({
|
|
|
36486
36750
|
onClick(workspace);
|
|
36487
36751
|
}
|
|
36488
36752
|
};
|
|
36753
|
+
const handleViewDetails = (event) => {
|
|
36754
|
+
event.stopPropagation();
|
|
36755
|
+
event.preventDefault();
|
|
36756
|
+
if (onViewDetails) {
|
|
36757
|
+
onViewDetails(workspace);
|
|
36758
|
+
}
|
|
36759
|
+
};
|
|
36489
36760
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
36490
36761
|
"div",
|
|
36491
36762
|
{
|
|
@@ -36513,14 +36784,20 @@ var CompactWorkspaceHealthCard = ({
|
|
|
36513
36784
|
workspace.uptimePercentage.toFixed(1),
|
|
36514
36785
|
"%"
|
|
36515
36786
|
] }),
|
|
36516
|
-
|
|
36517
|
-
|
|
36518
|
-
Math.max(0, workspace.uptimeDetails.expectedMinutes - workspace.uptimeDetails.actualMinutes),
|
|
36519
|
-
"m down"
|
|
36520
|
-
] })
|
|
36787
|
+
downtimeMinutes !== null && downtimeMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 dark:text-gray-400", children: "\u2022" }),
|
|
36788
|
+
downtimeLabel && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: downtimeLabel })
|
|
36521
36789
|
] }),
|
|
36522
36790
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
|
|
36523
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
|
|
36791
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) }),
|
|
36792
|
+
onViewDetails && /* @__PURE__ */ jsxRuntime.jsx(
|
|
36793
|
+
"button",
|
|
36794
|
+
{
|
|
36795
|
+
onClick: handleViewDetails,
|
|
36796
|
+
className: "rounded-full border border-gray-200 px-2 py-0.5 text-[11px] font-medium text-gray-600 hover:bg-gray-50",
|
|
36797
|
+
type: "button",
|
|
36798
|
+
children: "View"
|
|
36799
|
+
}
|
|
36800
|
+
)
|
|
36524
36801
|
] })
|
|
36525
36802
|
]
|
|
36526
36803
|
}
|
|
@@ -36529,6 +36806,7 @@ var CompactWorkspaceHealthCard = ({
|
|
|
36529
36806
|
var HealthStatusGrid = ({
|
|
36530
36807
|
workspaces,
|
|
36531
36808
|
onWorkspaceClick,
|
|
36809
|
+
onWorkspaceViewDetails,
|
|
36532
36810
|
showFilters = true,
|
|
36533
36811
|
groupBy: initialGroupBy = "none",
|
|
36534
36812
|
className = ""
|
|
@@ -36716,7 +36994,8 @@ var HealthStatusGrid = ({
|
|
|
36716
36994
|
{
|
|
36717
36995
|
workspace,
|
|
36718
36996
|
onClick: onWorkspaceClick,
|
|
36719
|
-
showDetails: true
|
|
36997
|
+
showDetails: true,
|
|
36998
|
+
onViewDetails: onWorkspaceViewDetails
|
|
36720
36999
|
},
|
|
36721
37000
|
workspace.workspace_id
|
|
36722
37001
|
)) })
|
|
@@ -40252,7 +40531,7 @@ var AIAgentView = () => {
|
|
|
40252
40531
|
}
|
|
40253
40532
|
return formattedLines.join("");
|
|
40254
40533
|
};
|
|
40255
|
-
const
|
|
40534
|
+
const formatTime5 = (timestamp) => {
|
|
40256
40535
|
const date = new Date(timestamp);
|
|
40257
40536
|
return date.toLocaleTimeString([], {
|
|
40258
40537
|
hour: "2-digit",
|
|
@@ -41516,7 +41795,7 @@ var AIAgentView = () => {
|
|
|
41516
41795
|
}
|
|
41517
41796
|
),
|
|
41518
41797
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `mt-1.5 sm:mt-2 flex items-center gap-2 text-xs text-gray-400 ${message.role === "user" ? "justify-end" : "justify-start"}`, children: [
|
|
41519
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
41798
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime5(message.created_at) }),
|
|
41520
41799
|
message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
41521
41800
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
|
|
41522
41801
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Axel" })
|
|
@@ -44347,8 +44626,7 @@ var KPIsOverviewView = ({
|
|
|
44347
44626
|
" Shift"
|
|
44348
44627
|
] })
|
|
44349
44628
|
] })
|
|
44350
|
-
] }) })
|
|
44351
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs sm:text-sm text-gray-600 text-center mt-2 sm:mt-3 px-2 sm:px-0", children: "Click on any line to view detailed performance metrics" })
|
|
44629
|
+
] }) })
|
|
44352
44630
|
] }) }),
|
|
44353
44631
|
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 p-3 sm:p-4 md:p-6 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 sm:gap-4 md:gap-6", children: lines.map((line) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
44354
44632
|
LineCard,
|
|
@@ -48665,18 +48943,41 @@ var useWorkspaceHealth = (options) => {
|
|
|
48665
48943
|
const isFetchingRef = React23.useRef(false);
|
|
48666
48944
|
const refreshIntervalRef = React23.useRef(null);
|
|
48667
48945
|
const healthTable = databaseConfig?.tables?.workspace_health || "workspace_health_status";
|
|
48668
|
-
const
|
|
48669
|
-
|
|
48670
|
-
const
|
|
48671
|
-
const
|
|
48672
|
-
const
|
|
48673
|
-
|
|
48674
|
-
|
|
48675
|
-
|
|
48676
|
-
|
|
48946
|
+
const computeSummary = React23.useCallback((data) => {
|
|
48947
|
+
const total = data.length;
|
|
48948
|
+
const healthy = data.filter((w) => w.status === "healthy").length;
|
|
48949
|
+
const unhealthy = data.filter((w) => w.status === "unhealthy").length;
|
|
48950
|
+
const warning6 = data.filter((w) => w.status === "warning").length;
|
|
48951
|
+
let uptimePercentage = total > 0 ? healthy / total * 100 : 100;
|
|
48952
|
+
let totalDowntimeMinutes;
|
|
48953
|
+
const withUptime = data.filter(
|
|
48954
|
+
(w) => w.uptimePercentage !== void 0 && w.uptimeDetails !== void 0
|
|
48955
|
+
);
|
|
48956
|
+
if (withUptime.length > 0) {
|
|
48957
|
+
const totalUptime = withUptime.reduce((sum, w) => sum + (w.uptimePercentage || 0), 0);
|
|
48958
|
+
uptimePercentage = totalUptime / withUptime.length;
|
|
48959
|
+
totalDowntimeMinutes = withUptime.reduce((sum, w) => {
|
|
48960
|
+
if (w.uptimeDetails) {
|
|
48961
|
+
return sum + Math.max(0, w.uptimeDetails.expectedMinutes - w.uptimeDetails.actualMinutes);
|
|
48962
|
+
}
|
|
48963
|
+
return sum;
|
|
48964
|
+
}, 0);
|
|
48965
|
+
}
|
|
48966
|
+
return {
|
|
48967
|
+
totalWorkspaces: total,
|
|
48968
|
+
healthyWorkspaces: healthy,
|
|
48969
|
+
unhealthyWorkspaces: unhealthy,
|
|
48970
|
+
warningWorkspaces: warning6,
|
|
48971
|
+
uptimePercentage,
|
|
48972
|
+
totalDowntimeMinutes,
|
|
48973
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
48974
|
+
};
|
|
48975
|
+
}, []);
|
|
48677
48976
|
const fetchWorkspacesHealth = React23.useCallback(async () => {
|
|
48678
|
-
if (
|
|
48977
|
+
if (isFetchingRef.current) return;
|
|
48679
48978
|
if (!options.companyId) {
|
|
48979
|
+
setWorkspaces([]);
|
|
48980
|
+
setSummary(computeSummary([]));
|
|
48680
48981
|
setLoading(false);
|
|
48681
48982
|
return;
|
|
48682
48983
|
}
|
|
@@ -48684,39 +48985,12 @@ var useWorkspaceHealth = (options) => {
|
|
|
48684
48985
|
isFetchingRef.current = true;
|
|
48685
48986
|
setLoading(true);
|
|
48686
48987
|
setError(null);
|
|
48687
|
-
|
|
48688
|
-
|
|
48689
|
-
|
|
48690
|
-
}
|
|
48691
|
-
query = query.order("workspace_display_name", { ascending: true });
|
|
48692
|
-
const { data, error: fetchError } = await query;
|
|
48693
|
-
if (fetchError) throw fetchError;
|
|
48694
|
-
const healthData = data || [];
|
|
48695
|
-
const workspacesWithStatus = healthData.map((ws) => {
|
|
48696
|
-
const status = calculateHealthStatus(ws);
|
|
48697
|
-
const now2 = /* @__PURE__ */ new Date();
|
|
48698
|
-
const lastUpdate = ws.last_heartbeat ? new Date(ws.last_heartbeat) : null;
|
|
48699
|
-
const timeSinceLastUpdate = lastUpdate ? `${Math.floor((now2.getTime() - lastUpdate.getTime()) / 6e4)}m ago` : "Never";
|
|
48700
|
-
return {
|
|
48701
|
-
...ws,
|
|
48702
|
-
status,
|
|
48703
|
-
timeSinceLastUpdate,
|
|
48704
|
-
isStale: !lastUpdate || now2.getTime() - lastUpdate.getTime() > 15 * 6e4
|
|
48705
|
-
};
|
|
48988
|
+
const workspacesWithStatus = await workspaceHealthService.getWorkspaceHealthStatus({
|
|
48989
|
+
lineId: options.lineId,
|
|
48990
|
+
companyId: options.companyId
|
|
48706
48991
|
});
|
|
48707
48992
|
setWorkspaces(workspacesWithStatus);
|
|
48708
|
-
|
|
48709
|
-
const healthy = workspacesWithStatus.filter((w) => w.status === "healthy").length;
|
|
48710
|
-
const unhealthy = workspacesWithStatus.filter((w) => w.status === "unhealthy").length;
|
|
48711
|
-
const warning6 = workspacesWithStatus.filter((w) => w.status === "warning").length;
|
|
48712
|
-
setSummary({
|
|
48713
|
-
totalWorkspaces: total,
|
|
48714
|
-
healthyWorkspaces: healthy,
|
|
48715
|
-
unhealthyWorkspaces: unhealthy,
|
|
48716
|
-
warningWorkspaces: warning6,
|
|
48717
|
-
uptimePercentage: total > 0 ? healthy / total * 100 : 100,
|
|
48718
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
48719
|
-
});
|
|
48993
|
+
setSummary(computeSummary(workspacesWithStatus));
|
|
48720
48994
|
} catch (err) {
|
|
48721
48995
|
console.error("[useWorkspaceHealth] Error fetching workspace health:", err);
|
|
48722
48996
|
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
@@ -48725,7 +48999,7 @@ var useWorkspaceHealth = (options) => {
|
|
|
48725
48999
|
setLoading(false);
|
|
48726
49000
|
isFetchingRef.current = false;
|
|
48727
49001
|
}
|
|
48728
|
-
}, [
|
|
49002
|
+
}, [options.companyId, options.lineId, computeSummary]);
|
|
48729
49003
|
React23.useEffect(() => {
|
|
48730
49004
|
fetchWorkspacesHealth();
|
|
48731
49005
|
}, [fetchWorkspacesHealth]);
|
|
@@ -48770,6 +49044,395 @@ var useWorkspaceHealth = (options) => {
|
|
|
48770
49044
|
refetch: fetchWorkspacesHealth
|
|
48771
49045
|
};
|
|
48772
49046
|
};
|
|
49047
|
+
var STATUS_COLORS = {
|
|
49048
|
+
up: "bg-emerald-500",
|
|
49049
|
+
down: "bg-rose-500",
|
|
49050
|
+
pending: "bg-gray-200"
|
|
49051
|
+
};
|
|
49052
|
+
var STATUS_TITLES = {
|
|
49053
|
+
up: "Uptime",
|
|
49054
|
+
down: "Downtime",
|
|
49055
|
+
pending: "Pending"
|
|
49056
|
+
};
|
|
49057
|
+
var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
|
|
49058
|
+
hour: "numeric",
|
|
49059
|
+
minute: "2-digit",
|
|
49060
|
+
hour12: true,
|
|
49061
|
+
timeZone: timezone
|
|
49062
|
+
}).format(date);
|
|
49063
|
+
var formatDuration = (minutes) => {
|
|
49064
|
+
if (minutes < 1) return "<1 min";
|
|
49065
|
+
if (minutes < 60) return `${minutes} min`;
|
|
49066
|
+
const hours = Math.floor(minutes / 60);
|
|
49067
|
+
const remainder = minutes % 60;
|
|
49068
|
+
if (remainder === 0) return `${hours} hr${hours > 1 ? "s" : ""}`;
|
|
49069
|
+
return `${hours} hr ${remainder} min`;
|
|
49070
|
+
};
|
|
49071
|
+
var formatDowntimeLabel = (minutes, includeSuffix = true) => {
|
|
49072
|
+
if (!minutes || minutes <= 0) {
|
|
49073
|
+
return includeSuffix ? "No downtime" : "0 min";
|
|
49074
|
+
}
|
|
49075
|
+
const rounded = Math.max(Math.round(minutes), 0);
|
|
49076
|
+
const days = Math.floor(rounded / 1440);
|
|
49077
|
+
const hours = Math.floor(rounded % 1440 / 60);
|
|
49078
|
+
const mins = rounded % 60;
|
|
49079
|
+
const parts = [];
|
|
49080
|
+
if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
|
|
49081
|
+
if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
|
|
49082
|
+
if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
|
|
49083
|
+
if (!parts.length) {
|
|
49084
|
+
parts.push("1 min");
|
|
49085
|
+
}
|
|
49086
|
+
const label = parts.join(" ");
|
|
49087
|
+
return includeSuffix ? `${label} down` : label;
|
|
49088
|
+
};
|
|
49089
|
+
var UptimeTimelineStrip = ({
|
|
49090
|
+
points,
|
|
49091
|
+
totalMinutes,
|
|
49092
|
+
shiftStart,
|
|
49093
|
+
shiftEnd,
|
|
49094
|
+
timezone,
|
|
49095
|
+
className = "",
|
|
49096
|
+
uptimePercentage = 0,
|
|
49097
|
+
downtimeMinutes = 0
|
|
49098
|
+
}) => {
|
|
49099
|
+
const segments = React23.useMemo(() => {
|
|
49100
|
+
if (!points.length || totalMinutes <= 0) return [];
|
|
49101
|
+
const result = [];
|
|
49102
|
+
let current = {
|
|
49103
|
+
status: points[0].status,
|
|
49104
|
+
length: 1,
|
|
49105
|
+
startMinuteIndex: points[0].minuteIndex,
|
|
49106
|
+
startTimestamp: points[0].timestamp,
|
|
49107
|
+
endTimestamp: points[0].timestamp
|
|
49108
|
+
};
|
|
49109
|
+
for (let i = 1; i < points.length; i++) {
|
|
49110
|
+
const point = points[i];
|
|
49111
|
+
if (point.status === current.status) {
|
|
49112
|
+
current.length += 1;
|
|
49113
|
+
current.endTimestamp = point.timestamp;
|
|
49114
|
+
} else {
|
|
49115
|
+
result.push(current);
|
|
49116
|
+
current = {
|
|
49117
|
+
status: point.status,
|
|
49118
|
+
length: 1,
|
|
49119
|
+
startMinuteIndex: point.minuteIndex,
|
|
49120
|
+
startTimestamp: point.timestamp,
|
|
49121
|
+
endTimestamp: point.timestamp
|
|
49122
|
+
};
|
|
49123
|
+
}
|
|
49124
|
+
}
|
|
49125
|
+
result.push(current);
|
|
49126
|
+
return result;
|
|
49127
|
+
}, [points, totalMinutes]);
|
|
49128
|
+
const markers = React23.useMemo(() => {
|
|
49129
|
+
if (totalMinutes <= 0) return [];
|
|
49130
|
+
const startDate = new Date(shiftStart);
|
|
49131
|
+
const endDate = new Date(shiftEnd);
|
|
49132
|
+
const roundedTotal = Math.max(totalMinutes, 1);
|
|
49133
|
+
const markerInterval = totalMinutes > 360 ? 120 : 60;
|
|
49134
|
+
const markerList = [];
|
|
49135
|
+
for (let minute = 0; minute <= roundedTotal; minute += markerInterval) {
|
|
49136
|
+
const markerMinute = Math.min(minute, roundedTotal);
|
|
49137
|
+
const markerDate = dateFns.addMinutes(startDate, markerMinute);
|
|
49138
|
+
markerList.push({
|
|
49139
|
+
minute: markerMinute,
|
|
49140
|
+
label: formatTime4(markerDate, timezone)
|
|
49141
|
+
});
|
|
49142
|
+
}
|
|
49143
|
+
const endLabel = formatTime4(endDate, timezone);
|
|
49144
|
+
if (!markerList.some((marker) => marker.minute === roundedTotal)) {
|
|
49145
|
+
markerList.push({
|
|
49146
|
+
minute: roundedTotal,
|
|
49147
|
+
label: endLabel
|
|
49148
|
+
});
|
|
49149
|
+
} else {
|
|
49150
|
+
markerList[markerList.length - 1] = {
|
|
49151
|
+
minute: roundedTotal,
|
|
49152
|
+
label: endLabel
|
|
49153
|
+
};
|
|
49154
|
+
}
|
|
49155
|
+
return markerList;
|
|
49156
|
+
}, [shiftStart, shiftEnd, timezone, totalMinutes]);
|
|
49157
|
+
if (!points.length || totalMinutes <= 0) {
|
|
49158
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full rounded-xl border border-dashed border-gray-200 bg-gray-50/50 p-6 text-center text-sm text-gray-600", children: "No uptime data available for this shift yet." });
|
|
49159
|
+
}
|
|
49160
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative w-full ${className}`, children: [
|
|
49161
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-900 font-semibold", children: [
|
|
49162
|
+
uptimePercentage.toFixed(1),
|
|
49163
|
+
" % uptime ",
|
|
49164
|
+
downtimeMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
49165
|
+
"(",
|
|
49166
|
+
formatDowntimeLabel(downtimeMinutes),
|
|
49167
|
+
")"
|
|
49168
|
+
] })
|
|
49169
|
+
] }) }),
|
|
49170
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex h-4 overflow-hidden rounded-lg border border-gray-200 shadow-sm bg-white", children: segments.map((segment, index) => {
|
|
49171
|
+
const startDate = new Date(segment.startTimestamp);
|
|
49172
|
+
const endDate = new Date(segment.endTimestamp);
|
|
49173
|
+
if (segment.length > 1) {
|
|
49174
|
+
endDate.setMinutes(endDate.getMinutes() + 1);
|
|
49175
|
+
}
|
|
49176
|
+
const tooltip = `${STATUS_TITLES[segment.status]} \u2022 ${formatDuration(segment.length)} (${formatTime4(
|
|
49177
|
+
startDate,
|
|
49178
|
+
timezone
|
|
49179
|
+
)} - ${formatTime4(endDate, timezone)})`;
|
|
49180
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
49181
|
+
"div",
|
|
49182
|
+
{
|
|
49183
|
+
className: `${STATUS_COLORS[segment.status]} transition-all hover:opacity-80 cursor-pointer`,
|
|
49184
|
+
style: { flex: segment.length },
|
|
49185
|
+
title: tooltip
|
|
49186
|
+
},
|
|
49187
|
+
`${segment.status}-${segment.startMinuteIndex}-${index}`
|
|
49188
|
+
);
|
|
49189
|
+
}) }),
|
|
49190
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none relative w-full mt-4 min-h-6", children: markers.map((marker) => {
|
|
49191
|
+
const left = totalMinutes > 0 ? marker.minute / totalMinutes * 100 : 0;
|
|
49192
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49193
|
+
"div",
|
|
49194
|
+
{
|
|
49195
|
+
className: "absolute flex -translate-x-1/2 flex-col items-center",
|
|
49196
|
+
style: { left: `${left}%` },
|
|
49197
|
+
children: [
|
|
49198
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1.5 h-2 w-px bg-gray-300" }),
|
|
49199
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium text-gray-600 whitespace-nowrap", children: marker.label })
|
|
49200
|
+
]
|
|
49201
|
+
},
|
|
49202
|
+
`${marker.label}-${marker.minute}`
|
|
49203
|
+
);
|
|
49204
|
+
}) })
|
|
49205
|
+
] });
|
|
49206
|
+
};
|
|
49207
|
+
var UptimeTimelineStrip_default = UptimeTimelineStrip;
|
|
49208
|
+
var SHORT_INTERRUPT_THRESHOLD_MINUTES = 3;
|
|
49209
|
+
var formatDuration2 = (minutes) => {
|
|
49210
|
+
if (!minutes || minutes <= 0) return "0 min";
|
|
49211
|
+
const rounded = Math.max(Math.round(minutes), 0);
|
|
49212
|
+
const days = Math.floor(rounded / 1440);
|
|
49213
|
+
const hours = Math.floor(rounded % 1440 / 60);
|
|
49214
|
+
const mins = rounded % 60;
|
|
49215
|
+
const parts = [];
|
|
49216
|
+
if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
|
|
49217
|
+
if (hours) parts.push(`${hours} hr${hours === 1 ? "" : "s"}`);
|
|
49218
|
+
if (mins) parts.push(`${mins} min${mins === 1 ? "" : "s"}`);
|
|
49219
|
+
if (!parts.length) {
|
|
49220
|
+
parts.push("1 min");
|
|
49221
|
+
}
|
|
49222
|
+
return parts.join(" ");
|
|
49223
|
+
};
|
|
49224
|
+
var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
|
|
49225
|
+
if (!minutes || minutes <= 0) {
|
|
49226
|
+
return includeSuffix ? "No downtime" : "0 min";
|
|
49227
|
+
}
|
|
49228
|
+
const label = formatDuration2(minutes);
|
|
49229
|
+
return includeSuffix ? `${label} down` : label;
|
|
49230
|
+
};
|
|
49231
|
+
var formatTimeRange = (start, end, timezone) => {
|
|
49232
|
+
const formatter = new Intl.DateTimeFormat("en-IN", {
|
|
49233
|
+
hour: "numeric",
|
|
49234
|
+
minute: "2-digit",
|
|
49235
|
+
hour12: true,
|
|
49236
|
+
timeZone: timezone
|
|
49237
|
+
});
|
|
49238
|
+
return `${formatter.format(start)} - ${formatter.format(end)}`;
|
|
49239
|
+
};
|
|
49240
|
+
var WorkspaceUptimeDetailModal = ({
|
|
49241
|
+
workspace,
|
|
49242
|
+
isOpen,
|
|
49243
|
+
onClose
|
|
49244
|
+
}) => {
|
|
49245
|
+
const timezone = useAppTimezone() || "UTC";
|
|
49246
|
+
const logsContainerRef = React23.useRef(null);
|
|
49247
|
+
const [showScrollIndicator, setShowScrollIndicator] = React23.useState(false);
|
|
49248
|
+
const {
|
|
49249
|
+
timeline,
|
|
49250
|
+
loading,
|
|
49251
|
+
error,
|
|
49252
|
+
refetch
|
|
49253
|
+
} = useWorkspaceUptimeTimeline({
|
|
49254
|
+
workspaceId: workspace?.workspace_id,
|
|
49255
|
+
companyId: workspace?.company_id,
|
|
49256
|
+
enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
|
|
49257
|
+
refreshInterval: 6e4
|
|
49258
|
+
});
|
|
49259
|
+
React23.useEffect(() => {
|
|
49260
|
+
if (!isOpen || !workspace) return;
|
|
49261
|
+
const handleKeyDown = (event) => {
|
|
49262
|
+
if (event.key === "Escape") {
|
|
49263
|
+
onClose();
|
|
49264
|
+
}
|
|
49265
|
+
};
|
|
49266
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
49267
|
+
return () => {
|
|
49268
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
49269
|
+
};
|
|
49270
|
+
}, [isOpen, onClose, workspace]);
|
|
49271
|
+
const shiftStart = timeline ? new Date(timeline.shiftStart) : null;
|
|
49272
|
+
const shiftEnd = timeline ? new Date(timeline.shiftEnd) : null;
|
|
49273
|
+
const downtimeSegments = timeline?.downtimeSegments || [];
|
|
49274
|
+
downtimeSegments.length;
|
|
49275
|
+
const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
|
|
49276
|
+
const uptimePercentage = timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? 0;
|
|
49277
|
+
const allInterruptionsSorted = React23.useMemo(
|
|
49278
|
+
() => [...downtimeSegments].sort(
|
|
49279
|
+
(a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
|
|
49280
|
+
),
|
|
49281
|
+
[downtimeSegments]
|
|
49282
|
+
);
|
|
49283
|
+
React23.useEffect(() => {
|
|
49284
|
+
const checkScroll = () => {
|
|
49285
|
+
const container2 = logsContainerRef.current;
|
|
49286
|
+
if (container2) {
|
|
49287
|
+
const hasScroll = container2.scrollHeight > container2.clientHeight;
|
|
49288
|
+
const isAtBottom = container2.scrollHeight - container2.scrollTop <= container2.clientHeight + 10;
|
|
49289
|
+
setShowScrollIndicator(hasScroll && !isAtBottom);
|
|
49290
|
+
}
|
|
49291
|
+
};
|
|
49292
|
+
checkScroll();
|
|
49293
|
+
const container = logsContainerRef.current;
|
|
49294
|
+
if (container) {
|
|
49295
|
+
container.addEventListener("scroll", checkScroll);
|
|
49296
|
+
return () => container.removeEventListener("scroll", checkScroll);
|
|
49297
|
+
}
|
|
49298
|
+
}, [downtimeSegments]);
|
|
49299
|
+
const renderSegment = (segment) => {
|
|
49300
|
+
const start = new Date(segment.startTime);
|
|
49301
|
+
const end = new Date(segment.endTime);
|
|
49302
|
+
const isMajor = segment.durationMinutes >= SHORT_INTERRUPT_THRESHOLD_MINUTES;
|
|
49303
|
+
const containerClasses = isMajor ? "border-rose-200 bg-rose-50" : "border-gray-200 bg-white";
|
|
49304
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49305
|
+
"div",
|
|
49306
|
+
{
|
|
49307
|
+
className: `rounded-lg border px-5 py-3 ${containerClasses}`,
|
|
49308
|
+
children: [
|
|
49309
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-gray-900", children: formatTimeRange(start, end, timezone) }),
|
|
49310
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-600 mt-1", children: [
|
|
49311
|
+
"Duration: ",
|
|
49312
|
+
formatDowntimeLabel2(segment.durationMinutes, false)
|
|
49313
|
+
] })
|
|
49314
|
+
]
|
|
49315
|
+
},
|
|
49316
|
+
`${segment.startMinuteIndex}-${segment.endMinuteIndex}`
|
|
49317
|
+
);
|
|
49318
|
+
};
|
|
49319
|
+
if (!isOpen || !workspace) {
|
|
49320
|
+
return null;
|
|
49321
|
+
}
|
|
49322
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
49323
|
+
"div",
|
|
49324
|
+
{
|
|
49325
|
+
className: "fixed inset-0 z-[60] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4",
|
|
49326
|
+
onClick: onClose,
|
|
49327
|
+
"aria-modal": "true",
|
|
49328
|
+
role: "dialog",
|
|
49329
|
+
"aria-labelledby": "uptime-detail-title",
|
|
49330
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49331
|
+
"div",
|
|
49332
|
+
{
|
|
49333
|
+
className: "relative flex w-full max-w-4xl max-h-[90vh] flex-col rounded-2xl bg-white shadow-2xl border border-gray-100",
|
|
49334
|
+
onClick: (event) => event.stopPropagation(),
|
|
49335
|
+
role: "document",
|
|
49336
|
+
"aria-labelledby": "uptime-detail-title",
|
|
49337
|
+
children: [
|
|
49338
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-start justify-between border-b border-gray-100 px-8 py-6 sticky top-0 z-10 bg-white rounded-t-2xl", children: [
|
|
49339
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
|
|
49340
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: "uptime-detail-title", className: "text-2xl font-semibold text-gray-900 truncate mb-3", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 6)}` }),
|
|
49341
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
|
|
49342
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-900", children: timeline?.shiftLabel || "Current Shift" }),
|
|
49343
|
+
shiftStart && shiftEnd && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
49344
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
49345
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
|
|
49346
|
+
] })
|
|
49347
|
+
] }),
|
|
49348
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
49349
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
49350
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-2 h-2 rounded-full ${workspace.status === "healthy" ? "bg-emerald-500 animate-pulse" : workspace.status === "warning" ? "bg-amber-500" : "bg-rose-500"}` }),
|
|
49351
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs font-medium ${workspace.status === "healthy" ? "text-emerald-700" : workspace.status === "warning" ? "text-amber-700" : "text-rose-700"}`, children: workspace.status === "healthy" ? "Operational" : workspace.status === "warning" ? "Intermittent" : "Down" })
|
|
49352
|
+
] }),
|
|
49353
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
49354
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500", children: [
|
|
49355
|
+
"Last heartbeat ",
|
|
49356
|
+
workspace.timeSinceLastUpdate
|
|
49357
|
+
] })
|
|
49358
|
+
] })
|
|
49359
|
+
] }),
|
|
49360
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
49361
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
49362
|
+
"button",
|
|
49363
|
+
{
|
|
49364
|
+
onClick: refetch,
|
|
49365
|
+
disabled: loading,
|
|
49366
|
+
className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 transition-all duration-200 shadow-sm",
|
|
49367
|
+
children: [
|
|
49368
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }),
|
|
49369
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Refresh" })
|
|
49370
|
+
]
|
|
49371
|
+
}
|
|
49372
|
+
),
|
|
49373
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
49374
|
+
"button",
|
|
49375
|
+
{
|
|
49376
|
+
onClick: onClose,
|
|
49377
|
+
className: "rounded-lg p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-600 transition-colors duration-200",
|
|
49378
|
+
"aria-label": "Close uptime details",
|
|
49379
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-5 w-5" })
|
|
49380
|
+
}
|
|
49381
|
+
)
|
|
49382
|
+
] })
|
|
49383
|
+
] }),
|
|
49384
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-8 py-6 space-y-6", children: error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-rose-100 bg-rose-50 p-5 text-sm text-rose-700", children: [
|
|
49385
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
|
|
49386
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-rose-600/90", children: error.message })
|
|
49387
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
49388
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
|
|
49389
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
49390
|
+
UptimeTimelineStrip_default,
|
|
49391
|
+
{
|
|
49392
|
+
points: timeline?.points || [],
|
|
49393
|
+
totalMinutes: timeline?.totalMinutes || 0,
|
|
49394
|
+
shiftStart: timeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
|
|
49395
|
+
shiftEnd: timeline?.shiftEnd || (/* @__PURE__ */ new Date()).toISOString(),
|
|
49396
|
+
timezone,
|
|
49397
|
+
uptimePercentage,
|
|
49398
|
+
downtimeMinutes
|
|
49399
|
+
}
|
|
49400
|
+
),
|
|
49401
|
+
loading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
|
|
49402
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
|
|
49403
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Updating timeline\u2026" })
|
|
49404
|
+
] })
|
|
49405
|
+
] }),
|
|
49406
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-4", children: [
|
|
49407
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
|
|
49408
|
+
downtimeSegments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600", children: "No downtime events recorded for this shift." }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
49409
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
49410
|
+
"div",
|
|
49411
|
+
{
|
|
49412
|
+
ref: logsContainerRef,
|
|
49413
|
+
className: "max-h-[400px] overflow-y-auto space-y-2 pr-2",
|
|
49414
|
+
style: {
|
|
49415
|
+
scrollbarWidth: "thin",
|
|
49416
|
+
scrollbarColor: "#CBD5E0 #F7FAFC"
|
|
49417
|
+
},
|
|
49418
|
+
children: allInterruptionsSorted.map((segment) => renderSegment(segment))
|
|
49419
|
+
}
|
|
49420
|
+
),
|
|
49421
|
+
showScrollIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white via-white/80 to-transparent pointer-events-none flex items-end justify-center pb-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-gray-500 animate-bounce", children: [
|
|
49422
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" }),
|
|
49423
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Scroll for more" }),
|
|
49424
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
|
|
49425
|
+
] }) })
|
|
49426
|
+
] })
|
|
49427
|
+
] })
|
|
49428
|
+
] }) }) })
|
|
49429
|
+
]
|
|
49430
|
+
}
|
|
49431
|
+
)
|
|
49432
|
+
}
|
|
49433
|
+
);
|
|
49434
|
+
};
|
|
49435
|
+
var WorkspaceUptimeDetailModal_default = WorkspaceUptimeDetailModal;
|
|
48773
49436
|
var WorkspaceHealthView = ({
|
|
48774
49437
|
lineId,
|
|
48775
49438
|
companyId,
|
|
@@ -48779,6 +49442,7 @@ var WorkspaceHealthView = ({
|
|
|
48779
49442
|
const router$1 = router.useRouter();
|
|
48780
49443
|
const [groupBy, setGroupBy] = React23.useState("line");
|
|
48781
49444
|
const timezone = useAppTimezone();
|
|
49445
|
+
const [selectedWorkspace, setSelectedWorkspace] = React23.useState(null);
|
|
48782
49446
|
const operationalDate = getOperationalDate(timezone || "UTC");
|
|
48783
49447
|
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
48784
49448
|
const isNightShift = currentHour >= 18 || currentHour < 6;
|
|
@@ -48819,6 +49483,12 @@ var WorkspaceHealthView = ({
|
|
|
48819
49483
|
},
|
|
48820
49484
|
[router$1, onNavigate]
|
|
48821
49485
|
);
|
|
49486
|
+
const handleViewDetails = React23.useCallback((workspace) => {
|
|
49487
|
+
setSelectedWorkspace(workspace);
|
|
49488
|
+
}, []);
|
|
49489
|
+
const handleCloseDetails = React23.useCallback(() => {
|
|
49490
|
+
setSelectedWorkspace(null);
|
|
49491
|
+
}, []);
|
|
48822
49492
|
const handleExport = React23.useCallback(() => {
|
|
48823
49493
|
const csv = [
|
|
48824
49494
|
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
@@ -48876,178 +49546,189 @@ var WorkspaceHealthView = ({
|
|
|
48876
49546
|
)
|
|
48877
49547
|
] }) }) }) });
|
|
48878
49548
|
}
|
|
48879
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48880
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
48881
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
48882
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
48883
|
-
/* @__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(
|
|
48884
49596
|
BackButtonMinimal,
|
|
48885
49597
|
{
|
|
48886
49598
|
onClick: () => router$1.push("/"),
|
|
48887
49599
|
text: "Back",
|
|
48888
|
-
size: "
|
|
49600
|
+
size: "default",
|
|
48889
49601
|
"aria-label": "Navigate back to dashboard"
|
|
48890
49602
|
}
|
|
48891
|
-
),
|
|
48892
|
-
/* @__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: [
|
|
48893
49612
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48894
49613
|
"button",
|
|
48895
49614
|
{
|
|
48896
49615
|
onClick: () => {
|
|
48897
49616
|
refetch();
|
|
48898
49617
|
},
|
|
48899
|
-
className: "p-
|
|
49618
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48900
49619
|
"aria-label": "Refresh",
|
|
48901
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-
|
|
49620
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
48902
49621
|
}
|
|
48903
49622
|
),
|
|
48904
49623
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48905
49624
|
"button",
|
|
48906
49625
|
{
|
|
48907
49626
|
onClick: handleExport,
|
|
48908
|
-
className: "p-
|
|
49627
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48909
49628
|
"aria-label": "Export CSV",
|
|
48910
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-
|
|
49629
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
48911
49630
|
}
|
|
48912
49631
|
)
|
|
48913
|
-
] })
|
|
48914
|
-
|
|
48915
|
-
|
|
48916
|
-
|
|
48917
|
-
/* @__PURE__ */ jsxRuntime.
|
|
48918
|
-
|
|
48919
|
-
|
|
49632
|
+
] }),
|
|
49633
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
49634
|
+
] }) }),
|
|
49635
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 sm:mt-2 bg-blue-50 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-4", children: [
|
|
49636
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm sm:text-base md:text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(LiveTimer, {}) }),
|
|
49637
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
|
|
49638
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
|
|
49639
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
|
|
49640
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
|
|
49641
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
|
|
49642
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: [
|
|
49643
|
+
shiftType,
|
|
49644
|
+
" Shift"
|
|
49645
|
+
] })
|
|
48920
49646
|
] })
|
|
48921
49647
|
] }) })
|
|
48922
49648
|
] }),
|
|
48923
|
-
/* @__PURE__ */ jsxRuntime.
|
|
48924
|
-
|
|
48925
|
-
|
|
49649
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
49650
|
+
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49651
|
+
motion.div,
|
|
48926
49652
|
{
|
|
48927
|
-
|
|
48928
|
-
|
|
48929
|
-
|
|
48930
|
-
"
|
|
48931
|
-
|
|
48932
|
-
|
|
48933
|
-
|
|
48934
|
-
|
|
48935
|
-
|
|
48936
|
-
|
|
48937
|
-
|
|
48938
|
-
|
|
48939
|
-
|
|
48940
|
-
|
|
48941
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48942
|
-
"button",
|
|
48943
|
-
{
|
|
48944
|
-
onClick: () => {
|
|
48945
|
-
refetch();
|
|
48946
|
-
},
|
|
48947
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48948
|
-
"aria-label": "Refresh",
|
|
48949
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
48950
|
-
}
|
|
48951
|
-
),
|
|
48952
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
48953
|
-
"button",
|
|
48954
|
-
{
|
|
48955
|
-
onClick: handleExport,
|
|
48956
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
48957
|
-
"aria-label": "Export CSV",
|
|
48958
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
48959
|
-
}
|
|
48960
|
-
)
|
|
48961
|
-
] }),
|
|
48962
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
48963
|
-
] }) }),
|
|
48964
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 sm:mt-3 bg-blue-50 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-4", children: [
|
|
48965
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm sm:text-base md:text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(LiveTimer, {}) }),
|
|
48966
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
|
|
48967
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
|
|
48968
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-blue-300" }),
|
|
48969
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
|
|
48970
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
|
|
48971
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-sm md:text-base font-medium text-blue-600", children: [
|
|
48972
|
-
shiftType,
|
|
48973
|
-
" Shift"
|
|
48974
|
-
] })
|
|
48975
|
-
] })
|
|
48976
|
-
] }) })
|
|
48977
|
-
] }),
|
|
48978
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
48979
|
-
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48980
|
-
motion.div,
|
|
48981
|
-
{
|
|
48982
|
-
initial: { opacity: 0, y: 20 },
|
|
48983
|
-
animate: { opacity: 1, y: 0 },
|
|
48984
|
-
transition: { duration: 0.3, delay: 0.1 },
|
|
48985
|
-
className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
|
|
48986
|
-
children: [
|
|
48987
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
|
|
48988
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "System Availability" }) }),
|
|
48989
|
-
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
48990
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
48991
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
|
|
48992
|
-
summary.uptimePercentage.toFixed(1),
|
|
48993
|
-
"%"
|
|
49653
|
+
initial: { opacity: 0, y: 20 },
|
|
49654
|
+
animate: { opacity: 1, y: 0 },
|
|
49655
|
+
transition: { duration: 0.3, delay: 0.1 },
|
|
49656
|
+
className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
|
|
49657
|
+
children: [
|
|
49658
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
|
|
49659
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "System Availability" }) }),
|
|
49660
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
49661
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
49662
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
|
|
49663
|
+
summary.uptimePercentage.toFixed(1),
|
|
49664
|
+
"%"
|
|
49665
|
+
] }),
|
|
49666
|
+
summary.uptimePercentage >= 97 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "h-5 w-5 text-green-500" }) : summary.uptimePercentage >= 90 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5 text-yellow-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingDown, { className: "h-5 w-5 text-red-500" })
|
|
48994
49667
|
] }),
|
|
48995
|
-
|
|
48996
|
-
] })
|
|
48997
|
-
|
|
48998
|
-
|
|
48999
|
-
|
|
49000
|
-
|
|
49001
|
-
|
|
49002
|
-
|
|
49003
|
-
|
|
49004
|
-
|
|
49005
|
-
|
|
49006
|
-
|
|
49007
|
-
|
|
49008
|
-
|
|
49009
|
-
|
|
49010
|
-
|
|
49011
|
-
|
|
49012
|
-
|
|
49013
|
-
|
|
49014
|
-
|
|
49015
|
-
|
|
49016
|
-
|
|
49017
|
-
|
|
49018
|
-
|
|
49019
|
-
|
|
49020
|
-
|
|
49021
|
-
|
|
49022
|
-
|
|
49023
|
-
|
|
49024
|
-
|
|
49025
|
-
|
|
49026
|
-
|
|
49027
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
|
|
49668
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Overall system uptime today" })
|
|
49669
|
+
] })
|
|
49670
|
+
] }),
|
|
49671
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
|
|
49672
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
49673
|
+
getStatusIcon("healthy"),
|
|
49674
|
+
"Healthy"
|
|
49675
|
+
] }) }),
|
|
49676
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
49677
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.healthyWorkspaces }),
|
|
49678
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
|
|
49679
|
+
] })
|
|
49680
|
+
] }),
|
|
49681
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
|
|
49682
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
49683
|
+
getStatusIcon("warning"),
|
|
49684
|
+
"Warning"
|
|
49685
|
+
] }) }),
|
|
49686
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
49687
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.warningWorkspaces }),
|
|
49688
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
|
|
49689
|
+
] })
|
|
49690
|
+
] }),
|
|
49691
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
|
|
49692
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
49693
|
+
getStatusIcon("unhealthy"),
|
|
49694
|
+
"Unhealthy"
|
|
49695
|
+
] }) }),
|
|
49696
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
49697
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.unhealthyWorkspaces }),
|
|
49698
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
|
|
49699
|
+
] })
|
|
49028
49700
|
] })
|
|
49029
|
-
]
|
|
49030
|
-
|
|
49031
|
-
|
|
49032
|
-
|
|
49033
|
-
|
|
49034
|
-
|
|
49035
|
-
|
|
49036
|
-
|
|
49037
|
-
|
|
49038
|
-
|
|
49039
|
-
|
|
49040
|
-
|
|
49041
|
-
|
|
49042
|
-
|
|
49043
|
-
|
|
49044
|
-
|
|
49045
|
-
|
|
49046
|
-
|
|
49047
|
-
|
|
49048
|
-
|
|
49049
|
-
|
|
49050
|
-
|
|
49701
|
+
]
|
|
49702
|
+
}
|
|
49703
|
+
),
|
|
49704
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
49705
|
+
motion.div,
|
|
49706
|
+
{
|
|
49707
|
+
initial: { opacity: 0, y: 20 },
|
|
49708
|
+
animate: { opacity: 1, y: 0 },
|
|
49709
|
+
transition: { duration: 0.3, delay: 0.2 },
|
|
49710
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
49711
|
+
HealthStatusGrid,
|
|
49712
|
+
{
|
|
49713
|
+
workspaces,
|
|
49714
|
+
onWorkspaceClick: handleWorkspaceClick,
|
|
49715
|
+
onWorkspaceViewDetails: handleViewDetails,
|
|
49716
|
+
showFilters: true,
|
|
49717
|
+
groupBy
|
|
49718
|
+
}
|
|
49719
|
+
)
|
|
49720
|
+
}
|
|
49721
|
+
)
|
|
49722
|
+
] })
|
|
49723
|
+
] }),
|
|
49724
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
49725
|
+
WorkspaceUptimeDetailModal_default,
|
|
49726
|
+
{
|
|
49727
|
+
workspace: selectedWorkspace,
|
|
49728
|
+
isOpen: Boolean(selectedWorkspace),
|
|
49729
|
+
onClose: handleCloseDetails
|
|
49730
|
+
}
|
|
49731
|
+
)
|
|
49051
49732
|
] });
|
|
49052
49733
|
};
|
|
49053
49734
|
var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
|
|
@@ -53007,6 +53688,7 @@ exports.useWorkspaceHealthStatus = useWorkspaceHealthStatus;
|
|
|
53007
53688
|
exports.useWorkspaceMetrics = useWorkspaceMetrics;
|
|
53008
53689
|
exports.useWorkspaceNavigation = useWorkspaceNavigation;
|
|
53009
53690
|
exports.useWorkspaceOperators = useWorkspaceOperators;
|
|
53691
|
+
exports.useWorkspaceUptimeTimeline = useWorkspaceUptimeTimeline;
|
|
53010
53692
|
exports.userService = userService;
|
|
53011
53693
|
exports.videoPrefetchManager = videoPrefetchManager;
|
|
53012
53694
|
exports.videoPreloader = videoPreloader;
|