@optifye/dashboard-core 6.12.50 → 6.12.51
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/{automation-B472r1h3.d.mts → automation-Bxp-JzQB.d.mts} +9 -1
- package/dist/{automation-B472r1h3.d.ts → automation-Bxp-JzQB.d.ts} +9 -1
- package/dist/automation.d.mts +1 -1
- package/dist/automation.d.ts +1 -1
- package/dist/index.css +4 -0
- package/dist/index.d.mts +111 -14
- package/dist/index.d.ts +111 -14
- package/dist/index.js +1144 -253
- package/dist/index.mjs +1145 -255
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6085,6 +6085,10 @@ var workspaceService = {
|
|
|
6085
6085
|
_workspaceCameraIpsInFlight: /* @__PURE__ */ new Map(),
|
|
6086
6086
|
_workspaceCameraIpsCacheExpiryMs: 5 * 60 * 1e3,
|
|
6087
6087
|
// 5 minutes cache
|
|
6088
|
+
// Cache for workspace bulb IP configs
|
|
6089
|
+
_workspaceLightConfigsCache: /* @__PURE__ */ new Map(),
|
|
6090
|
+
_workspaceLightConfigsInFlight: /* @__PURE__ */ new Map(),
|
|
6091
|
+
_workspaceLightConfigsCacheExpiryMs: 5 * 60 * 1e3,
|
|
6088
6092
|
async getWorkspaces(lineId, options) {
|
|
6089
6093
|
const enabledOnly = options?.enabledOnly ?? false;
|
|
6090
6094
|
const force = options?.force ?? false;
|
|
@@ -6262,6 +6266,61 @@ var workspaceService = {
|
|
|
6262
6266
|
this._workspaceCameraIpsInFlight.set(cacheKey, fetchPromise);
|
|
6263
6267
|
return fetchPromise;
|
|
6264
6268
|
},
|
|
6269
|
+
async getWorkspaceLightConfigs(params) {
|
|
6270
|
+
const workspaceIds = (params.workspaceIds || []).filter(Boolean);
|
|
6271
|
+
const lineIds = (params.lineIds || []).filter(Boolean);
|
|
6272
|
+
const force = params.force ?? false;
|
|
6273
|
+
if (!workspaceIds.length && !lineIds.length) {
|
|
6274
|
+
return {};
|
|
6275
|
+
}
|
|
6276
|
+
const workspaceKey = workspaceIds.slice().sort().join(",");
|
|
6277
|
+
const lineKey = lineIds.slice().sort().join(",");
|
|
6278
|
+
const cacheKey = workspaceKey ? `workspaces:${workspaceKey}` : `lines:${lineKey}`;
|
|
6279
|
+
const now4 = Date.now();
|
|
6280
|
+
const cached = this._workspaceLightConfigsCache.get(cacheKey);
|
|
6281
|
+
if (!force && cached && now4 - cached.timestamp < this._workspaceLightConfigsCacheExpiryMs) {
|
|
6282
|
+
return cached.lightConfigs;
|
|
6283
|
+
}
|
|
6284
|
+
const inFlight = this._workspaceLightConfigsInFlight.get(cacheKey);
|
|
6285
|
+
if (!force && inFlight) {
|
|
6286
|
+
return inFlight;
|
|
6287
|
+
}
|
|
6288
|
+
const fetchPromise = (async () => {
|
|
6289
|
+
try {
|
|
6290
|
+
const token = await getAuthToken2();
|
|
6291
|
+
const apiUrl = getBackendUrl2();
|
|
6292
|
+
const response = await fetch(`${apiUrl}/api/workspaces/light-configs`, {
|
|
6293
|
+
method: "POST",
|
|
6294
|
+
headers: {
|
|
6295
|
+
"Authorization": `Bearer ${token}`,
|
|
6296
|
+
"Content-Type": "application/json"
|
|
6297
|
+
},
|
|
6298
|
+
body: JSON.stringify({
|
|
6299
|
+
workspace_ids: workspaceIds.length ? workspaceIds : void 0,
|
|
6300
|
+
line_ids: lineIds.length ? lineIds : void 0
|
|
6301
|
+
})
|
|
6302
|
+
});
|
|
6303
|
+
if (!response.ok) {
|
|
6304
|
+
const errorText = await response.text();
|
|
6305
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
6306
|
+
}
|
|
6307
|
+
const data = await response.json();
|
|
6308
|
+
const lightConfigs = data.light_configs || {};
|
|
6309
|
+
this._workspaceLightConfigsCache.set(cacheKey, {
|
|
6310
|
+
lightConfigs,
|
|
6311
|
+
timestamp: Date.now()
|
|
6312
|
+
});
|
|
6313
|
+
return lightConfigs;
|
|
6314
|
+
} catch (error) {
|
|
6315
|
+
console.error("Error fetching workspace light configs:", error);
|
|
6316
|
+
throw error;
|
|
6317
|
+
} finally {
|
|
6318
|
+
this._workspaceLightConfigsInFlight.delete(cacheKey);
|
|
6319
|
+
}
|
|
6320
|
+
})();
|
|
6321
|
+
this._workspaceLightConfigsInFlight.set(cacheKey, fetchPromise);
|
|
6322
|
+
return fetchPromise;
|
|
6323
|
+
},
|
|
6265
6324
|
/**
|
|
6266
6325
|
* Fetches workspace display names from the database
|
|
6267
6326
|
* Returns a map of workspace_id -> display_name
|
|
@@ -6682,6 +6741,138 @@ var computeWorkspaceHealthSummary = (data, lastUpdated = (/* @__PURE__ */ new Da
|
|
|
6682
6741
|
lastUpdated
|
|
6683
6742
|
};
|
|
6684
6743
|
};
|
|
6744
|
+
var normalizeHourBucket = (bucket) => {
|
|
6745
|
+
if (Array.isArray(bucket)) return bucket;
|
|
6746
|
+
if (bucket && typeof bucket === "object" && "values" in bucket && Array.isArray(bucket.values)) {
|
|
6747
|
+
return bucket.values;
|
|
6748
|
+
}
|
|
6749
|
+
return void 0;
|
|
6750
|
+
};
|
|
6751
|
+
var normalizeOutputHourly = (outputHourlyRaw) => outputHourlyRaw && typeof outputHourlyRaw === "object" ? Object.fromEntries(
|
|
6752
|
+
Object.entries(outputHourlyRaw).map(([key, value]) => [key, normalizeHourBucket(value) || []])
|
|
6753
|
+
) : {};
|
|
6754
|
+
var interpretUptimeValue = (value) => {
|
|
6755
|
+
if (value === null || value === void 0) return "down";
|
|
6756
|
+
if (typeof value === "string") {
|
|
6757
|
+
return value.trim().toLowerCase() === "x" ? "down" : "up";
|
|
6758
|
+
}
|
|
6759
|
+
return "up";
|
|
6760
|
+
};
|
|
6761
|
+
var deriveStatusForMinute = (minuteOffset, minuteDate, outputHourly, outputArray, timezone) => {
|
|
6762
|
+
const hourKey = dateFnsTz.formatInTimeZone(minuteDate, timezone, "H");
|
|
6763
|
+
const minuteKey = Number.parseInt(dateFnsTz.formatInTimeZone(minuteDate, timezone, "m"), 10);
|
|
6764
|
+
const hourBucket = outputHourly[hourKey];
|
|
6765
|
+
if (Array.isArray(hourBucket)) {
|
|
6766
|
+
const value = hourBucket[minuteKey];
|
|
6767
|
+
if (value !== void 0) {
|
|
6768
|
+
return interpretUptimeValue(value);
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
if (minuteOffset < outputArray.length) {
|
|
6772
|
+
return interpretUptimeValue(outputArray[minuteOffset]);
|
|
6773
|
+
}
|
|
6774
|
+
return "down";
|
|
6775
|
+
};
|
|
6776
|
+
var hasCompletedTimelineData = (completedMinutes, shiftStartDate, outputHourly, outputArray, timezone) => {
|
|
6777
|
+
if (completedMinutes <= 0) {
|
|
6778
|
+
return false;
|
|
6779
|
+
}
|
|
6780
|
+
for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex += 1) {
|
|
6781
|
+
const minuteDate = dateFns.addMinutes(shiftStartDate, minuteIndex);
|
|
6782
|
+
const hourKey = dateFnsTz.formatInTimeZone(minuteDate, timezone, "H");
|
|
6783
|
+
const minuteKey = Number.parseInt(dateFnsTz.formatInTimeZone(minuteDate, timezone, "m"), 10);
|
|
6784
|
+
const hourBucket = outputHourly[hourKey];
|
|
6785
|
+
if (Array.isArray(hourBucket) && hourBucket[minuteKey] !== void 0) {
|
|
6786
|
+
return true;
|
|
6787
|
+
}
|
|
6788
|
+
if (minuteIndex < outputArray.length) {
|
|
6789
|
+
return true;
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
return false;
|
|
6793
|
+
};
|
|
6794
|
+
var computeWorkspaceUptime = ({
|
|
6795
|
+
totalMinutes,
|
|
6796
|
+
completedMinutes,
|
|
6797
|
+
shiftStartDate,
|
|
6798
|
+
outputHourly,
|
|
6799
|
+
outputArray,
|
|
6800
|
+
timezone
|
|
6801
|
+
}) => {
|
|
6802
|
+
const hasData = hasCompletedTimelineData(
|
|
6803
|
+
completedMinutes,
|
|
6804
|
+
shiftStartDate,
|
|
6805
|
+
outputHourly,
|
|
6806
|
+
outputArray,
|
|
6807
|
+
timezone
|
|
6808
|
+
);
|
|
6809
|
+
if (!hasData) {
|
|
6810
|
+
return {
|
|
6811
|
+
hasData: false,
|
|
6812
|
+
uptimeMinutes: 0,
|
|
6813
|
+
downtimeMinutes: 0,
|
|
6814
|
+
uptimePercentage: 0,
|
|
6815
|
+
points: [],
|
|
6816
|
+
downtimeSegments: []
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
const points = [];
|
|
6820
|
+
let uptimeMinutes = 0;
|
|
6821
|
+
let downtimeMinutes = 0;
|
|
6822
|
+
for (let minuteIndex = 0; minuteIndex < totalMinutes; minuteIndex += 1) {
|
|
6823
|
+
const minuteDate = dateFns.addMinutes(shiftStartDate, minuteIndex);
|
|
6824
|
+
const timestamp = dateFnsTz.formatInTimeZone(minuteDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
6825
|
+
const status = minuteIndex < completedMinutes ? deriveStatusForMinute(minuteIndex, minuteDate, outputHourly, outputArray, timezone) : "pending";
|
|
6826
|
+
if (status === "up") {
|
|
6827
|
+
uptimeMinutes += 1;
|
|
6828
|
+
} else if (status === "down") {
|
|
6829
|
+
downtimeMinutes += 1;
|
|
6830
|
+
}
|
|
6831
|
+
points.push({
|
|
6832
|
+
minuteIndex,
|
|
6833
|
+
timestamp,
|
|
6834
|
+
status
|
|
6835
|
+
});
|
|
6836
|
+
}
|
|
6837
|
+
const downtimeSegments = [];
|
|
6838
|
+
let currentSegmentStart = null;
|
|
6839
|
+
const pushSegment = (startIndex, endIndex) => {
|
|
6840
|
+
if (endIndex <= startIndex) return;
|
|
6841
|
+
const segmentStartDate = dateFns.addMinutes(shiftStartDate, startIndex);
|
|
6842
|
+
const segmentEndDate = dateFns.addMinutes(shiftStartDate, endIndex);
|
|
6843
|
+
downtimeSegments.push({
|
|
6844
|
+
startMinuteIndex: startIndex,
|
|
6845
|
+
endMinuteIndex: endIndex,
|
|
6846
|
+
startTime: dateFnsTz.formatInTimeZone(segmentStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
6847
|
+
endTime: dateFnsTz.formatInTimeZone(segmentEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
6848
|
+
durationMinutes: endIndex - startIndex
|
|
6849
|
+
});
|
|
6850
|
+
};
|
|
6851
|
+
for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex += 1) {
|
|
6852
|
+
const point = points[minuteIndex];
|
|
6853
|
+
if (point.status === "down") {
|
|
6854
|
+
if (currentSegmentStart === null) {
|
|
6855
|
+
currentSegmentStart = minuteIndex;
|
|
6856
|
+
}
|
|
6857
|
+
} else if (currentSegmentStart !== null) {
|
|
6858
|
+
pushSegment(currentSegmentStart, minuteIndex);
|
|
6859
|
+
currentSegmentStart = null;
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
if (currentSegmentStart !== null) {
|
|
6863
|
+
pushSegment(currentSegmentStart, completedMinutes);
|
|
6864
|
+
}
|
|
6865
|
+
const completedWindow = Math.max(1, uptimeMinutes + downtimeMinutes);
|
|
6866
|
+
const uptimePercentage = completedMinutes > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
6867
|
+
return {
|
|
6868
|
+
hasData: true,
|
|
6869
|
+
uptimeMinutes,
|
|
6870
|
+
downtimeMinutes,
|
|
6871
|
+
uptimePercentage,
|
|
6872
|
+
points,
|
|
6873
|
+
downtimeSegments
|
|
6874
|
+
};
|
|
6875
|
+
};
|
|
6685
6876
|
|
|
6686
6877
|
// src/lib/services/workspaceHealthService.ts
|
|
6687
6878
|
var DATA_PROCESSING_DELAY_MINUTES = 5;
|
|
@@ -6708,6 +6899,92 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6708
6899
|
setCache(key, data) {
|
|
6709
6900
|
this.cache.set(key, { data, timestamp: Date.now() });
|
|
6710
6901
|
}
|
|
6902
|
+
hasBackendUrl() {
|
|
6903
|
+
return Boolean(process.env.NEXT_PUBLIC_BACKEND_URL);
|
|
6904
|
+
}
|
|
6905
|
+
async fetchBackendWorkspaceUptimeSummaries(companyId, lineIds, date, shiftId, timezone) {
|
|
6906
|
+
if (!this.hasBackendUrl()) {
|
|
6907
|
+
return null;
|
|
6908
|
+
}
|
|
6909
|
+
const supabase = _getSupabaseInstance();
|
|
6910
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
6911
|
+
const params = new URLSearchParams({
|
|
6912
|
+
company_id: companyId,
|
|
6913
|
+
date,
|
|
6914
|
+
shift_id: String(shiftId),
|
|
6915
|
+
timezone
|
|
6916
|
+
});
|
|
6917
|
+
const filteredLineIds = (lineIds || []).filter(Boolean);
|
|
6918
|
+
if (filteredLineIds.length > 0) {
|
|
6919
|
+
params.set("line_ids", filteredLineIds.join(","));
|
|
6920
|
+
}
|
|
6921
|
+
const cacheKey = `backend-uptime-summary:${companyId}:${filteredLineIds.join(",") || "all"}:${date}:${shiftId}:${timezone}`;
|
|
6922
|
+
const cached = this.getFromCache(cacheKey);
|
|
6923
|
+
if (cached) {
|
|
6924
|
+
return cached;
|
|
6925
|
+
}
|
|
6926
|
+
try {
|
|
6927
|
+
const response = await fetchBackendJson(
|
|
6928
|
+
supabase,
|
|
6929
|
+
`/api/dashboard/workspace-uptime?${params.toString()}`,
|
|
6930
|
+
{
|
|
6931
|
+
retries: 1,
|
|
6932
|
+
timeoutMs: 15e3,
|
|
6933
|
+
sentry: {
|
|
6934
|
+
capture: true,
|
|
6935
|
+
surface: "workspace_health",
|
|
6936
|
+
route: "GET /api/dashboard/workspace-uptime",
|
|
6937
|
+
severity: "warning",
|
|
6938
|
+
quotaKey: "workspace-health-uptime-summary"
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
);
|
|
6942
|
+
const uptimeMap = /* @__PURE__ */ new Map();
|
|
6943
|
+
for (const entry of response.uptimes || []) {
|
|
6944
|
+
if (!entry.workspaceId || !entry.uptimeDetails) continue;
|
|
6945
|
+
uptimeMap.set(this.getUptimeMapKey(entry.lineId, entry.workspaceId), entry.uptimeDetails);
|
|
6946
|
+
}
|
|
6947
|
+
this.setCache(cacheKey, uptimeMap);
|
|
6948
|
+
return uptimeMap;
|
|
6949
|
+
} catch (error) {
|
|
6950
|
+
console.warn("[workspaceHealthService] Backend uptime summary failed; using frontend fallback", error);
|
|
6951
|
+
return null;
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
async fetchBackendWorkspaceUptimeTimeline(workspaceId, companyId, lineId, date, shiftId, timezone) {
|
|
6955
|
+
if (!this.hasBackendUrl()) {
|
|
6956
|
+
return null;
|
|
6957
|
+
}
|
|
6958
|
+
const supabase = _getSupabaseInstance();
|
|
6959
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
6960
|
+
const params = new URLSearchParams({
|
|
6961
|
+
company_id: companyId,
|
|
6962
|
+
line_id: lineId,
|
|
6963
|
+
date,
|
|
6964
|
+
shift_id: String(shiftId),
|
|
6965
|
+
timezone
|
|
6966
|
+
});
|
|
6967
|
+
try {
|
|
6968
|
+
return await fetchBackendJson(
|
|
6969
|
+
supabase,
|
|
6970
|
+
`/api/dashboard/workspace/${workspaceId}/uptime-timeline?${params.toString()}`,
|
|
6971
|
+
{
|
|
6972
|
+
retries: 1,
|
|
6973
|
+
timeoutMs: 15e3,
|
|
6974
|
+
sentry: {
|
|
6975
|
+
capture: true,
|
|
6976
|
+
surface: "workspace_health",
|
|
6977
|
+
route: "GET /api/dashboard/workspace/{workspace_id}/uptime-timeline",
|
|
6978
|
+
severity: "warning",
|
|
6979
|
+
quotaKey: "workspace-health-uptime-timeline"
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6982
|
+
);
|
|
6983
|
+
} catch (error) {
|
|
6984
|
+
console.warn("[workspaceHealthService] Backend uptime timeline failed; using frontend fallback", error);
|
|
6985
|
+
return null;
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6711
6988
|
getShiftTiming(timezone, shiftConfig) {
|
|
6712
6989
|
const currentShift = getCurrentShift(timezone, shiftConfig);
|
|
6713
6990
|
const { shiftId, date, shiftName, startTime, endTime } = currentShift;
|
|
@@ -6791,15 +7068,21 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6791
7068
|
pendingMinutes
|
|
6792
7069
|
};
|
|
6793
7070
|
}
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
)
|
|
7071
|
+
mergeOutputArrayValues(values) {
|
|
7072
|
+
const numericValues = values.filter(
|
|
7073
|
+
(value) => typeof value === "number" && Number.isFinite(value)
|
|
7074
|
+
);
|
|
7075
|
+
if (numericValues.length > 0) {
|
|
7076
|
+
const total = numericValues.reduce((sum, value) => sum + value, 0);
|
|
7077
|
+
return Number.isInteger(total) ? total : total;
|
|
7078
|
+
}
|
|
7079
|
+
if (values.length > 0 && values.every((value) => typeof value === "string" && value.trim().toLowerCase() === "x")) {
|
|
7080
|
+
return "x";
|
|
7081
|
+
}
|
|
7082
|
+
if (values.some((value) => value === null || value === void 0)) {
|
|
7083
|
+
return null;
|
|
7084
|
+
}
|
|
7085
|
+
return 0;
|
|
6803
7086
|
}
|
|
6804
7087
|
/**
|
|
6805
7088
|
* Group multi-SKU `performance_metrics` rows by workspace and merge their
|
|
@@ -6833,21 +7116,16 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6833
7116
|
const primary = group[0];
|
|
6834
7117
|
const mergedOutputHourly = mergeOutputHourly(
|
|
6835
7118
|
group.map((record) => ({
|
|
6836
|
-
output_hourly:
|
|
7119
|
+
output_hourly: normalizeOutputHourly(record.output_hourly || {})
|
|
6837
7120
|
}))
|
|
6838
7121
|
);
|
|
6839
7122
|
const arrays = group.map((record) => Array.isArray(record.output_array) ? record.output_array : []).filter((arr) => arr.length > 0);
|
|
6840
7123
|
let mergedArray = [];
|
|
6841
7124
|
if (arrays.length > 0) {
|
|
6842
7125
|
const maxLen = arrays.reduce((acc, arr) => Math.max(acc, arr.length), 0);
|
|
6843
|
-
mergedArray = new Array(maxLen).fill(0);
|
|
6844
7126
|
for (let i = 0; i < maxLen; i += 1) {
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
const value = i < arr.length ? arr[i] : 0;
|
|
6848
|
-
if (typeof value === "number" && Number.isFinite(value)) sum += value;
|
|
6849
|
-
}
|
|
6850
|
-
mergedArray[i] = sum;
|
|
7127
|
+
const values = arrays.filter((arr) => i < arr.length).map((arr) => arr[i]);
|
|
7128
|
+
mergedArray[i] = this.mergeOutputArrayValues(values);
|
|
6851
7129
|
}
|
|
6852
7130
|
}
|
|
6853
7131
|
return {
|
|
@@ -6857,45 +7135,392 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6857
7135
|
};
|
|
6858
7136
|
});
|
|
6859
7137
|
}
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
7138
|
+
parseShiftTime(value) {
|
|
7139
|
+
const [hourPart = "0", minutePart = "0"] = String(value || "00:00").split(":");
|
|
7140
|
+
const hour = Number.parseInt(hourPart, 10);
|
|
7141
|
+
const minute = Number.parseInt(minutePart, 10);
|
|
7142
|
+
return {
|
|
7143
|
+
hour: Number.isFinite(hour) ? hour : 0,
|
|
7144
|
+
minute: Number.isFinite(minute) ? minute : 0
|
|
7145
|
+
};
|
|
7146
|
+
}
|
|
7147
|
+
getShiftDurationMinutes(startTime, endTime) {
|
|
7148
|
+
const start = this.parseShiftTime(startTime);
|
|
7149
|
+
const end = this.parseShiftTime(endTime);
|
|
7150
|
+
let duration = end.hour * 60 + end.minute - (start.hour * 60 + start.minute);
|
|
7151
|
+
if (duration <= 0) {
|
|
7152
|
+
duration += 24 * 60;
|
|
6864
7153
|
}
|
|
6865
|
-
return
|
|
7154
|
+
return duration;
|
|
6866
7155
|
}
|
|
6867
|
-
|
|
6868
|
-
const
|
|
6869
|
-
const
|
|
6870
|
-
const
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
7156
|
+
resolveLightShiftWindow(shiftConfig, timezone, overrideDate, overrideShiftId, now4 = /* @__PURE__ */ new Date()) {
|
|
7157
|
+
const currentShift = getCurrentShift(timezone, shiftConfig);
|
|
7158
|
+
const queryDate = overrideDate ?? currentShift.date;
|
|
7159
|
+
const queryShiftId = overrideShiftId ?? currentShift.shiftId;
|
|
7160
|
+
const targetShiftConfig = shiftConfig?.shifts?.find((shift) => shift.shiftId === queryShiftId);
|
|
7161
|
+
const startTime = targetShiftConfig?.startTime || currentShift.startTime || "06:00";
|
|
7162
|
+
const endTime = targetShiftConfig?.endTime || currentShift.endTime || "18:00";
|
|
7163
|
+
const shiftLabel = targetShiftConfig?.shiftName || targetShiftConfig?.name || currentShift.shiftName || `Shift ${queryShiftId}`;
|
|
7164
|
+
const shiftStartDate = dateFnsTz.fromZonedTime(`${queryDate}T${startTime}:00`, timezone);
|
|
7165
|
+
const totalMinutes = this.getShiftDurationMinutes(startTime, endTime);
|
|
7166
|
+
const shiftEndDate = dateFns.addMinutes(shiftStartDate, totalMinutes);
|
|
7167
|
+
const boundedWindowEnd = new Date(Math.min(Math.max(now4.getTime(), shiftStartDate.getTime()), shiftEndDate.getTime()));
|
|
7168
|
+
const completedMinutes = Math.max(
|
|
7169
|
+
0,
|
|
7170
|
+
Math.min(totalMinutes, Math.floor((boundedWindowEnd.getTime() - shiftStartDate.getTime()) / 6e4))
|
|
7171
|
+
);
|
|
7172
|
+
return {
|
|
7173
|
+
shiftId: queryShiftId,
|
|
7174
|
+
shiftLabel,
|
|
7175
|
+
shiftStartDate,
|
|
7176
|
+
shiftEndDate,
|
|
7177
|
+
windowEndDate: boundedWindowEnd,
|
|
7178
|
+
totalMinutes,
|
|
7179
|
+
completedMinutes,
|
|
7180
|
+
pendingMinutes: Math.max(0, totalMinutes - completedMinutes)
|
|
7181
|
+
};
|
|
7182
|
+
}
|
|
7183
|
+
isLightStatus(status) {
|
|
7184
|
+
return status === "up" || status === "down" || status === "unknown";
|
|
7185
|
+
}
|
|
7186
|
+
secondsBetween(start, end) {
|
|
7187
|
+
return Math.max(0, Math.round((end.getTime() - start.getTime()) / 1e3));
|
|
7188
|
+
}
|
|
7189
|
+
roundPercent(value) {
|
|
7190
|
+
return Number(value.toFixed(1));
|
|
7191
|
+
}
|
|
7192
|
+
emptyLightTimeline(shiftWindow, timezone, bulbIp = null) {
|
|
7193
|
+
return {
|
|
7194
|
+
shiftId: shiftWindow.shiftId,
|
|
7195
|
+
shiftLabel: shiftWindow.shiftLabel,
|
|
7196
|
+
shiftStart: dateFnsTz.formatInTimeZone(shiftWindow.shiftStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7197
|
+
shiftEnd: dateFnsTz.formatInTimeZone(shiftWindow.shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7198
|
+
totalMinutes: shiftWindow.totalMinutes,
|
|
7199
|
+
completedMinutes: shiftWindow.completedMinutes,
|
|
7200
|
+
upSeconds: 0,
|
|
7201
|
+
downSeconds: 0,
|
|
7202
|
+
unknownSeconds: 0,
|
|
7203
|
+
totalObservedSeconds: 0,
|
|
7204
|
+
downtimeSeconds: 0,
|
|
7205
|
+
uptimePercentage: null,
|
|
7206
|
+
hasData: false,
|
|
7207
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7208
|
+
bulbIp,
|
|
7209
|
+
points: [],
|
|
7210
|
+
statusSegments: []
|
|
7211
|
+
};
|
|
7212
|
+
}
|
|
7213
|
+
async resolveWorkspaceLightConfigs(workspaces) {
|
|
7214
|
+
const supabase = _getSupabaseInstance();
|
|
7215
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
7216
|
+
const visibleWorkspaces = workspaces.filter((workspace) => workspace.workspace_id);
|
|
7217
|
+
if (!visibleWorkspaces.length) return /* @__PURE__ */ new Map();
|
|
7218
|
+
const workspaceIds = Array.from(new Set(visibleWorkspaces.map((workspace) => String(workspace.workspace_id))));
|
|
7219
|
+
const configs = /* @__PURE__ */ new Map();
|
|
7220
|
+
if (process.env.NEXT_PUBLIC_BACKEND_URL) {
|
|
7221
|
+
try {
|
|
7222
|
+
const backendConfigs = await workspaceService.getWorkspaceLightConfigs({ workspaceIds });
|
|
7223
|
+
for (const [workspaceId, config] of Object.entries(backendConfigs)) {
|
|
7224
|
+
if (!config?.bulb_ip) continue;
|
|
7225
|
+
configs.set(workspaceId, {
|
|
7226
|
+
healthWorkspaceId: workspaceId,
|
|
7227
|
+
lineId: config.line_id,
|
|
7228
|
+
workspaceDbId: config.workspace_id || workspaceId,
|
|
7229
|
+
bulbIp: String(config.bulb_ip)
|
|
7230
|
+
});
|
|
7231
|
+
}
|
|
7232
|
+
if (configs.size === workspaceIds.length) {
|
|
7233
|
+
return configs;
|
|
7234
|
+
}
|
|
7235
|
+
} catch (error) {
|
|
7236
|
+
console.error("[workspaceHealthService] Backend light config lookup failed, falling back to direct Supabase:", error);
|
|
6875
7237
|
}
|
|
6876
7238
|
}
|
|
6877
|
-
|
|
6878
|
-
|
|
7239
|
+
const unresolvedVisibleWorkspaces = visibleWorkspaces.filter(
|
|
7240
|
+
(workspace) => !configs.has(String(workspace.workspace_id))
|
|
7241
|
+
);
|
|
7242
|
+
if (!unresolvedVisibleWorkspaces.length) return configs;
|
|
7243
|
+
const fallbackWorkspaceIds = Array.from(new Set(unresolvedVisibleWorkspaces.map((workspace) => String(workspace.workspace_id))));
|
|
7244
|
+
const fallbackLineIds = Array.from(new Set(unresolvedVisibleWorkspaces.map((workspace) => workspace.line_id).filter(Boolean).map(String)));
|
|
7245
|
+
const fallbackVisiblePairs = new Set(unresolvedVisibleWorkspaces.map((workspace) => `${workspace.line_id || ""}::${workspace.workspace_id}`));
|
|
7246
|
+
let workspaceRows = [];
|
|
7247
|
+
try {
|
|
7248
|
+
let query = supabase.from("workspaces").select("id, workspace_id, line_id, enable_bulb_light").in("workspace_id", fallbackWorkspaceIds);
|
|
7249
|
+
if (fallbackLineIds.length) {
|
|
7250
|
+
query = query.in("line_id", fallbackLineIds);
|
|
7251
|
+
}
|
|
7252
|
+
const { data, error } = await query;
|
|
7253
|
+
if (error) throw error;
|
|
7254
|
+
workspaceRows = Array.isArray(data) ? data : [];
|
|
7255
|
+
} catch (error) {
|
|
7256
|
+
console.error("[workspaceHealthService] Error resolving light workspace rows:", error);
|
|
7257
|
+
return configs;
|
|
7258
|
+
}
|
|
7259
|
+
const dbIdToVisible = /* @__PURE__ */ new Map();
|
|
7260
|
+
for (const row of workspaceRows) {
|
|
7261
|
+
const healthWorkspaceId = row?.workspace_id ? String(row.workspace_id) : "";
|
|
7262
|
+
const lineId = row?.line_id ? String(row.line_id) : "";
|
|
7263
|
+
const dbId = row?.id ? String(row.id) : "";
|
|
7264
|
+
if (!healthWorkspaceId || !dbId) continue;
|
|
7265
|
+
if (fallbackLineIds.length && !fallbackVisiblePairs.has(`${lineId}::${healthWorkspaceId}`)) continue;
|
|
7266
|
+
if (row?.enable_bulb_light === false) continue;
|
|
7267
|
+
dbIdToVisible.set(dbId, { healthWorkspaceId, lineId });
|
|
7268
|
+
}
|
|
7269
|
+
const addMapping = (mapping, visible, workspaceDbId) => {
|
|
7270
|
+
const bulbIp = mapping?.bulb_ip ? String(mapping.bulb_ip).trim() : "";
|
|
7271
|
+
if (!bulbIp) return;
|
|
7272
|
+
configs.set(visible.healthWorkspaceId, {
|
|
7273
|
+
healthWorkspaceId: visible.healthWorkspaceId,
|
|
7274
|
+
lineId: visible.lineId,
|
|
7275
|
+
workspaceDbId,
|
|
7276
|
+
bulbIp
|
|
7277
|
+
});
|
|
7278
|
+
};
|
|
7279
|
+
const dbIds = Array.from(dbIdToVisible.keys());
|
|
7280
|
+
if (dbIds.length) {
|
|
7281
|
+
const { data, error } = await supabase.from("workspace_ip_mapping").select("workspace_id, bulb_ip").in("workspace_id", dbIds);
|
|
7282
|
+
if (!error && Array.isArray(data)) {
|
|
7283
|
+
for (const mapping of data) {
|
|
7284
|
+
const dbId = mapping?.workspace_id ? String(mapping.workspace_id) : "";
|
|
7285
|
+
const visible = dbIdToVisible.get(dbId);
|
|
7286
|
+
if (visible) addMapping(mapping, visible, dbId);
|
|
7287
|
+
}
|
|
7288
|
+
} else if (error) {
|
|
7289
|
+
console.error("[workspaceHealthService] Error resolving light IP mappings:", error);
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
const unresolvedVisibleIds = fallbackWorkspaceIds.filter((workspaceId) => !configs.has(workspaceId));
|
|
7293
|
+
if (unresolvedVisibleIds.length) {
|
|
7294
|
+
const visibleById = new Map(unresolvedVisibleWorkspaces.map((workspace) => [
|
|
7295
|
+
String(workspace.workspace_id),
|
|
7296
|
+
{ healthWorkspaceId: String(workspace.workspace_id), lineId: workspace.line_id }
|
|
7297
|
+
]));
|
|
7298
|
+
const { data, error } = await supabase.from("workspace_ip_mapping").select("workspace_id, bulb_ip").in("workspace_id", unresolvedVisibleIds);
|
|
7299
|
+
if (!error && Array.isArray(data)) {
|
|
7300
|
+
for (const mapping of data) {
|
|
7301
|
+
const workspaceId = mapping?.workspace_id ? String(mapping.workspace_id) : "";
|
|
7302
|
+
const visible = visibleById.get(workspaceId);
|
|
7303
|
+
if (visible) addMapping(mapping, visible, null);
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
return configs;
|
|
7308
|
+
}
|
|
7309
|
+
async fetchLightIntervals(bulbIps, windowStart, windowEnd) {
|
|
7310
|
+
if (!bulbIps.length || windowEnd <= windowStart) return [];
|
|
7311
|
+
const supabase = _getSupabaseInstance();
|
|
7312
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
7313
|
+
const { data, error } = await supabase.from("factory_light_status_intervals").select("workspace_id, bulb_ip, status, started_at, ended_at, last_seen_at, last_error").in("bulb_ip", bulbIps).lt("started_at", windowEnd.toISOString()).or(`ended_at.is.null,ended_at.gt.${windowStart.toISOString()}`);
|
|
7314
|
+
if (error) {
|
|
7315
|
+
console.error("[workspaceHealthService] Error fetching light status intervals:", error);
|
|
7316
|
+
throw error;
|
|
6879
7317
|
}
|
|
6880
|
-
return
|
|
7318
|
+
return Array.isArray(data) ? data : [];
|
|
6881
7319
|
}
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
7320
|
+
getLightIntervalEffectiveRange(interval, windowStart, windowEnd) {
|
|
7321
|
+
const startedAt = new Date(interval.started_at);
|
|
7322
|
+
const endedAt = interval.ended_at ? new Date(interval.ended_at) : windowEnd;
|
|
7323
|
+
const effectiveStart = new Date(Math.max(startedAt.getTime(), windowStart.getTime()));
|
|
7324
|
+
const effectiveEnd = new Date(Math.min(endedAt.getTime(), windowEnd.getTime()));
|
|
7325
|
+
if (effectiveEnd <= effectiveStart) return null;
|
|
7326
|
+
return {
|
|
7327
|
+
start: effectiveStart,
|
|
7328
|
+
end: effectiveEnd,
|
|
7329
|
+
seconds: this.secondsBetween(effectiveStart, effectiveEnd)
|
|
7330
|
+
};
|
|
7331
|
+
}
|
|
7332
|
+
summarizeLightIntervals(intervals, windowStart, windowEnd, now4) {
|
|
7333
|
+
let upSeconds = 0;
|
|
7334
|
+
let downSeconds = 0;
|
|
7335
|
+
let unknownSeconds = 0;
|
|
7336
|
+
let currentInterval = null;
|
|
7337
|
+
for (const interval of intervals) {
|
|
7338
|
+
if (!this.isLightStatus(interval.status)) continue;
|
|
7339
|
+
const range = this.getLightIntervalEffectiveRange(interval, windowStart, windowEnd);
|
|
7340
|
+
if (!range) continue;
|
|
7341
|
+
if (interval.status === "up") upSeconds += range.seconds;
|
|
7342
|
+
if (interval.status === "down") downSeconds += range.seconds;
|
|
7343
|
+
if (interval.status === "unknown") unknownSeconds += range.seconds;
|
|
7344
|
+
if (interval.ended_at == null) {
|
|
7345
|
+
currentInterval = interval;
|
|
7346
|
+
}
|
|
7347
|
+
}
|
|
7348
|
+
const totalObservedSeconds = upSeconds + downSeconds + unknownSeconds;
|
|
7349
|
+
return {
|
|
7350
|
+
hasLightConfig: true,
|
|
7351
|
+
bulbIp: null,
|
|
7352
|
+
currentStatus: this.isLightStatus(currentInterval?.status) ? currentInterval.status : null,
|
|
7353
|
+
currentStartedAt: currentInterval?.started_at || null,
|
|
7354
|
+
lastSeenAt: currentInterval?.last_seen_at || null,
|
|
7355
|
+
currentDurationSeconds: currentInterval?.started_at ? this.secondsBetween(new Date(currentInterval.started_at), now4) : null,
|
|
7356
|
+
lastError: currentInterval?.last_error || null,
|
|
7357
|
+
uptimePercent: totalObservedSeconds > 0 ? this.roundPercent(upSeconds / totalObservedSeconds * 100) : null,
|
|
7358
|
+
upSeconds,
|
|
7359
|
+
downSeconds,
|
|
7360
|
+
unknownSeconds,
|
|
7361
|
+
totalObservedSeconds
|
|
7362
|
+
};
|
|
7363
|
+
}
|
|
7364
|
+
async summarizeResolvedLightConfigs(configs, shiftWindow, now4) {
|
|
7365
|
+
if (!configs.size) return /* @__PURE__ */ new Map();
|
|
7366
|
+
const bulbIps = Array.from(new Set(Array.from(configs.values()).map((config) => config.bulbIp)));
|
|
7367
|
+
const intervals = await this.fetchLightIntervals(bulbIps, shiftWindow.shiftStartDate, shiftWindow.windowEndDate);
|
|
7368
|
+
const intervalsByBulb = /* @__PURE__ */ new Map();
|
|
7369
|
+
for (const interval of intervals) {
|
|
7370
|
+
const bulbIp = interval.bulb_ip ? String(interval.bulb_ip) : "";
|
|
7371
|
+
if (!bulbIp) continue;
|
|
7372
|
+
if (!intervalsByBulb.has(bulbIp)) intervalsByBulb.set(bulbIp, []);
|
|
7373
|
+
intervalsByBulb.get(bulbIp).push(interval);
|
|
7374
|
+
}
|
|
7375
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
7376
|
+
for (const config of configs.values()) {
|
|
7377
|
+
const summary = this.summarizeLightIntervals(
|
|
7378
|
+
intervalsByBulb.get(config.bulbIp) || [],
|
|
7379
|
+
shiftWindow.shiftStartDate,
|
|
7380
|
+
shiftWindow.windowEndDate,
|
|
7381
|
+
now4
|
|
7382
|
+
);
|
|
7383
|
+
summaries.set(config.healthWorkspaceId, {
|
|
7384
|
+
...summary,
|
|
7385
|
+
hasLightConfig: true,
|
|
7386
|
+
bulbIp: config.bulbIp
|
|
7387
|
+
});
|
|
6885
7388
|
}
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
7389
|
+
return summaries;
|
|
7390
|
+
}
|
|
7391
|
+
async getWorkspaceLightSummaries(workspaces, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId, now4 = /* @__PURE__ */ new Date(), lineShiftConfigs) {
|
|
7392
|
+
const dashboardConfig = _getDashboardConfigInstance();
|
|
7393
|
+
const shiftConfig = passedShiftConfig || dashboardConfig?.shiftConfig;
|
|
7394
|
+
const timezone = passedTimezone || shiftConfig?.timezone || dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
|
|
7395
|
+
if (lineShiftConfigs && lineShiftConfigs.size > 0) {
|
|
7396
|
+
const resolvedConfigs = await this.resolveWorkspaceLightConfigs(workspaces);
|
|
7397
|
+
if (!resolvedConfigs.size) return /* @__PURE__ */ new Map();
|
|
7398
|
+
const groupedByLine = /* @__PURE__ */ new Map();
|
|
7399
|
+
const fallbackConfigs = /* @__PURE__ */ new Map();
|
|
7400
|
+
for (const workspace of workspaces) {
|
|
7401
|
+
const workspaceId = workspace.workspace_id ? String(workspace.workspace_id) : "";
|
|
7402
|
+
const config = workspaceId ? resolvedConfigs.get(workspaceId) : null;
|
|
7403
|
+
if (!config) continue;
|
|
7404
|
+
const lineId = workspace.line_id ? String(workspace.line_id) : "";
|
|
7405
|
+
const lineShiftConfig = lineId ? lineShiftConfigs.get(lineId) : null;
|
|
7406
|
+
if (!lineId || !lineShiftConfig) {
|
|
7407
|
+
fallbackConfigs.set(config.healthWorkspaceId, config);
|
|
7408
|
+
continue;
|
|
7409
|
+
}
|
|
7410
|
+
if (!groupedByLine.has(lineId)) groupedByLine.set(lineId, /* @__PURE__ */ new Map());
|
|
7411
|
+
groupedByLine.get(lineId).set(config.healthWorkspaceId, config);
|
|
6893
7412
|
}
|
|
6894
|
-
|
|
6895
|
-
|
|
7413
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
7414
|
+
const summaryPromises = Array.from(groupedByLine.entries()).map(async ([lineId, lineConfigs]) => {
|
|
7415
|
+
const lineShiftWindow = this.resolveLightShiftWindow(
|
|
7416
|
+
lineShiftConfigs.get(lineId),
|
|
7417
|
+
timezone,
|
|
7418
|
+
overrideDate,
|
|
7419
|
+
overrideShiftId,
|
|
7420
|
+
now4
|
|
7421
|
+
);
|
|
7422
|
+
return this.summarizeResolvedLightConfigs(lineConfigs, lineShiftWindow, now4);
|
|
7423
|
+
});
|
|
7424
|
+
if (fallbackConfigs.size > 0) {
|
|
7425
|
+
const fallbackShiftWindow = this.resolveLightShiftWindow(
|
|
7426
|
+
shiftConfig,
|
|
7427
|
+
timezone,
|
|
7428
|
+
overrideDate,
|
|
7429
|
+
overrideShiftId,
|
|
7430
|
+
now4
|
|
7431
|
+
);
|
|
7432
|
+
summaryPromises.push(this.summarizeResolvedLightConfigs(fallbackConfigs, fallbackShiftWindow, now4));
|
|
6896
7433
|
}
|
|
7434
|
+
const resolvedSummaries = await Promise.all(summaryPromises);
|
|
7435
|
+
for (const groupSummaries of resolvedSummaries) {
|
|
7436
|
+
groupSummaries.forEach((summary, workspaceId) => summaries.set(workspaceId, summary));
|
|
7437
|
+
}
|
|
7438
|
+
return summaries;
|
|
6897
7439
|
}
|
|
6898
|
-
|
|
7440
|
+
const shiftWindow = this.resolveLightShiftWindow(shiftConfig, timezone, overrideDate, overrideShiftId, now4);
|
|
7441
|
+
const configs = await this.resolveWorkspaceLightConfigs(workspaces);
|
|
7442
|
+
return this.summarizeResolvedLightConfigs(configs, shiftWindow, now4);
|
|
7443
|
+
}
|
|
7444
|
+
async getWorkspaceLightTimeline(workspaceId, lineId, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId, now4 = /* @__PURE__ */ new Date()) {
|
|
7445
|
+
if (!workspaceId) {
|
|
7446
|
+
throw new Error("workspaceId is required to fetch light timeline");
|
|
7447
|
+
}
|
|
7448
|
+
const dashboardConfig = _getDashboardConfigInstance();
|
|
7449
|
+
const shiftConfig = passedShiftConfig || dashboardConfig?.shiftConfig;
|
|
7450
|
+
const timezone = passedTimezone || shiftConfig?.timezone || dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
|
|
7451
|
+
const shiftWindow = this.resolveLightShiftWindow(shiftConfig, timezone, overrideDate, overrideShiftId, now4);
|
|
7452
|
+
const configs = await this.resolveWorkspaceLightConfigs([{ workspace_id: workspaceId, line_id: lineId }]);
|
|
7453
|
+
const config = configs.get(workspaceId);
|
|
7454
|
+
if (!config) {
|
|
7455
|
+
return this.emptyLightTimeline(shiftWindow, timezone);
|
|
7456
|
+
}
|
|
7457
|
+
const intervals = await this.fetchLightIntervals([config.bulbIp], shiftWindow.shiftStartDate, shiftWindow.windowEndDate);
|
|
7458
|
+
const summary = this.summarizeLightIntervals(intervals, shiftWindow.shiftStartDate, shiftWindow.windowEndDate, now4);
|
|
7459
|
+
const hasData = summary.totalObservedSeconds > 0;
|
|
7460
|
+
const points = [];
|
|
7461
|
+
if (hasData) {
|
|
7462
|
+
for (let minuteIndex = 0; minuteIndex < shiftWindow.totalMinutes; minuteIndex++) {
|
|
7463
|
+
const minuteStart = dateFns.addMinutes(shiftWindow.shiftStartDate, minuteIndex);
|
|
7464
|
+
const minuteEnd = dateFns.addMinutes(minuteStart, 1);
|
|
7465
|
+
const timestamp = dateFnsTz.formatInTimeZone(minuteStart, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
7466
|
+
let status = "pending";
|
|
7467
|
+
if (minuteIndex < shiftWindow.completedMinutes) {
|
|
7468
|
+
const matchingInterval = intervals.find((interval) => {
|
|
7469
|
+
if (!this.isLightStatus(interval.status)) return false;
|
|
7470
|
+
const start = new Date(interval.started_at);
|
|
7471
|
+
const end = interval.ended_at ? new Date(interval.ended_at) : shiftWindow.windowEndDate;
|
|
7472
|
+
return start < minuteEnd && end > minuteStart;
|
|
7473
|
+
});
|
|
7474
|
+
status = this.isLightStatus(matchingInterval?.status) ? matchingInterval.status : "pending";
|
|
7475
|
+
}
|
|
7476
|
+
points.push({ minuteIndex, timestamp, status });
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
const statusSegments = intervals.filter((interval) => interval.status === "down" || interval.status === "unknown").reduce((segments, interval) => {
|
|
7480
|
+
const range = this.getLightIntervalEffectiveRange(interval, shiftWindow.shiftStartDate, shiftWindow.windowEndDate);
|
|
7481
|
+
if (!range || !this.isLightStatus(interval.status)) return segments;
|
|
7482
|
+
const startMinuteIndex = Math.max(0, Math.floor((range.start.getTime() - shiftWindow.shiftStartDate.getTime()) / 6e4));
|
|
7483
|
+
const endMinuteIndex = Math.min(
|
|
7484
|
+
shiftWindow.totalMinutes,
|
|
7485
|
+
Math.ceil((range.end.getTime() - shiftWindow.shiftStartDate.getTime()) / 6e4)
|
|
7486
|
+
);
|
|
7487
|
+
segments.push({
|
|
7488
|
+
status: interval.status,
|
|
7489
|
+
startMinuteIndex,
|
|
7490
|
+
endMinuteIndex,
|
|
7491
|
+
startTime: dateFnsTz.formatInTimeZone(range.start, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7492
|
+
endTime: dateFnsTz.formatInTimeZone(range.end, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7493
|
+
durationSeconds: range.seconds,
|
|
7494
|
+
durationMinutes: Math.ceil(range.seconds / 60),
|
|
7495
|
+
isCurrent: interval.ended_at == null,
|
|
7496
|
+
lastError: interval.last_error || null
|
|
7497
|
+
});
|
|
7498
|
+
return segments;
|
|
7499
|
+
}, []).sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
|
|
7500
|
+
return {
|
|
7501
|
+
shiftId: shiftWindow.shiftId,
|
|
7502
|
+
shiftLabel: shiftWindow.shiftLabel,
|
|
7503
|
+
shiftStart: dateFnsTz.formatInTimeZone(shiftWindow.shiftStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7504
|
+
shiftEnd: dateFnsTz.formatInTimeZone(shiftWindow.shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7505
|
+
totalMinutes: shiftWindow.totalMinutes,
|
|
7506
|
+
completedMinutes: shiftWindow.completedMinutes,
|
|
7507
|
+
upSeconds: summary.upSeconds,
|
|
7508
|
+
downSeconds: summary.downSeconds,
|
|
7509
|
+
unknownSeconds: summary.unknownSeconds,
|
|
7510
|
+
totalObservedSeconds: summary.totalObservedSeconds,
|
|
7511
|
+
downtimeSeconds: summary.downSeconds,
|
|
7512
|
+
uptimePercentage: summary.uptimePercent,
|
|
7513
|
+
hasData,
|
|
7514
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7515
|
+
bulbIp: config.bulbIp,
|
|
7516
|
+
currentStatus: summary.currentStatus,
|
|
7517
|
+
currentStartedAt: summary.currentStartedAt,
|
|
7518
|
+
currentDurationSeconds: summary.currentDurationSeconds,
|
|
7519
|
+
lastSeenAt: summary.lastSeenAt,
|
|
7520
|
+
lastError: summary.lastError,
|
|
7521
|
+
points,
|
|
7522
|
+
statusSegments
|
|
7523
|
+
};
|
|
6899
7524
|
}
|
|
6900
7525
|
async getWorkspaceHealthStatus(options = {}) {
|
|
6901
7526
|
const supabase = _getSupabaseInstance();
|
|
@@ -6930,7 +7555,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6930
7555
|
timezone,
|
|
6931
7556
|
options.lineShiftConfigs,
|
|
6932
7557
|
options.date,
|
|
6933
|
-
options.shiftId
|
|
7558
|
+
options.shiftId,
|
|
7559
|
+
options.lineId
|
|
6934
7560
|
);
|
|
6935
7561
|
} catch (error2) {
|
|
6936
7562
|
console.error("Error calculating uptime:", error2);
|
|
@@ -6995,6 +7621,27 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6995
7621
|
} catch (e) {
|
|
6996
7622
|
console.error("Error filtering workspaces:", e);
|
|
6997
7623
|
}
|
|
7624
|
+
if (filteredData.length > 0) {
|
|
7625
|
+
try {
|
|
7626
|
+
const lightSummaries = await this.getWorkspaceLightSummaries(
|
|
7627
|
+
filteredData,
|
|
7628
|
+
shiftConfig,
|
|
7629
|
+
timezone,
|
|
7630
|
+
options.date,
|
|
7631
|
+
options.shiftId,
|
|
7632
|
+
/* @__PURE__ */ new Date(),
|
|
7633
|
+
options.lineShiftConfigs
|
|
7634
|
+
);
|
|
7635
|
+
if (lightSummaries.size > 0) {
|
|
7636
|
+
filteredData = filteredData.map((workspace) => {
|
|
7637
|
+
const lightSummary = lightSummaries.get(workspace.workspace_id);
|
|
7638
|
+
return lightSummary ? { ...workspace, lightSummary } : workspace;
|
|
7639
|
+
});
|
|
7640
|
+
}
|
|
7641
|
+
} catch (error2) {
|
|
7642
|
+
console.error("Error calculating light uptime summaries:", error2);
|
|
7643
|
+
}
|
|
7644
|
+
}
|
|
6998
7645
|
if (options.status) {
|
|
6999
7646
|
filteredData = filteredData.filter((item) => item.status === options.status);
|
|
7000
7647
|
}
|
|
@@ -7023,7 +7670,7 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7023
7670
|
}
|
|
7024
7671
|
return filteredData;
|
|
7025
7672
|
}
|
|
7026
|
-
async getWorkspaceUptimeTimeline(workspaceId, companyId, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId) {
|
|
7673
|
+
async getWorkspaceUptimeTimeline(workspaceId, companyId, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId, lineId) {
|
|
7027
7674
|
if (!workspaceId) {
|
|
7028
7675
|
throw new Error("workspaceId is required to fetch uptime timeline");
|
|
7029
7676
|
}
|
|
@@ -7042,6 +7689,19 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7042
7689
|
const currentTiming = this.getShiftTiming(timezone, shiftConfig);
|
|
7043
7690
|
const queryDate = overrideDate ?? currentTiming.date;
|
|
7044
7691
|
const queryShiftId = overrideShiftId ?? currentTiming.shiftId;
|
|
7692
|
+
if (lineId) {
|
|
7693
|
+
const backendTimeline = await this.fetchBackendWorkspaceUptimeTimeline(
|
|
7694
|
+
workspaceId,
|
|
7695
|
+
companyId,
|
|
7696
|
+
lineId,
|
|
7697
|
+
queryDate,
|
|
7698
|
+
queryShiftId,
|
|
7699
|
+
timezone
|
|
7700
|
+
);
|
|
7701
|
+
if (backendTimeline) {
|
|
7702
|
+
return backendTimeline;
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7045
7705
|
let shiftStartDate;
|
|
7046
7706
|
let shiftEndDate;
|
|
7047
7707
|
let totalMinutes;
|
|
@@ -7090,18 +7750,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7090
7750
|
}
|
|
7091
7751
|
const mergedRecords = this.mergeRecordsByWorkspace(Array.isArray(data) ? data : []);
|
|
7092
7752
|
const record = mergedRecords.length > 0 ? mergedRecords[0] : null;
|
|
7093
|
-
const outputHourly =
|
|
7753
|
+
const outputHourly = normalizeOutputHourly(record?.output_hourly || {});
|
|
7094
7754
|
const outputArray = Array.isArray(record?.output_array) ? record.output_array : [];
|
|
7095
|
-
const
|
|
7096
|
-
|
|
7097
|
-
|
|
7098
|
-
|
|
7099
|
-
|
|
7100
|
-
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
)
|
|
7104
|
-
if (!hasData) {
|
|
7755
|
+
const uptime = record ? computeWorkspaceUptime({
|
|
7756
|
+
totalMinutes,
|
|
7757
|
+
completedMinutes,
|
|
7758
|
+
shiftStartDate,
|
|
7759
|
+
outputHourly,
|
|
7760
|
+
outputArray,
|
|
7761
|
+
timezone
|
|
7762
|
+
}) : null;
|
|
7763
|
+
if (!uptime?.hasData) {
|
|
7105
7764
|
return {
|
|
7106
7765
|
shiftId: queryShiftId,
|
|
7107
7766
|
shiftLabel,
|
|
@@ -7119,80 +7778,6 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7119
7778
|
downtimeSegments: []
|
|
7120
7779
|
};
|
|
7121
7780
|
}
|
|
7122
|
-
const points = [];
|
|
7123
|
-
let uptimeMinutes = 0;
|
|
7124
|
-
let downtimeMinutes = 0;
|
|
7125
|
-
const MIN_DOWNTIME_MINUTES = 2;
|
|
7126
|
-
for (let minuteIndex = 0; minuteIndex < totalMinutes; minuteIndex++) {
|
|
7127
|
-
const minuteDate = dateFns.addMinutes(shiftStartDate, minuteIndex);
|
|
7128
|
-
const timestamp = dateFnsTz.formatInTimeZone(minuteDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
7129
|
-
let status;
|
|
7130
|
-
if (minuteIndex < completedMinutes) {
|
|
7131
|
-
status = this.deriveStatusForMinute(
|
|
7132
|
-
minuteIndex,
|
|
7133
|
-
minuteDate,
|
|
7134
|
-
outputHourly,
|
|
7135
|
-
outputArray,
|
|
7136
|
-
timezone
|
|
7137
|
-
);
|
|
7138
|
-
if (status === "up") {
|
|
7139
|
-
uptimeMinutes += 1;
|
|
7140
|
-
} else {
|
|
7141
|
-
downtimeMinutes += 1;
|
|
7142
|
-
}
|
|
7143
|
-
} else {
|
|
7144
|
-
status = "pending";
|
|
7145
|
-
}
|
|
7146
|
-
points.push({
|
|
7147
|
-
minuteIndex,
|
|
7148
|
-
timestamp,
|
|
7149
|
-
status
|
|
7150
|
-
});
|
|
7151
|
-
}
|
|
7152
|
-
const downtimeSegments = [];
|
|
7153
|
-
let currentSegmentStart = null;
|
|
7154
|
-
const pushSegment = (startIndex, endIndex) => {
|
|
7155
|
-
if (endIndex <= startIndex) return;
|
|
7156
|
-
const segmentStartDate = dateFns.addMinutes(shiftStartDate, startIndex);
|
|
7157
|
-
const segmentEndDate = dateFns.addMinutes(shiftStartDate, endIndex);
|
|
7158
|
-
downtimeSegments.push({
|
|
7159
|
-
startMinuteIndex: startIndex,
|
|
7160
|
-
endMinuteIndex: endIndex,
|
|
7161
|
-
startTime: dateFnsTz.formatInTimeZone(segmentStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7162
|
-
endTime: dateFnsTz.formatInTimeZone(segmentEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7163
|
-
durationMinutes: endIndex - startIndex
|
|
7164
|
-
});
|
|
7165
|
-
};
|
|
7166
|
-
for (let i = 0; i < completedMinutes; i++) {
|
|
7167
|
-
const point = points[i];
|
|
7168
|
-
if (point.status === "down") {
|
|
7169
|
-
if (currentSegmentStart === null) {
|
|
7170
|
-
currentSegmentStart = i;
|
|
7171
|
-
}
|
|
7172
|
-
} else if (currentSegmentStart !== null) {
|
|
7173
|
-
pushSegment(currentSegmentStart, i);
|
|
7174
|
-
currentSegmentStart = null;
|
|
7175
|
-
}
|
|
7176
|
-
}
|
|
7177
|
-
if (currentSegmentStart !== null) {
|
|
7178
|
-
pushSegment(currentSegmentStart, completedMinutes);
|
|
7179
|
-
}
|
|
7180
|
-
const filteredSegments = [];
|
|
7181
|
-
downtimeSegments.forEach((segment) => {
|
|
7182
|
-
if (segment.durationMinutes >= MIN_DOWNTIME_MINUTES) {
|
|
7183
|
-
filteredSegments.push(segment);
|
|
7184
|
-
} else {
|
|
7185
|
-
for (let i = segment.startMinuteIndex; i < segment.endMinuteIndex; i++) {
|
|
7186
|
-
if (points[i] && points[i].status === "down") {
|
|
7187
|
-
points[i].status = "up";
|
|
7188
|
-
downtimeMinutes = Math.max(0, downtimeMinutes - 1);
|
|
7189
|
-
uptimeMinutes += 1;
|
|
7190
|
-
}
|
|
7191
|
-
}
|
|
7192
|
-
}
|
|
7193
|
-
});
|
|
7194
|
-
const completedWindow = Math.max(1, uptimeMinutes + downtimeMinutes);
|
|
7195
|
-
const uptimePercentage = completedMinutes > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
7196
7781
|
return {
|
|
7197
7782
|
shiftId: queryShiftId,
|
|
7198
7783
|
shiftLabel,
|
|
@@ -7200,14 +7785,14 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7200
7785
|
shiftEnd: dateFnsTz.formatInTimeZone(shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7201
7786
|
totalMinutes,
|
|
7202
7787
|
completedMinutes,
|
|
7203
|
-
uptimeMinutes,
|
|
7204
|
-
downtimeMinutes,
|
|
7788
|
+
uptimeMinutes: uptime.uptimeMinutes,
|
|
7789
|
+
downtimeMinutes: uptime.downtimeMinutes,
|
|
7205
7790
|
pendingMinutes,
|
|
7206
|
-
uptimePercentage,
|
|
7791
|
+
uptimePercentage: uptime.uptimePercentage,
|
|
7207
7792
|
hasData: true,
|
|
7208
7793
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7209
|
-
points,
|
|
7210
|
-
downtimeSegments:
|
|
7794
|
+
points: uptime.points,
|
|
7795
|
+
downtimeSegments: uptime.downtimeSegments
|
|
7211
7796
|
};
|
|
7212
7797
|
}
|
|
7213
7798
|
async getWorkspaceHealthById(workspaceId) {
|
|
@@ -7319,7 +7904,7 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7319
7904
|
getUptimeMapKey(lineId, workspaceId) {
|
|
7320
7905
|
return lineId ? `${lineId}::${workspaceId}` : workspaceId;
|
|
7321
7906
|
}
|
|
7322
|
-
async calculateWorkspaceUptime(companyId, passedShiftConfig, timezone, lineShiftConfigs, overrideDate, overrideShiftId) {
|
|
7907
|
+
async calculateWorkspaceUptime(companyId, passedShiftConfig, timezone, lineShiftConfigs, overrideDate, overrideShiftId, lineId) {
|
|
7323
7908
|
const supabase = _getSupabaseInstance();
|
|
7324
7909
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
7325
7910
|
const dashboardConfig = _getDashboardConfigInstance();
|
|
@@ -7341,6 +7926,16 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7341
7926
|
const currentTiming = this.getShiftTiming(effectiveTimezone, shiftConfig);
|
|
7342
7927
|
const queryDate = overrideDate ?? currentTiming.date;
|
|
7343
7928
|
const queryShiftId = overrideShiftId ?? currentTiming.shiftId;
|
|
7929
|
+
const backendUptime = await this.fetchBackendWorkspaceUptimeSummaries(
|
|
7930
|
+
companyId,
|
|
7931
|
+
lineId ? [lineId] : void 0,
|
|
7932
|
+
queryDate,
|
|
7933
|
+
queryShiftId,
|
|
7934
|
+
effectiveTimezone
|
|
7935
|
+
);
|
|
7936
|
+
if (backendUptime) {
|
|
7937
|
+
return backendUptime;
|
|
7938
|
+
}
|
|
7344
7939
|
let shiftStartDate = currentTiming.shiftStartDate;
|
|
7345
7940
|
let completedMinutes = currentTiming.completedMinutes;
|
|
7346
7941
|
const isHistoricalDate = overrideDate && overrideDate !== currentTiming.date;
|
|
@@ -7364,50 +7959,21 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7364
7959
|
const uptimeMap = /* @__PURE__ */ new Map();
|
|
7365
7960
|
const mergedRecords = this.mergeRecordsByWorkspace(queryData || []);
|
|
7366
7961
|
for (const record of mergedRecords) {
|
|
7367
|
-
const outputHourly =
|
|
7962
|
+
const outputHourly = normalizeOutputHourly(record.output_hourly || {});
|
|
7368
7963
|
const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
minuteDate,
|
|
7378
|
-
outputHourly,
|
|
7379
|
-
outputArray,
|
|
7380
|
-
effectiveTimezone
|
|
7381
|
-
);
|
|
7382
|
-
if (status === "down") {
|
|
7383
|
-
currentDownRun += 1;
|
|
7384
|
-
} else {
|
|
7385
|
-
if (currentDownRun > 0) {
|
|
7386
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7387
|
-
downtimeMinutes += currentDownRun;
|
|
7388
|
-
} else {
|
|
7389
|
-
uptimeMinutes += currentDownRun;
|
|
7390
|
-
}
|
|
7391
|
-
currentDownRun = 0;
|
|
7392
|
-
}
|
|
7393
|
-
uptimeMinutes += 1;
|
|
7394
|
-
}
|
|
7395
|
-
}
|
|
7396
|
-
if (currentDownRun > 0) {
|
|
7397
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7398
|
-
downtimeMinutes += currentDownRun;
|
|
7399
|
-
} else {
|
|
7400
|
-
uptimeMinutes += currentDownRun;
|
|
7401
|
-
}
|
|
7402
|
-
currentDownRun = 0;
|
|
7403
|
-
}
|
|
7404
|
-
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
7405
|
-
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
7964
|
+
const uptime = computeWorkspaceUptime({
|
|
7965
|
+
totalMinutes: completedMinutes,
|
|
7966
|
+
completedMinutes,
|
|
7967
|
+
shiftStartDate,
|
|
7968
|
+
outputHourly,
|
|
7969
|
+
outputArray,
|
|
7970
|
+
timezone: effectiveTimezone
|
|
7971
|
+
});
|
|
7406
7972
|
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
7407
7973
|
uptimeMap.set(mapKey, {
|
|
7408
7974
|
expectedMinutes: completedMinutes,
|
|
7409
|
-
actualMinutes: uptimeMinutes,
|
|
7410
|
-
percentage,
|
|
7975
|
+
actualMinutes: uptime.uptimeMinutes,
|
|
7976
|
+
percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100,
|
|
7411
7977
|
lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
|
|
7412
7978
|
});
|
|
7413
7979
|
}
|
|
@@ -7460,6 +8026,26 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7460
8026
|
}
|
|
7461
8027
|
uniqueQueries.get(key).lineConfigs.push({ lineId, shiftStartDate, completedMinutes });
|
|
7462
8028
|
});
|
|
8029
|
+
const backendUptimeMap = /* @__PURE__ */ new Map();
|
|
8030
|
+
let backendAvailable = true;
|
|
8031
|
+
for (const { date, shiftId, lineConfigs } of uniqueQueries.values()) {
|
|
8032
|
+
const lineIds = Array.from(new Set(lineConfigs.map((lc) => lc.lineId).filter(Boolean)));
|
|
8033
|
+
const result = await this.fetchBackendWorkspaceUptimeSummaries(
|
|
8034
|
+
companyId,
|
|
8035
|
+
lineIds,
|
|
8036
|
+
date,
|
|
8037
|
+
shiftId,
|
|
8038
|
+
timezone
|
|
8039
|
+
);
|
|
8040
|
+
if (result === null) {
|
|
8041
|
+
backendAvailable = false;
|
|
8042
|
+
break;
|
|
8043
|
+
}
|
|
8044
|
+
result.forEach((value, key) => backendUptimeMap.set(key, value));
|
|
8045
|
+
}
|
|
8046
|
+
if (backendAvailable) {
|
|
8047
|
+
return backendUptimeMap;
|
|
8048
|
+
}
|
|
7463
8049
|
console.log(`[calculateWorkspaceUptimeMultiLine] Querying ${uniqueQueries.size} unique date/shift combinations for ${lineShiftConfigs.size} lines`);
|
|
7464
8050
|
uniqueQueries.forEach((queryConfig, key) => {
|
|
7465
8051
|
console.log(`[calculateWorkspaceUptimeMultiLine] Query batch ${key}:`, {
|
|
@@ -7511,49 +8097,21 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7511
8097
|
continue;
|
|
7512
8098
|
}
|
|
7513
8099
|
const { shiftStartDate, completedMinutes } = lineConfig;
|
|
7514
|
-
const outputHourly =
|
|
8100
|
+
const outputHourly = normalizeOutputHourly(record.output_hourly || {});
|
|
7515
8101
|
const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
minuteDate,
|
|
7525
|
-
outputHourly,
|
|
7526
|
-
outputArray,
|
|
7527
|
-
timezone
|
|
7528
|
-
);
|
|
7529
|
-
if (status === "down") {
|
|
7530
|
-
currentDownRun += 1;
|
|
7531
|
-
} else {
|
|
7532
|
-
if (currentDownRun > 0) {
|
|
7533
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7534
|
-
downtimeMinutes += currentDownRun;
|
|
7535
|
-
} else {
|
|
7536
|
-
uptimeMinutes += currentDownRun;
|
|
7537
|
-
}
|
|
7538
|
-
currentDownRun = 0;
|
|
7539
|
-
}
|
|
7540
|
-
uptimeMinutes += 1;
|
|
7541
|
-
}
|
|
7542
|
-
}
|
|
7543
|
-
if (currentDownRun > 0) {
|
|
7544
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7545
|
-
downtimeMinutes += currentDownRun;
|
|
7546
|
-
} else {
|
|
7547
|
-
uptimeMinutes += currentDownRun;
|
|
7548
|
-
}
|
|
7549
|
-
}
|
|
7550
|
-
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
7551
|
-
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
8102
|
+
const uptime = computeWorkspaceUptime({
|
|
8103
|
+
totalMinutes: completedMinutes,
|
|
8104
|
+
completedMinutes,
|
|
8105
|
+
shiftStartDate,
|
|
8106
|
+
outputHourly,
|
|
8107
|
+
outputArray,
|
|
8108
|
+
timezone
|
|
8109
|
+
});
|
|
7552
8110
|
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
7553
8111
|
uptimeMap.set(mapKey, {
|
|
7554
8112
|
expectedMinutes: completedMinutes,
|
|
7555
|
-
actualMinutes: uptimeMinutes,
|
|
7556
|
-
percentage,
|
|
8113
|
+
actualMinutes: uptime.uptimeMinutes,
|
|
8114
|
+
percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100,
|
|
7557
8115
|
lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
|
|
7558
8116
|
});
|
|
7559
8117
|
console.log(`[calculateWorkspaceUptimeMultiLine] Storing uptime:`, {
|
|
@@ -7562,8 +8120,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7562
8120
|
workspaceId: record.workspace_id,
|
|
7563
8121
|
workspaceDisplayName: record.workspace_display_name,
|
|
7564
8122
|
expectedMinutes: completedMinutes,
|
|
7565
|
-
actualMinutes: uptimeMinutes,
|
|
7566
|
-
percentage
|
|
8123
|
+
actualMinutes: uptime.uptimeMinutes,
|
|
8124
|
+
percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100
|
|
7567
8125
|
});
|
|
7568
8126
|
}
|
|
7569
8127
|
}
|
|
@@ -20804,7 +21362,8 @@ var useWorkspaceUptimeTimeline = (options) => {
|
|
|
20804
21362
|
effectiveShiftConfig,
|
|
20805
21363
|
effectiveTimezone,
|
|
20806
21364
|
overrideDate,
|
|
20807
|
-
overrideShiftId
|
|
21365
|
+
overrideShiftId,
|
|
21366
|
+
lineId
|
|
20808
21367
|
);
|
|
20809
21368
|
setTimeline(data);
|
|
20810
21369
|
} catch (err) {
|
|
@@ -20814,7 +21373,89 @@ var useWorkspaceUptimeTimeline = (options) => {
|
|
|
20814
21373
|
setLoading(false);
|
|
20815
21374
|
isFetchingRef.current = false;
|
|
20816
21375
|
}
|
|
20817
|
-
}, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
|
|
21376
|
+
}, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId, lineId]);
|
|
21377
|
+
React125.useEffect(() => {
|
|
21378
|
+
fetchTimeline();
|
|
21379
|
+
}, [fetchTimeline]);
|
|
21380
|
+
React125.useEffect(() => {
|
|
21381
|
+
if (!refreshInterval || refreshInterval <= 0 || !enabled) {
|
|
21382
|
+
return;
|
|
21383
|
+
}
|
|
21384
|
+
intervalRef.current = setInterval(() => {
|
|
21385
|
+
fetchTimeline();
|
|
21386
|
+
}, refreshInterval);
|
|
21387
|
+
return () => {
|
|
21388
|
+
if (intervalRef.current) {
|
|
21389
|
+
clearInterval(intervalRef.current);
|
|
21390
|
+
}
|
|
21391
|
+
};
|
|
21392
|
+
}, [refreshInterval, enabled, fetchTimeline]);
|
|
21393
|
+
return {
|
|
21394
|
+
timeline,
|
|
21395
|
+
loading: loading || shiftConfigPending,
|
|
21396
|
+
error,
|
|
21397
|
+
refetch: fetchTimeline
|
|
21398
|
+
};
|
|
21399
|
+
};
|
|
21400
|
+
var useWorkspaceLightTimeline = (options) => {
|
|
21401
|
+
const {
|
|
21402
|
+
workspaceId,
|
|
21403
|
+
lineId,
|
|
21404
|
+
enabled = true,
|
|
21405
|
+
refreshInterval,
|
|
21406
|
+
shiftConfig: passedShiftConfig,
|
|
21407
|
+
timezone: passedTimezone,
|
|
21408
|
+
date: overrideDate,
|
|
21409
|
+
shiftId: overrideShiftId
|
|
21410
|
+
} = options;
|
|
21411
|
+
const { shiftConfig: dynamicShiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
|
|
21412
|
+
const appTimezone = useAppTimezone();
|
|
21413
|
+
const shiftConfigPending = !passedShiftConfig && (!lineId || isShiftConfigLoading || !dynamicShiftConfig);
|
|
21414
|
+
const effectiveShiftConfig = passedShiftConfig ?? (shiftConfigPending ? void 0 : dynamicShiftConfig);
|
|
21415
|
+
const effectiveTimezone = passedTimezone || appTimezone || effectiveShiftConfig?.timezone || "UTC";
|
|
21416
|
+
const [timeline, setTimeline] = React125.useState(null);
|
|
21417
|
+
const [loading, setLoading] = React125.useState(false);
|
|
21418
|
+
const [error, setError] = React125.useState(null);
|
|
21419
|
+
const isFetchingRef = React125.useRef(false);
|
|
21420
|
+
const intervalRef = React125.useRef(null);
|
|
21421
|
+
React125.useEffect(() => {
|
|
21422
|
+
setTimeline(null);
|
|
21423
|
+
setError(null);
|
|
21424
|
+
setLoading(enabled && Boolean(workspaceId));
|
|
21425
|
+
}, [workspaceId, lineId, enabled]);
|
|
21426
|
+
const fetchTimeline = React125.useCallback(async () => {
|
|
21427
|
+
if (!enabled) return;
|
|
21428
|
+
if (shiftConfigPending) {
|
|
21429
|
+
setLoading(true);
|
|
21430
|
+
return;
|
|
21431
|
+
}
|
|
21432
|
+
if (!effectiveShiftConfig || !workspaceId) {
|
|
21433
|
+
setLoading(false);
|
|
21434
|
+
setTimeline(null);
|
|
21435
|
+
return;
|
|
21436
|
+
}
|
|
21437
|
+
if (isFetchingRef.current) return;
|
|
21438
|
+
try {
|
|
21439
|
+
isFetchingRef.current = true;
|
|
21440
|
+
setLoading(true);
|
|
21441
|
+
setError(null);
|
|
21442
|
+
const data = await workspaceHealthService.getWorkspaceLightTimeline(
|
|
21443
|
+
workspaceId,
|
|
21444
|
+
lineId,
|
|
21445
|
+
effectiveShiftConfig,
|
|
21446
|
+
effectiveTimezone,
|
|
21447
|
+
overrideDate,
|
|
21448
|
+
overrideShiftId
|
|
21449
|
+
);
|
|
21450
|
+
setTimeline(data);
|
|
21451
|
+
} catch (err) {
|
|
21452
|
+
console.error("[useWorkspaceLightTimeline] Failed to fetch light timeline:", err);
|
|
21453
|
+
setError({ message: err?.message || "Failed to load light timeline", code: err?.code || "FETCH_ERROR" });
|
|
21454
|
+
} finally {
|
|
21455
|
+
setLoading(false);
|
|
21456
|
+
isFetchingRef.current = false;
|
|
21457
|
+
}
|
|
21458
|
+
}, [enabled, workspaceId, lineId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
|
|
20818
21459
|
React125.useEffect(() => {
|
|
20819
21460
|
fetchTimeline();
|
|
20820
21461
|
}, [fetchTimeline]);
|
|
@@ -58241,7 +58882,14 @@ var WorkspaceHealthCard = ({
|
|
|
58241
58882
|
event.stopPropagation();
|
|
58242
58883
|
event.preventDefault();
|
|
58243
58884
|
if (onViewDetails) {
|
|
58244
|
-
onViewDetails(workspace);
|
|
58885
|
+
onViewDetails(workspace, "camera", "card_button");
|
|
58886
|
+
}
|
|
58887
|
+
};
|
|
58888
|
+
const handleLightChipClick = (event) => {
|
|
58889
|
+
event.stopPropagation();
|
|
58890
|
+
event.preventDefault();
|
|
58891
|
+
if (onViewDetails) {
|
|
58892
|
+
onViewDetails(workspace, "light", "light_chip");
|
|
58245
58893
|
}
|
|
58246
58894
|
};
|
|
58247
58895
|
const handleKeyDown = (event) => {
|
|
@@ -58319,6 +58967,22 @@ var WorkspaceHealthCard = ({
|
|
|
58319
58967
|
};
|
|
58320
58968
|
};
|
|
58321
58969
|
const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
|
|
58970
|
+
const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary.bulbIp);
|
|
58971
|
+
const lightStatus = workspace.lightSummary?.currentStatus || null;
|
|
58972
|
+
const lightChipLabel = (() => {
|
|
58973
|
+
if (!workspace.lightSummary) return "Light --";
|
|
58974
|
+
if (lightStatus === "down") return "Light offline";
|
|
58975
|
+
if (lightStatus === "unknown") return "Light unknown";
|
|
58976
|
+
if (typeof workspace.lightSummary.uptimePercent === "number") {
|
|
58977
|
+
return `Light ${workspace.lightSummary.uptimePercent.toFixed(1)}%`;
|
|
58978
|
+
}
|
|
58979
|
+
if (lightStatus === "up") return "Light operational";
|
|
58980
|
+
return "Light --";
|
|
58981
|
+
})();
|
|
58982
|
+
const lightChipClassName = clsx(
|
|
58983
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-semibold transition-all",
|
|
58984
|
+
lightStatus === "down" ? "border-rose-200 bg-rose-50 text-rose-700 hover:bg-rose-100" : lightStatus === "unknown" ? "border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100" : "border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100"
|
|
58985
|
+
);
|
|
58322
58986
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
58323
58987
|
Card2,
|
|
58324
58988
|
{
|
|
@@ -58388,6 +59052,20 @@ var WorkspaceHealthCard = ({
|
|
|
58388
59052
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Camera IP:" }),
|
|
58389
59053
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "N/A" })
|
|
58390
59054
|
] }),
|
|
59055
|
+
hasLightConfig && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
59056
|
+
"button",
|
|
59057
|
+
{
|
|
59058
|
+
type: "button",
|
|
59059
|
+
onClick: handleLightChipClick,
|
|
59060
|
+
className: lightChipClassName,
|
|
59061
|
+
"aria-label": `Open light timeline for ${workspace.workspace_display_name || workspace.workspace_id}`,
|
|
59062
|
+
title: workspace.lightSummary?.bulbIp ? `Bulb IP ${workspace.lightSummary.bulbIp}` : "Light timeline",
|
|
59063
|
+
children: [
|
|
59064
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Lightbulb, { className: "h-3.5 w-3.5" }),
|
|
59065
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: lightChipLabel })
|
|
59066
|
+
]
|
|
59067
|
+
}
|
|
59068
|
+
),
|
|
58391
59069
|
/* @__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: [
|
|
58392
59070
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: downtimeConfig.label }),
|
|
58393
59071
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
|
|
@@ -58457,7 +59135,7 @@ var CompactWorkspaceHealthCard = ({
|
|
|
58457
59135
|
event.stopPropagation();
|
|
58458
59136
|
event.preventDefault();
|
|
58459
59137
|
if (onViewDetails) {
|
|
58460
|
-
onViewDetails(workspace);
|
|
59138
|
+
onViewDetails(workspace, "camera", "card_button");
|
|
58461
59139
|
}
|
|
58462
59140
|
};
|
|
58463
59141
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -84186,11 +84864,13 @@ var useWorkspaceHealth = (options) => {
|
|
|
84186
84864
|
var STATUS_COLORS = {
|
|
84187
84865
|
up: "bg-emerald-500",
|
|
84188
84866
|
down: "bg-rose-500",
|
|
84867
|
+
unknown: "bg-amber-400",
|
|
84189
84868
|
pending: "bg-gray-200"
|
|
84190
84869
|
};
|
|
84191
84870
|
var STATUS_TITLES = {
|
|
84192
84871
|
up: "Uptime",
|
|
84193
84872
|
down: "Downtime",
|
|
84873
|
+
unknown: "Unknown",
|
|
84194
84874
|
pending: "Pending"
|
|
84195
84875
|
};
|
|
84196
84876
|
var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
|
|
@@ -84233,7 +84913,9 @@ var UptimeTimelineStrip = ({
|
|
|
84233
84913
|
timezone,
|
|
84234
84914
|
className = "",
|
|
84235
84915
|
uptimePercentage = null,
|
|
84236
|
-
downtimeMinutes = 0
|
|
84916
|
+
downtimeMinutes = 0,
|
|
84917
|
+
metricLabel = "uptime",
|
|
84918
|
+
summaryText
|
|
84237
84919
|
}) => {
|
|
84238
84920
|
const segments = React125.useMemo(() => {
|
|
84239
84921
|
if (!points.length || totalMinutes <= 0) return [];
|
|
@@ -84297,9 +84979,11 @@ var UptimeTimelineStrip = ({
|
|
|
84297
84979
|
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." });
|
|
84298
84980
|
}
|
|
84299
84981
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative w-full ${className}`, children: [
|
|
84300
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-900 font-semibold", children: [
|
|
84982
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: summaryText ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-900 font-semibold", children: summaryText }) : typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-900 font-semibold", children: [
|
|
84301
84983
|
uptimePercentage.toFixed(1),
|
|
84302
|
-
" %
|
|
84984
|
+
" % ",
|
|
84985
|
+
metricLabel,
|
|
84986
|
+
" ",
|
|
84303
84987
|
downtimeMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
84304
84988
|
"(",
|
|
84305
84989
|
formatDowntimeLabel(downtimeMinutes),
|
|
@@ -84367,6 +85051,27 @@ var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
|
|
|
84367
85051
|
const label = formatDuration4(minutes);
|
|
84368
85052
|
return includeSuffix ? `${label} down` : label;
|
|
84369
85053
|
};
|
|
85054
|
+
var formatSecondsDuration = (seconds) => {
|
|
85055
|
+
if (!seconds || seconds <= 0) return "0 min";
|
|
85056
|
+
return formatDuration4(Math.max(1, Math.ceil(seconds / 60)));
|
|
85057
|
+
};
|
|
85058
|
+
var getLightStatusLabel = (status) => {
|
|
85059
|
+
if (status === "up") return "operational";
|
|
85060
|
+
if (status === "down") return "offline";
|
|
85061
|
+
if (status === "unknown") return "unknown";
|
|
85062
|
+
return "status unavailable";
|
|
85063
|
+
};
|
|
85064
|
+
var getLightDurationPrefix = (status) => {
|
|
85065
|
+
if (status === "up") return "Operational";
|
|
85066
|
+
if (status === "down") return "Offline";
|
|
85067
|
+
if (status === "unknown") return "Unknown";
|
|
85068
|
+
return "Current";
|
|
85069
|
+
};
|
|
85070
|
+
var formatLightError = (error) => {
|
|
85071
|
+
if (!error) return null;
|
|
85072
|
+
const cleaned = error.replace(/\s*\(after retry\)\s*$/i, "").trim();
|
|
85073
|
+
return cleaned || null;
|
|
85074
|
+
};
|
|
84370
85075
|
var formatTimeRange = (start, end, timezone) => {
|
|
84371
85076
|
const formatter = new Intl.DateTimeFormat("en-IN", {
|
|
84372
85077
|
hour: "numeric",
|
|
@@ -84382,12 +85087,15 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84382
85087
|
onClose,
|
|
84383
85088
|
shiftConfig: passedShiftConfig,
|
|
84384
85089
|
date,
|
|
84385
|
-
shiftId
|
|
85090
|
+
shiftId,
|
|
85091
|
+
initialMode = "camera"
|
|
84386
85092
|
}) => {
|
|
84387
85093
|
const timezone = useAppTimezone() || "UTC";
|
|
84388
85094
|
const logsContainerRef = React125.useRef(null);
|
|
84389
85095
|
const [showScrollIndicator, setShowScrollIndicator] = React125.useState(false);
|
|
85096
|
+
const [activeMode, setActiveMode] = React125.useState(initialMode);
|
|
84390
85097
|
const isHistorical = Boolean(date);
|
|
85098
|
+
const hasLightTimeline = Boolean(workspace?.lightSummary?.hasLightConfig && workspace?.lightSummary?.bulbIp);
|
|
84391
85099
|
const {
|
|
84392
85100
|
timeline,
|
|
84393
85101
|
loading,
|
|
@@ -84396,7 +85104,7 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84396
85104
|
} = useWorkspaceUptimeTimeline({
|
|
84397
85105
|
workspaceId: workspace?.workspace_id,
|
|
84398
85106
|
companyId: workspace?.company_id,
|
|
84399
|
-
enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
|
|
85107
|
+
enabled: isOpen && activeMode === "camera" && Boolean(workspace?.workspace_id && workspace?.company_id),
|
|
84400
85108
|
refreshInterval: isHistorical ? void 0 : 6e4,
|
|
84401
85109
|
// Disable auto-refresh for historical
|
|
84402
85110
|
lineId: workspace?.line_id,
|
|
@@ -84409,6 +85117,25 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84409
85117
|
shiftId
|
|
84410
85118
|
// Pass override shift ID for historical queries
|
|
84411
85119
|
});
|
|
85120
|
+
const {
|
|
85121
|
+
timeline: lightTimeline,
|
|
85122
|
+
loading: lightLoading,
|
|
85123
|
+
error: lightError,
|
|
85124
|
+
refetch: refetchLight
|
|
85125
|
+
} = useWorkspaceLightTimeline({
|
|
85126
|
+
workspaceId: workspace?.workspace_id,
|
|
85127
|
+
enabled: isOpen && activeMode === "light" && hasLightTimeline && Boolean(workspace?.workspace_id),
|
|
85128
|
+
refreshInterval: isHistorical ? void 0 : 6e4,
|
|
85129
|
+
lineId: workspace?.line_id,
|
|
85130
|
+
shiftConfig: passedShiftConfig || void 0,
|
|
85131
|
+
timezone,
|
|
85132
|
+
date,
|
|
85133
|
+
shiftId
|
|
85134
|
+
});
|
|
85135
|
+
React125.useEffect(() => {
|
|
85136
|
+
if (!isOpen) return;
|
|
85137
|
+
setActiveMode(initialMode === "light" && hasLightTimeline ? "light" : "camera");
|
|
85138
|
+
}, [hasLightTimeline, initialMode, isOpen]);
|
|
84412
85139
|
React125.useEffect(() => {
|
|
84413
85140
|
if (!isOpen || !workspace) return;
|
|
84414
85141
|
const handleKeyDown = (event) => {
|
|
@@ -84421,13 +85148,21 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84421
85148
|
window.removeEventListener("keydown", handleKeyDown);
|
|
84422
85149
|
};
|
|
84423
85150
|
}, [isOpen, onClose, workspace]);
|
|
84424
|
-
const
|
|
84425
|
-
const
|
|
85151
|
+
const activeTimeline = activeMode === "light" ? lightTimeline : timeline;
|
|
85152
|
+
const activeLoading = activeMode === "light" ? lightLoading : loading;
|
|
85153
|
+
const activeError = activeMode === "light" ? lightError : error;
|
|
85154
|
+
const activeRefetch = activeMode === "light" ? refetchLight : refetch;
|
|
85155
|
+
const shiftStart = activeTimeline ? new Date(activeTimeline.shiftStart) : null;
|
|
85156
|
+
const shiftEnd = activeTimeline ? new Date(activeTimeline.shiftEnd) : null;
|
|
84426
85157
|
const downtimeSegments = timeline?.downtimeSegments || [];
|
|
84427
85158
|
downtimeSegments.length;
|
|
84428
85159
|
const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
|
|
84429
|
-
const hasTimelineData = Boolean(timeline?.hasData);
|
|
84430
|
-
const uptimePercentage = hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
|
|
85160
|
+
const hasTimelineData = activeMode === "light" ? Boolean(lightTimeline?.hasData) : Boolean(timeline?.hasData);
|
|
85161
|
+
const uptimePercentage = activeMode === "light" ? lightTimeline?.uptimePercentage ?? null : hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
|
|
85162
|
+
const lightStatus = lightTimeline?.currentStatus || workspace?.lightSummary?.currentStatus || null;
|
|
85163
|
+
const lightStatusText = `Light ${getLightStatusLabel(lightStatus)}`;
|
|
85164
|
+
const lightDurationText = lightTimeline?.currentDurationSeconds ? `${getLightDurationPrefix(lightStatus)} for ${formatSecondsDuration(lightTimeline.currentDurationSeconds)}` : null;
|
|
85165
|
+
const lightLastError = formatLightError(lightTimeline?.lastError);
|
|
84431
85166
|
const allInterruptionsSorted = React125.useMemo(
|
|
84432
85167
|
() => [...downtimeSegments].sort(
|
|
84433
85168
|
(a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
|
|
@@ -84449,7 +85184,7 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84449
85184
|
container.addEventListener("scroll", checkScroll);
|
|
84450
85185
|
return () => container.removeEventListener("scroll", checkScroll);
|
|
84451
85186
|
}
|
|
84452
|
-
}, [downtimeSegments]);
|
|
85187
|
+
}, [downtimeSegments, lightTimeline?.statusSegments, activeMode]);
|
|
84453
85188
|
const renderSegment = (segment) => {
|
|
84454
85189
|
const start = new Date(segment.startTime);
|
|
84455
85190
|
const end = new Date(segment.endTime);
|
|
@@ -84470,6 +85205,63 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84470
85205
|
`${segment.startMinuteIndex}-${segment.endMinuteIndex}`
|
|
84471
85206
|
);
|
|
84472
85207
|
};
|
|
85208
|
+
const renderLightSegment = (segment) => {
|
|
85209
|
+
const start = new Date(segment.startTime);
|
|
85210
|
+
const end = new Date(segment.endTime);
|
|
85211
|
+
const startLabel = new Intl.DateTimeFormat("en-IN", {
|
|
85212
|
+
hour: "numeric",
|
|
85213
|
+
minute: "2-digit",
|
|
85214
|
+
hour12: true,
|
|
85215
|
+
timeZone: timezone
|
|
85216
|
+
}).format(start);
|
|
85217
|
+
const endLabel = segment.isCurrent ? "Now" : new Intl.DateTimeFormat("en-IN", {
|
|
85218
|
+
hour: "numeric",
|
|
85219
|
+
minute: "2-digit",
|
|
85220
|
+
hour12: true,
|
|
85221
|
+
timeZone: timezone
|
|
85222
|
+
}).format(end);
|
|
85223
|
+
const containerClasses = segment.status === "down" ? "border-rose-200 bg-rose-50" : "border-amber-200 bg-amber-50";
|
|
85224
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
85225
|
+
"div",
|
|
85226
|
+
{
|
|
85227
|
+
className: `rounded-lg border px-5 py-3 ${containerClasses}`,
|
|
85228
|
+
children: [
|
|
85229
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-semibold text-gray-900", children: [
|
|
85230
|
+
startLabel,
|
|
85231
|
+
" - ",
|
|
85232
|
+
endLabel
|
|
85233
|
+
] }),
|
|
85234
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-600 mt-1", children: [
|
|
85235
|
+
getLightDurationPrefix(segment.status),
|
|
85236
|
+
" for ",
|
|
85237
|
+
formatSecondsDuration(segment.durationSeconds)
|
|
85238
|
+
] }),
|
|
85239
|
+
formatLightError(segment.lastError) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs font-medium text-rose-700", children: formatLightError(segment.lastError) })
|
|
85240
|
+
]
|
|
85241
|
+
},
|
|
85242
|
+
`${segment.status}-${segment.startMinuteIndex}-${segment.endMinuteIndex}`
|
|
85243
|
+
);
|
|
85244
|
+
};
|
|
85245
|
+
const handleModeChange = (mode) => {
|
|
85246
|
+
if (mode === activeMode) return;
|
|
85247
|
+
const previousMode = activeMode;
|
|
85248
|
+
setActiveMode(mode);
|
|
85249
|
+
trackCoreEvent("Health Timeline Mode Changed", {
|
|
85250
|
+
previous_mode: previousMode,
|
|
85251
|
+
selected_mode: mode,
|
|
85252
|
+
source: "segmented_control",
|
|
85253
|
+
workspace_id: workspace?.workspace_id,
|
|
85254
|
+
line_id: workspace?.line_id
|
|
85255
|
+
});
|
|
85256
|
+
};
|
|
85257
|
+
const handleRefresh = () => {
|
|
85258
|
+
trackCoreEvent("Health Timeline Refreshed", {
|
|
85259
|
+
mode: activeMode,
|
|
85260
|
+
workspace_id: workspace?.workspace_id,
|
|
85261
|
+
line_id: workspace?.line_id
|
|
85262
|
+
});
|
|
85263
|
+
activeRefetch();
|
|
85264
|
+
};
|
|
84473
85265
|
if (!isOpen || !workspace) {
|
|
84474
85266
|
return null;
|
|
84475
85267
|
}
|
|
@@ -84493,13 +85285,35 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84493
85285
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
|
|
84494
85286
|
/* @__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)}` }),
|
|
84495
85287
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
|
|
84496
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-900", children:
|
|
85288
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-900", children: activeTimeline?.shiftLabel || "Current Shift" }),
|
|
84497
85289
|
shiftStart && shiftEnd && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
84498
85290
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
84499
85291
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
|
|
84500
85292
|
] })
|
|
84501
85293
|
] }),
|
|
84502
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-
|
|
85294
|
+
hasLightTimeline && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 inline-flex rounded-lg border border-gray-200 bg-gray-50 p-0.5", children: [
|
|
85295
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
85296
|
+
"button",
|
|
85297
|
+
{
|
|
85298
|
+
type: "button",
|
|
85299
|
+
onClick: () => handleModeChange("camera"),
|
|
85300
|
+
"aria-pressed": activeMode === "camera",
|
|
85301
|
+
className: `rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${activeMode === "camera" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
|
|
85302
|
+
children: "Camera"
|
|
85303
|
+
}
|
|
85304
|
+
),
|
|
85305
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
85306
|
+
"button",
|
|
85307
|
+
{
|
|
85308
|
+
type: "button",
|
|
85309
|
+
onClick: () => handleModeChange("light"),
|
|
85310
|
+
"aria-pressed": activeMode === "light",
|
|
85311
|
+
className: `rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${activeMode === "light" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
|
|
85312
|
+
children: "Light"
|
|
85313
|
+
}
|
|
85314
|
+
)
|
|
85315
|
+
] }),
|
|
85316
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: activeMode === "camera" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
84503
85317
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
84504
85318
|
/* @__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"}` }),
|
|
84505
85319
|
/* @__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" })
|
|
@@ -84516,17 +85330,37 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84516
85330
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-600 dark:text-slate-300 select-all", children: workspace.cameraIp })
|
|
84517
85331
|
] })
|
|
84518
85332
|
] })
|
|
84519
|
-
] })
|
|
85333
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
85334
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
85335
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-2 h-2 rounded-full ${lightStatus === "up" ? "bg-emerald-500 animate-pulse" : lightStatus === "unknown" ? "bg-amber-500" : "bg-rose-500"}` }),
|
|
85336
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs font-medium ${lightStatus === "up" ? "text-emerald-700" : lightStatus === "unknown" ? "text-amber-700" : "text-rose-700"}`, children: lightStatusText })
|
|
85337
|
+
] }),
|
|
85338
|
+
lightTimeline?.bulbIp && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
85339
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
85340
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 bg-slate-50 px-2 py-0.5 rounded border border-slate-100", title: "Bulb IP", children: [
|
|
85341
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Lightbulb, { className: "h-3 w-3 text-slate-400" }),
|
|
85342
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-slate-600 select-all", children: lightTimeline.bulbIp })
|
|
85343
|
+
] })
|
|
85344
|
+
] }),
|
|
85345
|
+
lightDurationText && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
85346
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
85347
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: lightDurationText })
|
|
85348
|
+
] }),
|
|
85349
|
+
lightLastError && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
85350
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
85351
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-rose-700", children: lightLastError })
|
|
85352
|
+
] })
|
|
85353
|
+
] }) })
|
|
84520
85354
|
] }),
|
|
84521
85355
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
84522
85356
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
84523
85357
|
"button",
|
|
84524
85358
|
{
|
|
84525
|
-
onClick:
|
|
84526
|
-
disabled:
|
|
85359
|
+
onClick: handleRefresh,
|
|
85360
|
+
disabled: activeLoading,
|
|
84527
85361
|
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",
|
|
84528
85362
|
children: [
|
|
84529
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-4 w-4 ${
|
|
85363
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-4 w-4 ${activeLoading ? "animate-spin" : ""}` }),
|
|
84530
85364
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Refresh" })
|
|
84531
85365
|
]
|
|
84532
85366
|
}
|
|
@@ -84542,31 +85376,63 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84542
85376
|
)
|
|
84543
85377
|
] })
|
|
84544
85378
|
] }),
|
|
84545
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-8 py-6 space-y-6", children:
|
|
85379
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-8 py-6 space-y-6", children: activeError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-rose-100 bg-rose-50 p-5 text-sm text-rose-700", children: [
|
|
84546
85380
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
|
|
84547
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-rose-600/90", children:
|
|
85381
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-rose-600/90", children: activeError.message })
|
|
84548
85382
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
84549
85383
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
|
|
84550
85384
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
84551
85385
|
UptimeTimelineStrip_default,
|
|
84552
85386
|
{
|
|
84553
|
-
points:
|
|
84554
|
-
totalMinutes:
|
|
84555
|
-
shiftStart:
|
|
84556
|
-
shiftEnd:
|
|
85387
|
+
points: activeTimeline?.points || [],
|
|
85388
|
+
totalMinutes: activeTimeline?.totalMinutes || 0,
|
|
85389
|
+
shiftStart: activeTimeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
|
|
85390
|
+
shiftEnd: activeTimeline?.shiftEnd || (/* @__PURE__ */ new Date()).toISOString(),
|
|
84557
85391
|
timezone,
|
|
84558
85392
|
uptimePercentage,
|
|
84559
|
-
downtimeMinutes: hasTimelineData ? downtimeMinutes : 0
|
|
85393
|
+
downtimeMinutes: activeMode === "light" ? 0 : hasTimelineData ? downtimeMinutes : 0,
|
|
85394
|
+
metricLabel: activeMode === "light" ? "light uptime" : "uptime",
|
|
85395
|
+
summaryText: activeMode === "light" && typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
85396
|
+
uptimePercentage.toFixed(1),
|
|
85397
|
+
" % light uptime",
|
|
85398
|
+
(lightTimeline?.downSeconds || 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
85399
|
+
" (",
|
|
85400
|
+
formatSecondsDuration(lightTimeline?.downSeconds || 0),
|
|
85401
|
+
" offline)"
|
|
85402
|
+
] }),
|
|
85403
|
+
(lightTimeline?.unknownSeconds || 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
85404
|
+
" (",
|
|
85405
|
+
formatSecondsDuration(lightTimeline?.unknownSeconds || 0),
|
|
85406
|
+
" unknown)"
|
|
85407
|
+
] })
|
|
85408
|
+
] }) : void 0
|
|
84560
85409
|
}
|
|
84561
85410
|
),
|
|
84562
|
-
|
|
85411
|
+
activeLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
|
|
84563
85412
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
|
|
84564
85413
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Updating timeline\u2026" })
|
|
84565
85414
|
] })
|
|
84566
85415
|
] }),
|
|
84567
85416
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-4", children: [
|
|
84568
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
|
|
84569
|
-
!hasTimelineData ? /* @__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
|
|
85417
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: activeMode === "light" ? "Light Status Logs" : "Downtime Logs" }),
|
|
85418
|
+
activeMode === "light" ? !hasTimelineData ? /* @__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 light status data available for this shift." }) }) : !lightTimeline?.statusSegments.length ? /* @__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 light down or unknown events recorded for this shift." }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
85419
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
85420
|
+
"div",
|
|
85421
|
+
{
|
|
85422
|
+
ref: logsContainerRef,
|
|
85423
|
+
className: "max-h-[400px] overflow-y-auto space-y-2 pr-2",
|
|
85424
|
+
style: {
|
|
85425
|
+
scrollbarWidth: "thin",
|
|
85426
|
+
scrollbarColor: "#CBD5E0 #F7FAFC"
|
|
85427
|
+
},
|
|
85428
|
+
children: lightTimeline.statusSegments.map((segment) => renderLightSegment(segment))
|
|
85429
|
+
}
|
|
85430
|
+
),
|
|
85431
|
+
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: [
|
|
85432
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Scroll for more" }),
|
|
85433
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
|
|
85434
|
+
] }) })
|
|
85435
|
+
] }) : !hasTimelineData ? /* @__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 uptime data available for this shift." }) }) : 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: [
|
|
84570
85436
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
84571
85437
|
"div",
|
|
84572
85438
|
{
|
|
@@ -84613,6 +85479,7 @@ var WorkspaceHealthView = ({
|
|
|
84613
85479
|
const timezone = useAppTimezone();
|
|
84614
85480
|
const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(effectiveLineIdForShiftConfig);
|
|
84615
85481
|
const [selectedWorkspace, setSelectedWorkspace] = React125.useState(null);
|
|
85482
|
+
const [selectedTimelineMode, setSelectedTimelineMode] = React125.useState("camera");
|
|
84616
85483
|
const [selectedDate, setSelectedDate] = React125.useState(void 0);
|
|
84617
85484
|
const [selectedShiftId, setSelectedShiftId] = React125.useState(void 0);
|
|
84618
85485
|
const [showDatePicker, setShowDatePicker] = React125.useState(false);
|
|
@@ -84672,11 +85539,33 @@ var WorkspaceHealthView = ({
|
|
|
84672
85539
|
},
|
|
84673
85540
|
[router$1, onNavigate]
|
|
84674
85541
|
);
|
|
84675
|
-
const handleViewDetails = React125.useCallback((workspace) => {
|
|
85542
|
+
const handleViewDetails = React125.useCallback((workspace, mode = "camera", source = "card_button") => {
|
|
85543
|
+
const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary?.bulbIp);
|
|
85544
|
+
if (source === "light_chip") {
|
|
85545
|
+
trackCoreEvent("Health Light Chip Clicked", {
|
|
85546
|
+
workspace_id: workspace.workspace_id,
|
|
85547
|
+
line_id: workspace.line_id,
|
|
85548
|
+
light_status: workspace.lightSummary?.currentStatus || null,
|
|
85549
|
+
uptime_percent: workspace.lightSummary?.uptimePercent ?? null,
|
|
85550
|
+
bulb_ip_present: Boolean(workspace.lightSummary?.bulbIp)
|
|
85551
|
+
});
|
|
85552
|
+
}
|
|
85553
|
+
trackCoreEvent("Health Timeline Opened", {
|
|
85554
|
+
source,
|
|
85555
|
+
initial_mode: mode,
|
|
85556
|
+
workspace_id: workspace.workspace_id,
|
|
85557
|
+
line_id: workspace.line_id,
|
|
85558
|
+
has_light_config: hasLightConfig,
|
|
85559
|
+
light_status: workspace.lightSummary?.currentStatus || null,
|
|
85560
|
+
selected_date: selectedDate || operationalDate,
|
|
85561
|
+
selected_shift_id: selectedShiftId ?? currentShiftDetails.shiftId
|
|
85562
|
+
});
|
|
85563
|
+
setSelectedTimelineMode(mode);
|
|
84676
85564
|
setSelectedWorkspace(workspace);
|
|
84677
|
-
}, []);
|
|
85565
|
+
}, [currentShiftDetails.shiftId, operationalDate, selectedDate, selectedShiftId]);
|
|
84678
85566
|
const handleCloseDetails = React125.useCallback(() => {
|
|
84679
85567
|
setSelectedWorkspace(null);
|
|
85568
|
+
setSelectedTimelineMode("camera");
|
|
84680
85569
|
}, []);
|
|
84681
85570
|
const getStatusIcon = (status) => {
|
|
84682
85571
|
switch (status) {
|
|
@@ -84934,7 +85823,8 @@ var WorkspaceHealthView = ({
|
|
|
84934
85823
|
onClose: handleCloseDetails,
|
|
84935
85824
|
shiftConfig: modalShiftConfig,
|
|
84936
85825
|
date: selectedDate,
|
|
84937
|
-
shiftId: selectedShiftId
|
|
85826
|
+
shiftId: selectedShiftId,
|
|
85827
|
+
initialMode: selectedTimelineMode
|
|
84938
85828
|
}
|
|
84939
85829
|
)
|
|
84940
85830
|
] });
|
|
@@ -93750,6 +94640,7 @@ exports.useWorkspaceHealthById = useWorkspaceHealthById;
|
|
|
93750
94640
|
exports.useWorkspaceHealthLastSeen = useWorkspaceHealthLastSeen;
|
|
93751
94641
|
exports.useWorkspaceHealthStatus = useWorkspaceHealthStatus;
|
|
93752
94642
|
exports.useWorkspaceHourSummary = useWorkspaceHourSummary;
|
|
94643
|
+
exports.useWorkspaceLightTimeline = useWorkspaceLightTimeline;
|
|
93753
94644
|
exports.useWorkspaceMetrics = useWorkspaceMetrics;
|
|
93754
94645
|
exports.useWorkspaceNavigation = useWorkspaceNavigation;
|
|
93755
94646
|
exports.useWorkspaceOperators = useWorkspaceOperators;
|