@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.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { EventEmitter } from 'events';
|
|
|
10
10
|
import { createClient, REALTIME_SUBSCRIBE_STATES } from '@supabase/supabase-js';
|
|
11
11
|
import Hls, { Events, ErrorTypes } from 'hls.js';
|
|
12
12
|
import useSWR from 'swr';
|
|
13
|
-
import { Camera, AlertTriangle, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, RefreshCw, ArrowUpDown, X, Coffee, Plus, Filter, ArrowUp, ArrowDown, ArrowRight, HelpCircle, ClipboardX, Activity, Wrench, UserX, Clock, Package, Monitor, CheckCircle2, ArrowLeft, Calendar, Save, AlertCircle, Loader2, Minus, ChevronLeft, ChevronRight, TrendingUp, Sparkles, Pause, Play, XCircle, Palette, LockKeyhole, TrendingDown, FolderOpen, Folder, ArrowDownWideNarrow, Tag, Sliders, Layers, Search, Edit2, CheckCircle, User, Users, Shield, Building2, Mail, Lock, Info, Share2, Trophy, Target, Download, Video, Copy, Sun, Moon, MousePointer, UserPlus, UserCog, Trash2, Eye, MoreVertical, BarChart3, Pencil, UserCheck, LogOut, Film, MessageSquare, Menu, Send, Settings, LifeBuoy, EyeOff, Zap, ExternalLink, Flame, Crown, Medal } from 'lucide-react';
|
|
13
|
+
import { Camera, AlertTriangle, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, RefreshCw, ArrowUpDown, X, Coffee, Plus, Filter, ArrowUp, ArrowDown, ArrowRight, HelpCircle, ClipboardX, Activity, Wrench, UserX, Clock, Package, Monitor, CheckCircle2, ArrowLeft, Calendar, Save, AlertCircle, Loader2, Minus, ChevronLeft, ChevronRight, TrendingUp, Sparkles, Pause, Play, XCircle, Palette, LockKeyhole, TrendingDown, FolderOpen, Folder, ArrowDownWideNarrow, Tag, Sliders, Layers, Search, Edit2, CheckCircle, User, Users, Shield, Building2, Mail, Lock, Info, Share2, Trophy, Target, Download, Video, Copy, Lightbulb, Sun, Moon, MousePointer, UserPlus, UserCog, Trash2, Eye, MoreVertical, BarChart3, Pencil, UserCheck, LogOut, Film, MessageSquare, Menu, Send, Settings, LifeBuoy, EyeOff, Zap, ExternalLink, Flame, Crown, Medal } from 'lucide-react';
|
|
14
14
|
import { memo, noop, warning, invariant, progress, secondsToMilliseconds, millisecondsToSeconds } from 'motion-utils';
|
|
15
15
|
import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, ReferenceLine, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, Customized, Cell, PieChart, Pie, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
|
|
16
16
|
import { Slot } from '@radix-ui/react-slot';
|
|
@@ -6056,6 +6056,10 @@ var workspaceService = {
|
|
|
6056
6056
|
_workspaceCameraIpsInFlight: /* @__PURE__ */ new Map(),
|
|
6057
6057
|
_workspaceCameraIpsCacheExpiryMs: 5 * 60 * 1e3,
|
|
6058
6058
|
// 5 minutes cache
|
|
6059
|
+
// Cache for workspace bulb IP configs
|
|
6060
|
+
_workspaceLightConfigsCache: /* @__PURE__ */ new Map(),
|
|
6061
|
+
_workspaceLightConfigsInFlight: /* @__PURE__ */ new Map(),
|
|
6062
|
+
_workspaceLightConfigsCacheExpiryMs: 5 * 60 * 1e3,
|
|
6059
6063
|
async getWorkspaces(lineId, options) {
|
|
6060
6064
|
const enabledOnly = options?.enabledOnly ?? false;
|
|
6061
6065
|
const force = options?.force ?? false;
|
|
@@ -6233,6 +6237,61 @@ var workspaceService = {
|
|
|
6233
6237
|
this._workspaceCameraIpsInFlight.set(cacheKey, fetchPromise);
|
|
6234
6238
|
return fetchPromise;
|
|
6235
6239
|
},
|
|
6240
|
+
async getWorkspaceLightConfigs(params) {
|
|
6241
|
+
const workspaceIds = (params.workspaceIds || []).filter(Boolean);
|
|
6242
|
+
const lineIds = (params.lineIds || []).filter(Boolean);
|
|
6243
|
+
const force = params.force ?? false;
|
|
6244
|
+
if (!workspaceIds.length && !lineIds.length) {
|
|
6245
|
+
return {};
|
|
6246
|
+
}
|
|
6247
|
+
const workspaceKey = workspaceIds.slice().sort().join(",");
|
|
6248
|
+
const lineKey = lineIds.slice().sort().join(",");
|
|
6249
|
+
const cacheKey = workspaceKey ? `workspaces:${workspaceKey}` : `lines:${lineKey}`;
|
|
6250
|
+
const now4 = Date.now();
|
|
6251
|
+
const cached = this._workspaceLightConfigsCache.get(cacheKey);
|
|
6252
|
+
if (!force && cached && now4 - cached.timestamp < this._workspaceLightConfigsCacheExpiryMs) {
|
|
6253
|
+
return cached.lightConfigs;
|
|
6254
|
+
}
|
|
6255
|
+
const inFlight = this._workspaceLightConfigsInFlight.get(cacheKey);
|
|
6256
|
+
if (!force && inFlight) {
|
|
6257
|
+
return inFlight;
|
|
6258
|
+
}
|
|
6259
|
+
const fetchPromise = (async () => {
|
|
6260
|
+
try {
|
|
6261
|
+
const token = await getAuthToken2();
|
|
6262
|
+
const apiUrl = getBackendUrl2();
|
|
6263
|
+
const response = await fetch(`${apiUrl}/api/workspaces/light-configs`, {
|
|
6264
|
+
method: "POST",
|
|
6265
|
+
headers: {
|
|
6266
|
+
"Authorization": `Bearer ${token}`,
|
|
6267
|
+
"Content-Type": "application/json"
|
|
6268
|
+
},
|
|
6269
|
+
body: JSON.stringify({
|
|
6270
|
+
workspace_ids: workspaceIds.length ? workspaceIds : void 0,
|
|
6271
|
+
line_ids: lineIds.length ? lineIds : void 0
|
|
6272
|
+
})
|
|
6273
|
+
});
|
|
6274
|
+
if (!response.ok) {
|
|
6275
|
+
const errorText = await response.text();
|
|
6276
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
6277
|
+
}
|
|
6278
|
+
const data = await response.json();
|
|
6279
|
+
const lightConfigs = data.light_configs || {};
|
|
6280
|
+
this._workspaceLightConfigsCache.set(cacheKey, {
|
|
6281
|
+
lightConfigs,
|
|
6282
|
+
timestamp: Date.now()
|
|
6283
|
+
});
|
|
6284
|
+
return lightConfigs;
|
|
6285
|
+
} catch (error) {
|
|
6286
|
+
console.error("Error fetching workspace light configs:", error);
|
|
6287
|
+
throw error;
|
|
6288
|
+
} finally {
|
|
6289
|
+
this._workspaceLightConfigsInFlight.delete(cacheKey);
|
|
6290
|
+
}
|
|
6291
|
+
})();
|
|
6292
|
+
this._workspaceLightConfigsInFlight.set(cacheKey, fetchPromise);
|
|
6293
|
+
return fetchPromise;
|
|
6294
|
+
},
|
|
6236
6295
|
/**
|
|
6237
6296
|
* Fetches workspace display names from the database
|
|
6238
6297
|
* Returns a map of workspace_id -> display_name
|
|
@@ -6653,6 +6712,138 @@ var computeWorkspaceHealthSummary = (data, lastUpdated = (/* @__PURE__ */ new Da
|
|
|
6653
6712
|
lastUpdated
|
|
6654
6713
|
};
|
|
6655
6714
|
};
|
|
6715
|
+
var normalizeHourBucket = (bucket) => {
|
|
6716
|
+
if (Array.isArray(bucket)) return bucket;
|
|
6717
|
+
if (bucket && typeof bucket === "object" && "values" in bucket && Array.isArray(bucket.values)) {
|
|
6718
|
+
return bucket.values;
|
|
6719
|
+
}
|
|
6720
|
+
return void 0;
|
|
6721
|
+
};
|
|
6722
|
+
var normalizeOutputHourly = (outputHourlyRaw) => outputHourlyRaw && typeof outputHourlyRaw === "object" ? Object.fromEntries(
|
|
6723
|
+
Object.entries(outputHourlyRaw).map(([key, value]) => [key, normalizeHourBucket(value) || []])
|
|
6724
|
+
) : {};
|
|
6725
|
+
var interpretUptimeValue = (value) => {
|
|
6726
|
+
if (value === null || value === void 0) return "down";
|
|
6727
|
+
if (typeof value === "string") {
|
|
6728
|
+
return value.trim().toLowerCase() === "x" ? "down" : "up";
|
|
6729
|
+
}
|
|
6730
|
+
return "up";
|
|
6731
|
+
};
|
|
6732
|
+
var deriveStatusForMinute = (minuteOffset, minuteDate, outputHourly, outputArray, timezone) => {
|
|
6733
|
+
const hourKey = formatInTimeZone(minuteDate, timezone, "H");
|
|
6734
|
+
const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
|
|
6735
|
+
const hourBucket = outputHourly[hourKey];
|
|
6736
|
+
if (Array.isArray(hourBucket)) {
|
|
6737
|
+
const value = hourBucket[minuteKey];
|
|
6738
|
+
if (value !== void 0) {
|
|
6739
|
+
return interpretUptimeValue(value);
|
|
6740
|
+
}
|
|
6741
|
+
}
|
|
6742
|
+
if (minuteOffset < outputArray.length) {
|
|
6743
|
+
return interpretUptimeValue(outputArray[minuteOffset]);
|
|
6744
|
+
}
|
|
6745
|
+
return "down";
|
|
6746
|
+
};
|
|
6747
|
+
var hasCompletedTimelineData = (completedMinutes, shiftStartDate, outputHourly, outputArray, timezone) => {
|
|
6748
|
+
if (completedMinutes <= 0) {
|
|
6749
|
+
return false;
|
|
6750
|
+
}
|
|
6751
|
+
for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex += 1) {
|
|
6752
|
+
const minuteDate = addMinutes(shiftStartDate, minuteIndex);
|
|
6753
|
+
const hourKey = formatInTimeZone(minuteDate, timezone, "H");
|
|
6754
|
+
const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
|
|
6755
|
+
const hourBucket = outputHourly[hourKey];
|
|
6756
|
+
if (Array.isArray(hourBucket) && hourBucket[minuteKey] !== void 0) {
|
|
6757
|
+
return true;
|
|
6758
|
+
}
|
|
6759
|
+
if (minuteIndex < outputArray.length) {
|
|
6760
|
+
return true;
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
return false;
|
|
6764
|
+
};
|
|
6765
|
+
var computeWorkspaceUptime = ({
|
|
6766
|
+
totalMinutes,
|
|
6767
|
+
completedMinutes,
|
|
6768
|
+
shiftStartDate,
|
|
6769
|
+
outputHourly,
|
|
6770
|
+
outputArray,
|
|
6771
|
+
timezone
|
|
6772
|
+
}) => {
|
|
6773
|
+
const hasData = hasCompletedTimelineData(
|
|
6774
|
+
completedMinutes,
|
|
6775
|
+
shiftStartDate,
|
|
6776
|
+
outputHourly,
|
|
6777
|
+
outputArray,
|
|
6778
|
+
timezone
|
|
6779
|
+
);
|
|
6780
|
+
if (!hasData) {
|
|
6781
|
+
return {
|
|
6782
|
+
hasData: false,
|
|
6783
|
+
uptimeMinutes: 0,
|
|
6784
|
+
downtimeMinutes: 0,
|
|
6785
|
+
uptimePercentage: 0,
|
|
6786
|
+
points: [],
|
|
6787
|
+
downtimeSegments: []
|
|
6788
|
+
};
|
|
6789
|
+
}
|
|
6790
|
+
const points = [];
|
|
6791
|
+
let uptimeMinutes = 0;
|
|
6792
|
+
let downtimeMinutes = 0;
|
|
6793
|
+
for (let minuteIndex = 0; minuteIndex < totalMinutes; minuteIndex += 1) {
|
|
6794
|
+
const minuteDate = addMinutes(shiftStartDate, minuteIndex);
|
|
6795
|
+
const timestamp = formatInTimeZone(minuteDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
6796
|
+
const status = minuteIndex < completedMinutes ? deriveStatusForMinute(minuteIndex, minuteDate, outputHourly, outputArray, timezone) : "pending";
|
|
6797
|
+
if (status === "up") {
|
|
6798
|
+
uptimeMinutes += 1;
|
|
6799
|
+
} else if (status === "down") {
|
|
6800
|
+
downtimeMinutes += 1;
|
|
6801
|
+
}
|
|
6802
|
+
points.push({
|
|
6803
|
+
minuteIndex,
|
|
6804
|
+
timestamp,
|
|
6805
|
+
status
|
|
6806
|
+
});
|
|
6807
|
+
}
|
|
6808
|
+
const downtimeSegments = [];
|
|
6809
|
+
let currentSegmentStart = null;
|
|
6810
|
+
const pushSegment = (startIndex, endIndex) => {
|
|
6811
|
+
if (endIndex <= startIndex) return;
|
|
6812
|
+
const segmentStartDate = addMinutes(shiftStartDate, startIndex);
|
|
6813
|
+
const segmentEndDate = addMinutes(shiftStartDate, endIndex);
|
|
6814
|
+
downtimeSegments.push({
|
|
6815
|
+
startMinuteIndex: startIndex,
|
|
6816
|
+
endMinuteIndex: endIndex,
|
|
6817
|
+
startTime: formatInTimeZone(segmentStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
6818
|
+
endTime: formatInTimeZone(segmentEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
6819
|
+
durationMinutes: endIndex - startIndex
|
|
6820
|
+
});
|
|
6821
|
+
};
|
|
6822
|
+
for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex += 1) {
|
|
6823
|
+
const point = points[minuteIndex];
|
|
6824
|
+
if (point.status === "down") {
|
|
6825
|
+
if (currentSegmentStart === null) {
|
|
6826
|
+
currentSegmentStart = minuteIndex;
|
|
6827
|
+
}
|
|
6828
|
+
} else if (currentSegmentStart !== null) {
|
|
6829
|
+
pushSegment(currentSegmentStart, minuteIndex);
|
|
6830
|
+
currentSegmentStart = null;
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
if (currentSegmentStart !== null) {
|
|
6834
|
+
pushSegment(currentSegmentStart, completedMinutes);
|
|
6835
|
+
}
|
|
6836
|
+
const completedWindow = Math.max(1, uptimeMinutes + downtimeMinutes);
|
|
6837
|
+
const uptimePercentage = completedMinutes > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
6838
|
+
return {
|
|
6839
|
+
hasData: true,
|
|
6840
|
+
uptimeMinutes,
|
|
6841
|
+
downtimeMinutes,
|
|
6842
|
+
uptimePercentage,
|
|
6843
|
+
points,
|
|
6844
|
+
downtimeSegments
|
|
6845
|
+
};
|
|
6846
|
+
};
|
|
6656
6847
|
|
|
6657
6848
|
// src/lib/services/workspaceHealthService.ts
|
|
6658
6849
|
var DATA_PROCESSING_DELAY_MINUTES = 5;
|
|
@@ -6679,6 +6870,92 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6679
6870
|
setCache(key, data) {
|
|
6680
6871
|
this.cache.set(key, { data, timestamp: Date.now() });
|
|
6681
6872
|
}
|
|
6873
|
+
hasBackendUrl() {
|
|
6874
|
+
return Boolean(process.env.NEXT_PUBLIC_BACKEND_URL);
|
|
6875
|
+
}
|
|
6876
|
+
async fetchBackendWorkspaceUptimeSummaries(companyId, lineIds, date, shiftId, timezone) {
|
|
6877
|
+
if (!this.hasBackendUrl()) {
|
|
6878
|
+
return null;
|
|
6879
|
+
}
|
|
6880
|
+
const supabase = _getSupabaseInstance();
|
|
6881
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
6882
|
+
const params = new URLSearchParams({
|
|
6883
|
+
company_id: companyId,
|
|
6884
|
+
date,
|
|
6885
|
+
shift_id: String(shiftId),
|
|
6886
|
+
timezone
|
|
6887
|
+
});
|
|
6888
|
+
const filteredLineIds = (lineIds || []).filter(Boolean);
|
|
6889
|
+
if (filteredLineIds.length > 0) {
|
|
6890
|
+
params.set("line_ids", filteredLineIds.join(","));
|
|
6891
|
+
}
|
|
6892
|
+
const cacheKey = `backend-uptime-summary:${companyId}:${filteredLineIds.join(",") || "all"}:${date}:${shiftId}:${timezone}`;
|
|
6893
|
+
const cached = this.getFromCache(cacheKey);
|
|
6894
|
+
if (cached) {
|
|
6895
|
+
return cached;
|
|
6896
|
+
}
|
|
6897
|
+
try {
|
|
6898
|
+
const response = await fetchBackendJson(
|
|
6899
|
+
supabase,
|
|
6900
|
+
`/api/dashboard/workspace-uptime?${params.toString()}`,
|
|
6901
|
+
{
|
|
6902
|
+
retries: 1,
|
|
6903
|
+
timeoutMs: 15e3,
|
|
6904
|
+
sentry: {
|
|
6905
|
+
capture: true,
|
|
6906
|
+
surface: "workspace_health",
|
|
6907
|
+
route: "GET /api/dashboard/workspace-uptime",
|
|
6908
|
+
severity: "warning",
|
|
6909
|
+
quotaKey: "workspace-health-uptime-summary"
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
);
|
|
6913
|
+
const uptimeMap = /* @__PURE__ */ new Map();
|
|
6914
|
+
for (const entry of response.uptimes || []) {
|
|
6915
|
+
if (!entry.workspaceId || !entry.uptimeDetails) continue;
|
|
6916
|
+
uptimeMap.set(this.getUptimeMapKey(entry.lineId, entry.workspaceId), entry.uptimeDetails);
|
|
6917
|
+
}
|
|
6918
|
+
this.setCache(cacheKey, uptimeMap);
|
|
6919
|
+
return uptimeMap;
|
|
6920
|
+
} catch (error) {
|
|
6921
|
+
console.warn("[workspaceHealthService] Backend uptime summary failed; using frontend fallback", error);
|
|
6922
|
+
return null;
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
async fetchBackendWorkspaceUptimeTimeline(workspaceId, companyId, lineId, date, shiftId, timezone) {
|
|
6926
|
+
if (!this.hasBackendUrl()) {
|
|
6927
|
+
return null;
|
|
6928
|
+
}
|
|
6929
|
+
const supabase = _getSupabaseInstance();
|
|
6930
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
6931
|
+
const params = new URLSearchParams({
|
|
6932
|
+
company_id: companyId,
|
|
6933
|
+
line_id: lineId,
|
|
6934
|
+
date,
|
|
6935
|
+
shift_id: String(shiftId),
|
|
6936
|
+
timezone
|
|
6937
|
+
});
|
|
6938
|
+
try {
|
|
6939
|
+
return await fetchBackendJson(
|
|
6940
|
+
supabase,
|
|
6941
|
+
`/api/dashboard/workspace/${workspaceId}/uptime-timeline?${params.toString()}`,
|
|
6942
|
+
{
|
|
6943
|
+
retries: 1,
|
|
6944
|
+
timeoutMs: 15e3,
|
|
6945
|
+
sentry: {
|
|
6946
|
+
capture: true,
|
|
6947
|
+
surface: "workspace_health",
|
|
6948
|
+
route: "GET /api/dashboard/workspace/{workspace_id}/uptime-timeline",
|
|
6949
|
+
severity: "warning",
|
|
6950
|
+
quotaKey: "workspace-health-uptime-timeline"
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
);
|
|
6954
|
+
} catch (error) {
|
|
6955
|
+
console.warn("[workspaceHealthService] Backend uptime timeline failed; using frontend fallback", error);
|
|
6956
|
+
return null;
|
|
6957
|
+
}
|
|
6958
|
+
}
|
|
6682
6959
|
getShiftTiming(timezone, shiftConfig) {
|
|
6683
6960
|
const currentShift = getCurrentShift(timezone, shiftConfig);
|
|
6684
6961
|
const { shiftId, date, shiftName, startTime, endTime } = currentShift;
|
|
@@ -6762,15 +7039,21 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6762
7039
|
pendingMinutes
|
|
6763
7040
|
};
|
|
6764
7041
|
}
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
)
|
|
7042
|
+
mergeOutputArrayValues(values) {
|
|
7043
|
+
const numericValues = values.filter(
|
|
7044
|
+
(value) => typeof value === "number" && Number.isFinite(value)
|
|
7045
|
+
);
|
|
7046
|
+
if (numericValues.length > 0) {
|
|
7047
|
+
const total = numericValues.reduce((sum, value) => sum + value, 0);
|
|
7048
|
+
return Number.isInteger(total) ? total : total;
|
|
7049
|
+
}
|
|
7050
|
+
if (values.length > 0 && values.every((value) => typeof value === "string" && value.trim().toLowerCase() === "x")) {
|
|
7051
|
+
return "x";
|
|
7052
|
+
}
|
|
7053
|
+
if (values.some((value) => value === null || value === void 0)) {
|
|
7054
|
+
return null;
|
|
7055
|
+
}
|
|
7056
|
+
return 0;
|
|
6774
7057
|
}
|
|
6775
7058
|
/**
|
|
6776
7059
|
* Group multi-SKU `performance_metrics` rows by workspace and merge their
|
|
@@ -6804,21 +7087,16 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6804
7087
|
const primary = group[0];
|
|
6805
7088
|
const mergedOutputHourly = mergeOutputHourly(
|
|
6806
7089
|
group.map((record) => ({
|
|
6807
|
-
output_hourly:
|
|
7090
|
+
output_hourly: normalizeOutputHourly(record.output_hourly || {})
|
|
6808
7091
|
}))
|
|
6809
7092
|
);
|
|
6810
7093
|
const arrays = group.map((record) => Array.isArray(record.output_array) ? record.output_array : []).filter((arr) => arr.length > 0);
|
|
6811
7094
|
let mergedArray = [];
|
|
6812
7095
|
if (arrays.length > 0) {
|
|
6813
7096
|
const maxLen = arrays.reduce((acc, arr) => Math.max(acc, arr.length), 0);
|
|
6814
|
-
mergedArray = new Array(maxLen).fill(0);
|
|
6815
7097
|
for (let i = 0; i < maxLen; i += 1) {
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
const value = i < arr.length ? arr[i] : 0;
|
|
6819
|
-
if (typeof value === "number" && Number.isFinite(value)) sum += value;
|
|
6820
|
-
}
|
|
6821
|
-
mergedArray[i] = sum;
|
|
7098
|
+
const values = arrays.filter((arr) => i < arr.length).map((arr) => arr[i]);
|
|
7099
|
+
mergedArray[i] = this.mergeOutputArrayValues(values);
|
|
6822
7100
|
}
|
|
6823
7101
|
}
|
|
6824
7102
|
return {
|
|
@@ -6828,45 +7106,392 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6828
7106
|
};
|
|
6829
7107
|
});
|
|
6830
7108
|
}
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
7109
|
+
parseShiftTime(value) {
|
|
7110
|
+
const [hourPart = "0", minutePart = "0"] = String(value || "00:00").split(":");
|
|
7111
|
+
const hour = Number.parseInt(hourPart, 10);
|
|
7112
|
+
const minute = Number.parseInt(minutePart, 10);
|
|
7113
|
+
return {
|
|
7114
|
+
hour: Number.isFinite(hour) ? hour : 0,
|
|
7115
|
+
minute: Number.isFinite(minute) ? minute : 0
|
|
7116
|
+
};
|
|
7117
|
+
}
|
|
7118
|
+
getShiftDurationMinutes(startTime, endTime) {
|
|
7119
|
+
const start = this.parseShiftTime(startTime);
|
|
7120
|
+
const end = this.parseShiftTime(endTime);
|
|
7121
|
+
let duration = end.hour * 60 + end.minute - (start.hour * 60 + start.minute);
|
|
7122
|
+
if (duration <= 0) {
|
|
7123
|
+
duration += 24 * 60;
|
|
6835
7124
|
}
|
|
6836
|
-
return
|
|
7125
|
+
return duration;
|
|
6837
7126
|
}
|
|
6838
|
-
|
|
6839
|
-
const
|
|
6840
|
-
const
|
|
6841
|
-
const
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
7127
|
+
resolveLightShiftWindow(shiftConfig, timezone, overrideDate, overrideShiftId, now4 = /* @__PURE__ */ new Date()) {
|
|
7128
|
+
const currentShift = getCurrentShift(timezone, shiftConfig);
|
|
7129
|
+
const queryDate = overrideDate ?? currentShift.date;
|
|
7130
|
+
const queryShiftId = overrideShiftId ?? currentShift.shiftId;
|
|
7131
|
+
const targetShiftConfig = shiftConfig?.shifts?.find((shift) => shift.shiftId === queryShiftId);
|
|
7132
|
+
const startTime = targetShiftConfig?.startTime || currentShift.startTime || "06:00";
|
|
7133
|
+
const endTime = targetShiftConfig?.endTime || currentShift.endTime || "18:00";
|
|
7134
|
+
const shiftLabel = targetShiftConfig?.shiftName || targetShiftConfig?.name || currentShift.shiftName || `Shift ${queryShiftId}`;
|
|
7135
|
+
const shiftStartDate = fromZonedTime(`${queryDate}T${startTime}:00`, timezone);
|
|
7136
|
+
const totalMinutes = this.getShiftDurationMinutes(startTime, endTime);
|
|
7137
|
+
const shiftEndDate = addMinutes(shiftStartDate, totalMinutes);
|
|
7138
|
+
const boundedWindowEnd = new Date(Math.min(Math.max(now4.getTime(), shiftStartDate.getTime()), shiftEndDate.getTime()));
|
|
7139
|
+
const completedMinutes = Math.max(
|
|
7140
|
+
0,
|
|
7141
|
+
Math.min(totalMinutes, Math.floor((boundedWindowEnd.getTime() - shiftStartDate.getTime()) / 6e4))
|
|
7142
|
+
);
|
|
7143
|
+
return {
|
|
7144
|
+
shiftId: queryShiftId,
|
|
7145
|
+
shiftLabel,
|
|
7146
|
+
shiftStartDate,
|
|
7147
|
+
shiftEndDate,
|
|
7148
|
+
windowEndDate: boundedWindowEnd,
|
|
7149
|
+
totalMinutes,
|
|
7150
|
+
completedMinutes,
|
|
7151
|
+
pendingMinutes: Math.max(0, totalMinutes - completedMinutes)
|
|
7152
|
+
};
|
|
7153
|
+
}
|
|
7154
|
+
isLightStatus(status) {
|
|
7155
|
+
return status === "up" || status === "down" || status === "unknown";
|
|
7156
|
+
}
|
|
7157
|
+
secondsBetween(start, end) {
|
|
7158
|
+
return Math.max(0, Math.round((end.getTime() - start.getTime()) / 1e3));
|
|
7159
|
+
}
|
|
7160
|
+
roundPercent(value) {
|
|
7161
|
+
return Number(value.toFixed(1));
|
|
7162
|
+
}
|
|
7163
|
+
emptyLightTimeline(shiftWindow, timezone, bulbIp = null) {
|
|
7164
|
+
return {
|
|
7165
|
+
shiftId: shiftWindow.shiftId,
|
|
7166
|
+
shiftLabel: shiftWindow.shiftLabel,
|
|
7167
|
+
shiftStart: formatInTimeZone(shiftWindow.shiftStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7168
|
+
shiftEnd: formatInTimeZone(shiftWindow.shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7169
|
+
totalMinutes: shiftWindow.totalMinutes,
|
|
7170
|
+
completedMinutes: shiftWindow.completedMinutes,
|
|
7171
|
+
upSeconds: 0,
|
|
7172
|
+
downSeconds: 0,
|
|
7173
|
+
unknownSeconds: 0,
|
|
7174
|
+
totalObservedSeconds: 0,
|
|
7175
|
+
downtimeSeconds: 0,
|
|
7176
|
+
uptimePercentage: null,
|
|
7177
|
+
hasData: false,
|
|
7178
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7179
|
+
bulbIp,
|
|
7180
|
+
points: [],
|
|
7181
|
+
statusSegments: []
|
|
7182
|
+
};
|
|
7183
|
+
}
|
|
7184
|
+
async resolveWorkspaceLightConfigs(workspaces) {
|
|
7185
|
+
const supabase = _getSupabaseInstance();
|
|
7186
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
7187
|
+
const visibleWorkspaces = workspaces.filter((workspace) => workspace.workspace_id);
|
|
7188
|
+
if (!visibleWorkspaces.length) return /* @__PURE__ */ new Map();
|
|
7189
|
+
const workspaceIds = Array.from(new Set(visibleWorkspaces.map((workspace) => String(workspace.workspace_id))));
|
|
7190
|
+
const configs = /* @__PURE__ */ new Map();
|
|
7191
|
+
if (process.env.NEXT_PUBLIC_BACKEND_URL) {
|
|
7192
|
+
try {
|
|
7193
|
+
const backendConfigs = await workspaceService.getWorkspaceLightConfigs({ workspaceIds });
|
|
7194
|
+
for (const [workspaceId, config] of Object.entries(backendConfigs)) {
|
|
7195
|
+
if (!config?.bulb_ip) continue;
|
|
7196
|
+
configs.set(workspaceId, {
|
|
7197
|
+
healthWorkspaceId: workspaceId,
|
|
7198
|
+
lineId: config.line_id,
|
|
7199
|
+
workspaceDbId: config.workspace_id || workspaceId,
|
|
7200
|
+
bulbIp: String(config.bulb_ip)
|
|
7201
|
+
});
|
|
7202
|
+
}
|
|
7203
|
+
if (configs.size === workspaceIds.length) {
|
|
7204
|
+
return configs;
|
|
7205
|
+
}
|
|
7206
|
+
} catch (error) {
|
|
7207
|
+
console.error("[workspaceHealthService] Backend light config lookup failed, falling back to direct Supabase:", error);
|
|
6846
7208
|
}
|
|
6847
7209
|
}
|
|
6848
|
-
|
|
6849
|
-
|
|
7210
|
+
const unresolvedVisibleWorkspaces = visibleWorkspaces.filter(
|
|
7211
|
+
(workspace) => !configs.has(String(workspace.workspace_id))
|
|
7212
|
+
);
|
|
7213
|
+
if (!unresolvedVisibleWorkspaces.length) return configs;
|
|
7214
|
+
const fallbackWorkspaceIds = Array.from(new Set(unresolvedVisibleWorkspaces.map((workspace) => String(workspace.workspace_id))));
|
|
7215
|
+
const fallbackLineIds = Array.from(new Set(unresolvedVisibleWorkspaces.map((workspace) => workspace.line_id).filter(Boolean).map(String)));
|
|
7216
|
+
const fallbackVisiblePairs = new Set(unresolvedVisibleWorkspaces.map((workspace) => `${workspace.line_id || ""}::${workspace.workspace_id}`));
|
|
7217
|
+
let workspaceRows = [];
|
|
7218
|
+
try {
|
|
7219
|
+
let query = supabase.from("workspaces").select("id, workspace_id, line_id, enable_bulb_light").in("workspace_id", fallbackWorkspaceIds);
|
|
7220
|
+
if (fallbackLineIds.length) {
|
|
7221
|
+
query = query.in("line_id", fallbackLineIds);
|
|
7222
|
+
}
|
|
7223
|
+
const { data, error } = await query;
|
|
7224
|
+
if (error) throw error;
|
|
7225
|
+
workspaceRows = Array.isArray(data) ? data : [];
|
|
7226
|
+
} catch (error) {
|
|
7227
|
+
console.error("[workspaceHealthService] Error resolving light workspace rows:", error);
|
|
7228
|
+
return configs;
|
|
7229
|
+
}
|
|
7230
|
+
const dbIdToVisible = /* @__PURE__ */ new Map();
|
|
7231
|
+
for (const row of workspaceRows) {
|
|
7232
|
+
const healthWorkspaceId = row?.workspace_id ? String(row.workspace_id) : "";
|
|
7233
|
+
const lineId = row?.line_id ? String(row.line_id) : "";
|
|
7234
|
+
const dbId = row?.id ? String(row.id) : "";
|
|
7235
|
+
if (!healthWorkspaceId || !dbId) continue;
|
|
7236
|
+
if (fallbackLineIds.length && !fallbackVisiblePairs.has(`${lineId}::${healthWorkspaceId}`)) continue;
|
|
7237
|
+
if (row?.enable_bulb_light === false) continue;
|
|
7238
|
+
dbIdToVisible.set(dbId, { healthWorkspaceId, lineId });
|
|
7239
|
+
}
|
|
7240
|
+
const addMapping = (mapping, visible, workspaceDbId) => {
|
|
7241
|
+
const bulbIp = mapping?.bulb_ip ? String(mapping.bulb_ip).trim() : "";
|
|
7242
|
+
if (!bulbIp) return;
|
|
7243
|
+
configs.set(visible.healthWorkspaceId, {
|
|
7244
|
+
healthWorkspaceId: visible.healthWorkspaceId,
|
|
7245
|
+
lineId: visible.lineId,
|
|
7246
|
+
workspaceDbId,
|
|
7247
|
+
bulbIp
|
|
7248
|
+
});
|
|
7249
|
+
};
|
|
7250
|
+
const dbIds = Array.from(dbIdToVisible.keys());
|
|
7251
|
+
if (dbIds.length) {
|
|
7252
|
+
const { data, error } = await supabase.from("workspace_ip_mapping").select("workspace_id, bulb_ip").in("workspace_id", dbIds);
|
|
7253
|
+
if (!error && Array.isArray(data)) {
|
|
7254
|
+
for (const mapping of data) {
|
|
7255
|
+
const dbId = mapping?.workspace_id ? String(mapping.workspace_id) : "";
|
|
7256
|
+
const visible = dbIdToVisible.get(dbId);
|
|
7257
|
+
if (visible) addMapping(mapping, visible, dbId);
|
|
7258
|
+
}
|
|
7259
|
+
} else if (error) {
|
|
7260
|
+
console.error("[workspaceHealthService] Error resolving light IP mappings:", error);
|
|
7261
|
+
}
|
|
7262
|
+
}
|
|
7263
|
+
const unresolvedVisibleIds = fallbackWorkspaceIds.filter((workspaceId) => !configs.has(workspaceId));
|
|
7264
|
+
if (unresolvedVisibleIds.length) {
|
|
7265
|
+
const visibleById = new Map(unresolvedVisibleWorkspaces.map((workspace) => [
|
|
7266
|
+
String(workspace.workspace_id),
|
|
7267
|
+
{ healthWorkspaceId: String(workspace.workspace_id), lineId: workspace.line_id }
|
|
7268
|
+
]));
|
|
7269
|
+
const { data, error } = await supabase.from("workspace_ip_mapping").select("workspace_id, bulb_ip").in("workspace_id", unresolvedVisibleIds);
|
|
7270
|
+
if (!error && Array.isArray(data)) {
|
|
7271
|
+
for (const mapping of data) {
|
|
7272
|
+
const workspaceId = mapping?.workspace_id ? String(mapping.workspace_id) : "";
|
|
7273
|
+
const visible = visibleById.get(workspaceId);
|
|
7274
|
+
if (visible) addMapping(mapping, visible, null);
|
|
7275
|
+
}
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
return configs;
|
|
7279
|
+
}
|
|
7280
|
+
async fetchLightIntervals(bulbIps, windowStart, windowEnd) {
|
|
7281
|
+
if (!bulbIps.length || windowEnd <= windowStart) return [];
|
|
7282
|
+
const supabase = _getSupabaseInstance();
|
|
7283
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
7284
|
+
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()}`);
|
|
7285
|
+
if (error) {
|
|
7286
|
+
console.error("[workspaceHealthService] Error fetching light status intervals:", error);
|
|
7287
|
+
throw error;
|
|
6850
7288
|
}
|
|
6851
|
-
return
|
|
7289
|
+
return Array.isArray(data) ? data : [];
|
|
6852
7290
|
}
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
7291
|
+
getLightIntervalEffectiveRange(interval, windowStart, windowEnd) {
|
|
7292
|
+
const startedAt = new Date(interval.started_at);
|
|
7293
|
+
const endedAt = interval.ended_at ? new Date(interval.ended_at) : windowEnd;
|
|
7294
|
+
const effectiveStart = new Date(Math.max(startedAt.getTime(), windowStart.getTime()));
|
|
7295
|
+
const effectiveEnd = new Date(Math.min(endedAt.getTime(), windowEnd.getTime()));
|
|
7296
|
+
if (effectiveEnd <= effectiveStart) return null;
|
|
7297
|
+
return {
|
|
7298
|
+
start: effectiveStart,
|
|
7299
|
+
end: effectiveEnd,
|
|
7300
|
+
seconds: this.secondsBetween(effectiveStart, effectiveEnd)
|
|
7301
|
+
};
|
|
7302
|
+
}
|
|
7303
|
+
summarizeLightIntervals(intervals, windowStart, windowEnd, now4) {
|
|
7304
|
+
let upSeconds = 0;
|
|
7305
|
+
let downSeconds = 0;
|
|
7306
|
+
let unknownSeconds = 0;
|
|
7307
|
+
let currentInterval = null;
|
|
7308
|
+
for (const interval of intervals) {
|
|
7309
|
+
if (!this.isLightStatus(interval.status)) continue;
|
|
7310
|
+
const range = this.getLightIntervalEffectiveRange(interval, windowStart, windowEnd);
|
|
7311
|
+
if (!range) continue;
|
|
7312
|
+
if (interval.status === "up") upSeconds += range.seconds;
|
|
7313
|
+
if (interval.status === "down") downSeconds += range.seconds;
|
|
7314
|
+
if (interval.status === "unknown") unknownSeconds += range.seconds;
|
|
7315
|
+
if (interval.ended_at == null) {
|
|
7316
|
+
currentInterval = interval;
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
const totalObservedSeconds = upSeconds + downSeconds + unknownSeconds;
|
|
7320
|
+
return {
|
|
7321
|
+
hasLightConfig: true,
|
|
7322
|
+
bulbIp: null,
|
|
7323
|
+
currentStatus: this.isLightStatus(currentInterval?.status) ? currentInterval.status : null,
|
|
7324
|
+
currentStartedAt: currentInterval?.started_at || null,
|
|
7325
|
+
lastSeenAt: currentInterval?.last_seen_at || null,
|
|
7326
|
+
currentDurationSeconds: currentInterval?.started_at ? this.secondsBetween(new Date(currentInterval.started_at), now4) : null,
|
|
7327
|
+
lastError: currentInterval?.last_error || null,
|
|
7328
|
+
uptimePercent: totalObservedSeconds > 0 ? this.roundPercent(upSeconds / totalObservedSeconds * 100) : null,
|
|
7329
|
+
upSeconds,
|
|
7330
|
+
downSeconds,
|
|
7331
|
+
unknownSeconds,
|
|
7332
|
+
totalObservedSeconds
|
|
7333
|
+
};
|
|
7334
|
+
}
|
|
7335
|
+
async summarizeResolvedLightConfigs(configs, shiftWindow, now4) {
|
|
7336
|
+
if (!configs.size) return /* @__PURE__ */ new Map();
|
|
7337
|
+
const bulbIps = Array.from(new Set(Array.from(configs.values()).map((config) => config.bulbIp)));
|
|
7338
|
+
const intervals = await this.fetchLightIntervals(bulbIps, shiftWindow.shiftStartDate, shiftWindow.windowEndDate);
|
|
7339
|
+
const intervalsByBulb = /* @__PURE__ */ new Map();
|
|
7340
|
+
for (const interval of intervals) {
|
|
7341
|
+
const bulbIp = interval.bulb_ip ? String(interval.bulb_ip) : "";
|
|
7342
|
+
if (!bulbIp) continue;
|
|
7343
|
+
if (!intervalsByBulb.has(bulbIp)) intervalsByBulb.set(bulbIp, []);
|
|
7344
|
+
intervalsByBulb.get(bulbIp).push(interval);
|
|
7345
|
+
}
|
|
7346
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
7347
|
+
for (const config of configs.values()) {
|
|
7348
|
+
const summary = this.summarizeLightIntervals(
|
|
7349
|
+
intervalsByBulb.get(config.bulbIp) || [],
|
|
7350
|
+
shiftWindow.shiftStartDate,
|
|
7351
|
+
shiftWindow.windowEndDate,
|
|
7352
|
+
now4
|
|
7353
|
+
);
|
|
7354
|
+
summaries.set(config.healthWorkspaceId, {
|
|
7355
|
+
...summary,
|
|
7356
|
+
hasLightConfig: true,
|
|
7357
|
+
bulbIp: config.bulbIp
|
|
7358
|
+
});
|
|
6856
7359
|
}
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
7360
|
+
return summaries;
|
|
7361
|
+
}
|
|
7362
|
+
async getWorkspaceLightSummaries(workspaces, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId, now4 = /* @__PURE__ */ new Date(), lineShiftConfigs) {
|
|
7363
|
+
const dashboardConfig = _getDashboardConfigInstance();
|
|
7364
|
+
const shiftConfig = passedShiftConfig || dashboardConfig?.shiftConfig;
|
|
7365
|
+
const timezone = passedTimezone || shiftConfig?.timezone || dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
|
|
7366
|
+
if (lineShiftConfigs && lineShiftConfigs.size > 0) {
|
|
7367
|
+
const resolvedConfigs = await this.resolveWorkspaceLightConfigs(workspaces);
|
|
7368
|
+
if (!resolvedConfigs.size) return /* @__PURE__ */ new Map();
|
|
7369
|
+
const groupedByLine = /* @__PURE__ */ new Map();
|
|
7370
|
+
const fallbackConfigs = /* @__PURE__ */ new Map();
|
|
7371
|
+
for (const workspace of workspaces) {
|
|
7372
|
+
const workspaceId = workspace.workspace_id ? String(workspace.workspace_id) : "";
|
|
7373
|
+
const config = workspaceId ? resolvedConfigs.get(workspaceId) : null;
|
|
7374
|
+
if (!config) continue;
|
|
7375
|
+
const lineId = workspace.line_id ? String(workspace.line_id) : "";
|
|
7376
|
+
const lineShiftConfig = lineId ? lineShiftConfigs.get(lineId) : null;
|
|
7377
|
+
if (!lineId || !lineShiftConfig) {
|
|
7378
|
+
fallbackConfigs.set(config.healthWorkspaceId, config);
|
|
7379
|
+
continue;
|
|
7380
|
+
}
|
|
7381
|
+
if (!groupedByLine.has(lineId)) groupedByLine.set(lineId, /* @__PURE__ */ new Map());
|
|
7382
|
+
groupedByLine.get(lineId).set(config.healthWorkspaceId, config);
|
|
6864
7383
|
}
|
|
6865
|
-
|
|
6866
|
-
|
|
7384
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
7385
|
+
const summaryPromises = Array.from(groupedByLine.entries()).map(async ([lineId, lineConfigs]) => {
|
|
7386
|
+
const lineShiftWindow = this.resolveLightShiftWindow(
|
|
7387
|
+
lineShiftConfigs.get(lineId),
|
|
7388
|
+
timezone,
|
|
7389
|
+
overrideDate,
|
|
7390
|
+
overrideShiftId,
|
|
7391
|
+
now4
|
|
7392
|
+
);
|
|
7393
|
+
return this.summarizeResolvedLightConfigs(lineConfigs, lineShiftWindow, now4);
|
|
7394
|
+
});
|
|
7395
|
+
if (fallbackConfigs.size > 0) {
|
|
7396
|
+
const fallbackShiftWindow = this.resolveLightShiftWindow(
|
|
7397
|
+
shiftConfig,
|
|
7398
|
+
timezone,
|
|
7399
|
+
overrideDate,
|
|
7400
|
+
overrideShiftId,
|
|
7401
|
+
now4
|
|
7402
|
+
);
|
|
7403
|
+
summaryPromises.push(this.summarizeResolvedLightConfigs(fallbackConfigs, fallbackShiftWindow, now4));
|
|
6867
7404
|
}
|
|
7405
|
+
const resolvedSummaries = await Promise.all(summaryPromises);
|
|
7406
|
+
for (const groupSummaries of resolvedSummaries) {
|
|
7407
|
+
groupSummaries.forEach((summary, workspaceId) => summaries.set(workspaceId, summary));
|
|
7408
|
+
}
|
|
7409
|
+
return summaries;
|
|
6868
7410
|
}
|
|
6869
|
-
|
|
7411
|
+
const shiftWindow = this.resolveLightShiftWindow(shiftConfig, timezone, overrideDate, overrideShiftId, now4);
|
|
7412
|
+
const configs = await this.resolveWorkspaceLightConfigs(workspaces);
|
|
7413
|
+
return this.summarizeResolvedLightConfigs(configs, shiftWindow, now4);
|
|
7414
|
+
}
|
|
7415
|
+
async getWorkspaceLightTimeline(workspaceId, lineId, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId, now4 = /* @__PURE__ */ new Date()) {
|
|
7416
|
+
if (!workspaceId) {
|
|
7417
|
+
throw new Error("workspaceId is required to fetch light timeline");
|
|
7418
|
+
}
|
|
7419
|
+
const dashboardConfig = _getDashboardConfigInstance();
|
|
7420
|
+
const shiftConfig = passedShiftConfig || dashboardConfig?.shiftConfig;
|
|
7421
|
+
const timezone = passedTimezone || shiftConfig?.timezone || dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
|
|
7422
|
+
const shiftWindow = this.resolveLightShiftWindow(shiftConfig, timezone, overrideDate, overrideShiftId, now4);
|
|
7423
|
+
const configs = await this.resolveWorkspaceLightConfigs([{ workspace_id: workspaceId, line_id: lineId }]);
|
|
7424
|
+
const config = configs.get(workspaceId);
|
|
7425
|
+
if (!config) {
|
|
7426
|
+
return this.emptyLightTimeline(shiftWindow, timezone);
|
|
7427
|
+
}
|
|
7428
|
+
const intervals = await this.fetchLightIntervals([config.bulbIp], shiftWindow.shiftStartDate, shiftWindow.windowEndDate);
|
|
7429
|
+
const summary = this.summarizeLightIntervals(intervals, shiftWindow.shiftStartDate, shiftWindow.windowEndDate, now4);
|
|
7430
|
+
const hasData = summary.totalObservedSeconds > 0;
|
|
7431
|
+
const points = [];
|
|
7432
|
+
if (hasData) {
|
|
7433
|
+
for (let minuteIndex = 0; minuteIndex < shiftWindow.totalMinutes; minuteIndex++) {
|
|
7434
|
+
const minuteStart = addMinutes(shiftWindow.shiftStartDate, minuteIndex);
|
|
7435
|
+
const minuteEnd = addMinutes(minuteStart, 1);
|
|
7436
|
+
const timestamp = formatInTimeZone(minuteStart, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
7437
|
+
let status = "pending";
|
|
7438
|
+
if (minuteIndex < shiftWindow.completedMinutes) {
|
|
7439
|
+
const matchingInterval = intervals.find((interval) => {
|
|
7440
|
+
if (!this.isLightStatus(interval.status)) return false;
|
|
7441
|
+
const start = new Date(interval.started_at);
|
|
7442
|
+
const end = interval.ended_at ? new Date(interval.ended_at) : shiftWindow.windowEndDate;
|
|
7443
|
+
return start < minuteEnd && end > minuteStart;
|
|
7444
|
+
});
|
|
7445
|
+
status = this.isLightStatus(matchingInterval?.status) ? matchingInterval.status : "pending";
|
|
7446
|
+
}
|
|
7447
|
+
points.push({ minuteIndex, timestamp, status });
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
const statusSegments = intervals.filter((interval) => interval.status === "down" || interval.status === "unknown").reduce((segments, interval) => {
|
|
7451
|
+
const range = this.getLightIntervalEffectiveRange(interval, shiftWindow.shiftStartDate, shiftWindow.windowEndDate);
|
|
7452
|
+
if (!range || !this.isLightStatus(interval.status)) return segments;
|
|
7453
|
+
const startMinuteIndex = Math.max(0, Math.floor((range.start.getTime() - shiftWindow.shiftStartDate.getTime()) / 6e4));
|
|
7454
|
+
const endMinuteIndex = Math.min(
|
|
7455
|
+
shiftWindow.totalMinutes,
|
|
7456
|
+
Math.ceil((range.end.getTime() - shiftWindow.shiftStartDate.getTime()) / 6e4)
|
|
7457
|
+
);
|
|
7458
|
+
segments.push({
|
|
7459
|
+
status: interval.status,
|
|
7460
|
+
startMinuteIndex,
|
|
7461
|
+
endMinuteIndex,
|
|
7462
|
+
startTime: formatInTimeZone(range.start, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7463
|
+
endTime: formatInTimeZone(range.end, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7464
|
+
durationSeconds: range.seconds,
|
|
7465
|
+
durationMinutes: Math.ceil(range.seconds / 60),
|
|
7466
|
+
isCurrent: interval.ended_at == null,
|
|
7467
|
+
lastError: interval.last_error || null
|
|
7468
|
+
});
|
|
7469
|
+
return segments;
|
|
7470
|
+
}, []).sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
|
|
7471
|
+
return {
|
|
7472
|
+
shiftId: shiftWindow.shiftId,
|
|
7473
|
+
shiftLabel: shiftWindow.shiftLabel,
|
|
7474
|
+
shiftStart: formatInTimeZone(shiftWindow.shiftStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7475
|
+
shiftEnd: formatInTimeZone(shiftWindow.shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7476
|
+
totalMinutes: shiftWindow.totalMinutes,
|
|
7477
|
+
completedMinutes: shiftWindow.completedMinutes,
|
|
7478
|
+
upSeconds: summary.upSeconds,
|
|
7479
|
+
downSeconds: summary.downSeconds,
|
|
7480
|
+
unknownSeconds: summary.unknownSeconds,
|
|
7481
|
+
totalObservedSeconds: summary.totalObservedSeconds,
|
|
7482
|
+
downtimeSeconds: summary.downSeconds,
|
|
7483
|
+
uptimePercentage: summary.uptimePercent,
|
|
7484
|
+
hasData,
|
|
7485
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7486
|
+
bulbIp: config.bulbIp,
|
|
7487
|
+
currentStatus: summary.currentStatus,
|
|
7488
|
+
currentStartedAt: summary.currentStartedAt,
|
|
7489
|
+
currentDurationSeconds: summary.currentDurationSeconds,
|
|
7490
|
+
lastSeenAt: summary.lastSeenAt,
|
|
7491
|
+
lastError: summary.lastError,
|
|
7492
|
+
points,
|
|
7493
|
+
statusSegments
|
|
7494
|
+
};
|
|
6870
7495
|
}
|
|
6871
7496
|
async getWorkspaceHealthStatus(options = {}) {
|
|
6872
7497
|
const supabase = _getSupabaseInstance();
|
|
@@ -6901,7 +7526,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6901
7526
|
timezone,
|
|
6902
7527
|
options.lineShiftConfigs,
|
|
6903
7528
|
options.date,
|
|
6904
|
-
options.shiftId
|
|
7529
|
+
options.shiftId,
|
|
7530
|
+
options.lineId
|
|
6905
7531
|
);
|
|
6906
7532
|
} catch (error2) {
|
|
6907
7533
|
console.error("Error calculating uptime:", error2);
|
|
@@ -6966,6 +7592,27 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6966
7592
|
} catch (e) {
|
|
6967
7593
|
console.error("Error filtering workspaces:", e);
|
|
6968
7594
|
}
|
|
7595
|
+
if (filteredData.length > 0) {
|
|
7596
|
+
try {
|
|
7597
|
+
const lightSummaries = await this.getWorkspaceLightSummaries(
|
|
7598
|
+
filteredData,
|
|
7599
|
+
shiftConfig,
|
|
7600
|
+
timezone,
|
|
7601
|
+
options.date,
|
|
7602
|
+
options.shiftId,
|
|
7603
|
+
/* @__PURE__ */ new Date(),
|
|
7604
|
+
options.lineShiftConfigs
|
|
7605
|
+
);
|
|
7606
|
+
if (lightSummaries.size > 0) {
|
|
7607
|
+
filteredData = filteredData.map((workspace) => {
|
|
7608
|
+
const lightSummary = lightSummaries.get(workspace.workspace_id);
|
|
7609
|
+
return lightSummary ? { ...workspace, lightSummary } : workspace;
|
|
7610
|
+
});
|
|
7611
|
+
}
|
|
7612
|
+
} catch (error2) {
|
|
7613
|
+
console.error("Error calculating light uptime summaries:", error2);
|
|
7614
|
+
}
|
|
7615
|
+
}
|
|
6969
7616
|
if (options.status) {
|
|
6970
7617
|
filteredData = filteredData.filter((item) => item.status === options.status);
|
|
6971
7618
|
}
|
|
@@ -6994,7 +7641,7 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
6994
7641
|
}
|
|
6995
7642
|
return filteredData;
|
|
6996
7643
|
}
|
|
6997
|
-
async getWorkspaceUptimeTimeline(workspaceId, companyId, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId) {
|
|
7644
|
+
async getWorkspaceUptimeTimeline(workspaceId, companyId, passedShiftConfig, passedTimezone, overrideDate, overrideShiftId, lineId) {
|
|
6998
7645
|
if (!workspaceId) {
|
|
6999
7646
|
throw new Error("workspaceId is required to fetch uptime timeline");
|
|
7000
7647
|
}
|
|
@@ -7013,6 +7660,19 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7013
7660
|
const currentTiming = this.getShiftTiming(timezone, shiftConfig);
|
|
7014
7661
|
const queryDate = overrideDate ?? currentTiming.date;
|
|
7015
7662
|
const queryShiftId = overrideShiftId ?? currentTiming.shiftId;
|
|
7663
|
+
if (lineId) {
|
|
7664
|
+
const backendTimeline = await this.fetchBackendWorkspaceUptimeTimeline(
|
|
7665
|
+
workspaceId,
|
|
7666
|
+
companyId,
|
|
7667
|
+
lineId,
|
|
7668
|
+
queryDate,
|
|
7669
|
+
queryShiftId,
|
|
7670
|
+
timezone
|
|
7671
|
+
);
|
|
7672
|
+
if (backendTimeline) {
|
|
7673
|
+
return backendTimeline;
|
|
7674
|
+
}
|
|
7675
|
+
}
|
|
7016
7676
|
let shiftStartDate;
|
|
7017
7677
|
let shiftEndDate;
|
|
7018
7678
|
let totalMinutes;
|
|
@@ -7061,18 +7721,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7061
7721
|
}
|
|
7062
7722
|
const mergedRecords = this.mergeRecordsByWorkspace(Array.isArray(data) ? data : []);
|
|
7063
7723
|
const record = mergedRecords.length > 0 ? mergedRecords[0] : null;
|
|
7064
|
-
const outputHourly =
|
|
7724
|
+
const outputHourly = normalizeOutputHourly(record?.output_hourly || {});
|
|
7065
7725
|
const outputArray = Array.isArray(record?.output_array) ? record.output_array : [];
|
|
7066
|
-
const
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
)
|
|
7075
|
-
if (!hasData) {
|
|
7726
|
+
const uptime = record ? computeWorkspaceUptime({
|
|
7727
|
+
totalMinutes,
|
|
7728
|
+
completedMinutes,
|
|
7729
|
+
shiftStartDate,
|
|
7730
|
+
outputHourly,
|
|
7731
|
+
outputArray,
|
|
7732
|
+
timezone
|
|
7733
|
+
}) : null;
|
|
7734
|
+
if (!uptime?.hasData) {
|
|
7076
7735
|
return {
|
|
7077
7736
|
shiftId: queryShiftId,
|
|
7078
7737
|
shiftLabel,
|
|
@@ -7090,80 +7749,6 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7090
7749
|
downtimeSegments: []
|
|
7091
7750
|
};
|
|
7092
7751
|
}
|
|
7093
|
-
const points = [];
|
|
7094
|
-
let uptimeMinutes = 0;
|
|
7095
|
-
let downtimeMinutes = 0;
|
|
7096
|
-
const MIN_DOWNTIME_MINUTES = 2;
|
|
7097
|
-
for (let minuteIndex = 0; minuteIndex < totalMinutes; minuteIndex++) {
|
|
7098
|
-
const minuteDate = addMinutes(shiftStartDate, minuteIndex);
|
|
7099
|
-
const timestamp = formatInTimeZone(minuteDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
7100
|
-
let status;
|
|
7101
|
-
if (minuteIndex < completedMinutes) {
|
|
7102
|
-
status = this.deriveStatusForMinute(
|
|
7103
|
-
minuteIndex,
|
|
7104
|
-
minuteDate,
|
|
7105
|
-
outputHourly,
|
|
7106
|
-
outputArray,
|
|
7107
|
-
timezone
|
|
7108
|
-
);
|
|
7109
|
-
if (status === "up") {
|
|
7110
|
-
uptimeMinutes += 1;
|
|
7111
|
-
} else {
|
|
7112
|
-
downtimeMinutes += 1;
|
|
7113
|
-
}
|
|
7114
|
-
} else {
|
|
7115
|
-
status = "pending";
|
|
7116
|
-
}
|
|
7117
|
-
points.push({
|
|
7118
|
-
minuteIndex,
|
|
7119
|
-
timestamp,
|
|
7120
|
-
status
|
|
7121
|
-
});
|
|
7122
|
-
}
|
|
7123
|
-
const downtimeSegments = [];
|
|
7124
|
-
let currentSegmentStart = null;
|
|
7125
|
-
const pushSegment = (startIndex, endIndex) => {
|
|
7126
|
-
if (endIndex <= startIndex) return;
|
|
7127
|
-
const segmentStartDate = addMinutes(shiftStartDate, startIndex);
|
|
7128
|
-
const segmentEndDate = addMinutes(shiftStartDate, endIndex);
|
|
7129
|
-
downtimeSegments.push({
|
|
7130
|
-
startMinuteIndex: startIndex,
|
|
7131
|
-
endMinuteIndex: endIndex,
|
|
7132
|
-
startTime: formatInTimeZone(segmentStartDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7133
|
-
endTime: formatInTimeZone(segmentEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7134
|
-
durationMinutes: endIndex - startIndex
|
|
7135
|
-
});
|
|
7136
|
-
};
|
|
7137
|
-
for (let i = 0; i < completedMinutes; i++) {
|
|
7138
|
-
const point = points[i];
|
|
7139
|
-
if (point.status === "down") {
|
|
7140
|
-
if (currentSegmentStart === null) {
|
|
7141
|
-
currentSegmentStart = i;
|
|
7142
|
-
}
|
|
7143
|
-
} else if (currentSegmentStart !== null) {
|
|
7144
|
-
pushSegment(currentSegmentStart, i);
|
|
7145
|
-
currentSegmentStart = null;
|
|
7146
|
-
}
|
|
7147
|
-
}
|
|
7148
|
-
if (currentSegmentStart !== null) {
|
|
7149
|
-
pushSegment(currentSegmentStart, completedMinutes);
|
|
7150
|
-
}
|
|
7151
|
-
const filteredSegments = [];
|
|
7152
|
-
downtimeSegments.forEach((segment) => {
|
|
7153
|
-
if (segment.durationMinutes >= MIN_DOWNTIME_MINUTES) {
|
|
7154
|
-
filteredSegments.push(segment);
|
|
7155
|
-
} else {
|
|
7156
|
-
for (let i = segment.startMinuteIndex; i < segment.endMinuteIndex; i++) {
|
|
7157
|
-
if (points[i] && points[i].status === "down") {
|
|
7158
|
-
points[i].status = "up";
|
|
7159
|
-
downtimeMinutes = Math.max(0, downtimeMinutes - 1);
|
|
7160
|
-
uptimeMinutes += 1;
|
|
7161
|
-
}
|
|
7162
|
-
}
|
|
7163
|
-
}
|
|
7164
|
-
});
|
|
7165
|
-
const completedWindow = Math.max(1, uptimeMinutes + downtimeMinutes);
|
|
7166
|
-
const uptimePercentage = completedMinutes > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
7167
7752
|
return {
|
|
7168
7753
|
shiftId: queryShiftId,
|
|
7169
7754
|
shiftLabel,
|
|
@@ -7171,14 +7756,14 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7171
7756
|
shiftEnd: formatInTimeZone(shiftEndDate, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
7172
7757
|
totalMinutes,
|
|
7173
7758
|
completedMinutes,
|
|
7174
|
-
uptimeMinutes,
|
|
7175
|
-
downtimeMinutes,
|
|
7759
|
+
uptimeMinutes: uptime.uptimeMinutes,
|
|
7760
|
+
downtimeMinutes: uptime.downtimeMinutes,
|
|
7176
7761
|
pendingMinutes,
|
|
7177
|
-
uptimePercentage,
|
|
7762
|
+
uptimePercentage: uptime.uptimePercentage,
|
|
7178
7763
|
hasData: true,
|
|
7179
7764
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7180
|
-
points,
|
|
7181
|
-
downtimeSegments:
|
|
7765
|
+
points: uptime.points,
|
|
7766
|
+
downtimeSegments: uptime.downtimeSegments
|
|
7182
7767
|
};
|
|
7183
7768
|
}
|
|
7184
7769
|
async getWorkspaceHealthById(workspaceId) {
|
|
@@ -7290,7 +7875,7 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7290
7875
|
getUptimeMapKey(lineId, workspaceId) {
|
|
7291
7876
|
return lineId ? `${lineId}::${workspaceId}` : workspaceId;
|
|
7292
7877
|
}
|
|
7293
|
-
async calculateWorkspaceUptime(companyId, passedShiftConfig, timezone, lineShiftConfigs, overrideDate, overrideShiftId) {
|
|
7878
|
+
async calculateWorkspaceUptime(companyId, passedShiftConfig, timezone, lineShiftConfigs, overrideDate, overrideShiftId, lineId) {
|
|
7294
7879
|
const supabase = _getSupabaseInstance();
|
|
7295
7880
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
7296
7881
|
const dashboardConfig = _getDashboardConfigInstance();
|
|
@@ -7312,6 +7897,16 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7312
7897
|
const currentTiming = this.getShiftTiming(effectiveTimezone, shiftConfig);
|
|
7313
7898
|
const queryDate = overrideDate ?? currentTiming.date;
|
|
7314
7899
|
const queryShiftId = overrideShiftId ?? currentTiming.shiftId;
|
|
7900
|
+
const backendUptime = await this.fetchBackendWorkspaceUptimeSummaries(
|
|
7901
|
+
companyId,
|
|
7902
|
+
lineId ? [lineId] : void 0,
|
|
7903
|
+
queryDate,
|
|
7904
|
+
queryShiftId,
|
|
7905
|
+
effectiveTimezone
|
|
7906
|
+
);
|
|
7907
|
+
if (backendUptime) {
|
|
7908
|
+
return backendUptime;
|
|
7909
|
+
}
|
|
7315
7910
|
let shiftStartDate = currentTiming.shiftStartDate;
|
|
7316
7911
|
let completedMinutes = currentTiming.completedMinutes;
|
|
7317
7912
|
const isHistoricalDate = overrideDate && overrideDate !== currentTiming.date;
|
|
@@ -7335,50 +7930,21 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7335
7930
|
const uptimeMap = /* @__PURE__ */ new Map();
|
|
7336
7931
|
const mergedRecords = this.mergeRecordsByWorkspace(queryData || []);
|
|
7337
7932
|
for (const record of mergedRecords) {
|
|
7338
|
-
const outputHourly =
|
|
7933
|
+
const outputHourly = normalizeOutputHourly(record.output_hourly || {});
|
|
7339
7934
|
const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
minuteDate,
|
|
7349
|
-
outputHourly,
|
|
7350
|
-
outputArray,
|
|
7351
|
-
effectiveTimezone
|
|
7352
|
-
);
|
|
7353
|
-
if (status === "down") {
|
|
7354
|
-
currentDownRun += 1;
|
|
7355
|
-
} else {
|
|
7356
|
-
if (currentDownRun > 0) {
|
|
7357
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7358
|
-
downtimeMinutes += currentDownRun;
|
|
7359
|
-
} else {
|
|
7360
|
-
uptimeMinutes += currentDownRun;
|
|
7361
|
-
}
|
|
7362
|
-
currentDownRun = 0;
|
|
7363
|
-
}
|
|
7364
|
-
uptimeMinutes += 1;
|
|
7365
|
-
}
|
|
7366
|
-
}
|
|
7367
|
-
if (currentDownRun > 0) {
|
|
7368
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7369
|
-
downtimeMinutes += currentDownRun;
|
|
7370
|
-
} else {
|
|
7371
|
-
uptimeMinutes += currentDownRun;
|
|
7372
|
-
}
|
|
7373
|
-
currentDownRun = 0;
|
|
7374
|
-
}
|
|
7375
|
-
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
7376
|
-
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
7935
|
+
const uptime = computeWorkspaceUptime({
|
|
7936
|
+
totalMinutes: completedMinutes,
|
|
7937
|
+
completedMinutes,
|
|
7938
|
+
shiftStartDate,
|
|
7939
|
+
outputHourly,
|
|
7940
|
+
outputArray,
|
|
7941
|
+
timezone: effectiveTimezone
|
|
7942
|
+
});
|
|
7377
7943
|
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
7378
7944
|
uptimeMap.set(mapKey, {
|
|
7379
7945
|
expectedMinutes: completedMinutes,
|
|
7380
|
-
actualMinutes: uptimeMinutes,
|
|
7381
|
-
percentage,
|
|
7946
|
+
actualMinutes: uptime.uptimeMinutes,
|
|
7947
|
+
percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100,
|
|
7382
7948
|
lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
|
|
7383
7949
|
});
|
|
7384
7950
|
}
|
|
@@ -7431,6 +7997,26 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7431
7997
|
}
|
|
7432
7998
|
uniqueQueries.get(key).lineConfigs.push({ lineId, shiftStartDate, completedMinutes });
|
|
7433
7999
|
});
|
|
8000
|
+
const backendUptimeMap = /* @__PURE__ */ new Map();
|
|
8001
|
+
let backendAvailable = true;
|
|
8002
|
+
for (const { date, shiftId, lineConfigs } of uniqueQueries.values()) {
|
|
8003
|
+
const lineIds = Array.from(new Set(lineConfigs.map((lc) => lc.lineId).filter(Boolean)));
|
|
8004
|
+
const result = await this.fetchBackendWorkspaceUptimeSummaries(
|
|
8005
|
+
companyId,
|
|
8006
|
+
lineIds,
|
|
8007
|
+
date,
|
|
8008
|
+
shiftId,
|
|
8009
|
+
timezone
|
|
8010
|
+
);
|
|
8011
|
+
if (result === null) {
|
|
8012
|
+
backendAvailable = false;
|
|
8013
|
+
break;
|
|
8014
|
+
}
|
|
8015
|
+
result.forEach((value, key) => backendUptimeMap.set(key, value));
|
|
8016
|
+
}
|
|
8017
|
+
if (backendAvailable) {
|
|
8018
|
+
return backendUptimeMap;
|
|
8019
|
+
}
|
|
7434
8020
|
console.log(`[calculateWorkspaceUptimeMultiLine] Querying ${uniqueQueries.size} unique date/shift combinations for ${lineShiftConfigs.size} lines`);
|
|
7435
8021
|
uniqueQueries.forEach((queryConfig, key) => {
|
|
7436
8022
|
console.log(`[calculateWorkspaceUptimeMultiLine] Query batch ${key}:`, {
|
|
@@ -7482,49 +8068,21 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7482
8068
|
continue;
|
|
7483
8069
|
}
|
|
7484
8070
|
const { shiftStartDate, completedMinutes } = lineConfig;
|
|
7485
|
-
const outputHourly =
|
|
8071
|
+
const outputHourly = normalizeOutputHourly(record.output_hourly || {});
|
|
7486
8072
|
const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
minuteDate,
|
|
7496
|
-
outputHourly,
|
|
7497
|
-
outputArray,
|
|
7498
|
-
timezone
|
|
7499
|
-
);
|
|
7500
|
-
if (status === "down") {
|
|
7501
|
-
currentDownRun += 1;
|
|
7502
|
-
} else {
|
|
7503
|
-
if (currentDownRun > 0) {
|
|
7504
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7505
|
-
downtimeMinutes += currentDownRun;
|
|
7506
|
-
} else {
|
|
7507
|
-
uptimeMinutes += currentDownRun;
|
|
7508
|
-
}
|
|
7509
|
-
currentDownRun = 0;
|
|
7510
|
-
}
|
|
7511
|
-
uptimeMinutes += 1;
|
|
7512
|
-
}
|
|
7513
|
-
}
|
|
7514
|
-
if (currentDownRun > 0) {
|
|
7515
|
-
if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
|
|
7516
|
-
downtimeMinutes += currentDownRun;
|
|
7517
|
-
} else {
|
|
7518
|
-
uptimeMinutes += currentDownRun;
|
|
7519
|
-
}
|
|
7520
|
-
}
|
|
7521
|
-
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
7522
|
-
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
8073
|
+
const uptime = computeWorkspaceUptime({
|
|
8074
|
+
totalMinutes: completedMinutes,
|
|
8075
|
+
completedMinutes,
|
|
8076
|
+
shiftStartDate,
|
|
8077
|
+
outputHourly,
|
|
8078
|
+
outputArray,
|
|
8079
|
+
timezone
|
|
8080
|
+
});
|
|
7523
8081
|
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
7524
8082
|
uptimeMap.set(mapKey, {
|
|
7525
8083
|
expectedMinutes: completedMinutes,
|
|
7526
|
-
actualMinutes: uptimeMinutes,
|
|
7527
|
-
percentage,
|
|
8084
|
+
actualMinutes: uptime.uptimeMinutes,
|
|
8085
|
+
percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100,
|
|
7528
8086
|
lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
|
|
7529
8087
|
});
|
|
7530
8088
|
console.log(`[calculateWorkspaceUptimeMultiLine] Storing uptime:`, {
|
|
@@ -7533,8 +8091,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
7533
8091
|
workspaceId: record.workspace_id,
|
|
7534
8092
|
workspaceDisplayName: record.workspace_display_name,
|
|
7535
8093
|
expectedMinutes: completedMinutes,
|
|
7536
|
-
actualMinutes: uptimeMinutes,
|
|
7537
|
-
percentage
|
|
8094
|
+
actualMinutes: uptime.uptimeMinutes,
|
|
8095
|
+
percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100
|
|
7538
8096
|
});
|
|
7539
8097
|
}
|
|
7540
8098
|
}
|
|
@@ -20775,7 +21333,8 @@ var useWorkspaceUptimeTimeline = (options) => {
|
|
|
20775
21333
|
effectiveShiftConfig,
|
|
20776
21334
|
effectiveTimezone,
|
|
20777
21335
|
overrideDate,
|
|
20778
|
-
overrideShiftId
|
|
21336
|
+
overrideShiftId,
|
|
21337
|
+
lineId
|
|
20779
21338
|
);
|
|
20780
21339
|
setTimeline(data);
|
|
20781
21340
|
} catch (err) {
|
|
@@ -20785,7 +21344,89 @@ var useWorkspaceUptimeTimeline = (options) => {
|
|
|
20785
21344
|
setLoading(false);
|
|
20786
21345
|
isFetchingRef.current = false;
|
|
20787
21346
|
}
|
|
20788
|
-
}, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
|
|
21347
|
+
}, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId, lineId]);
|
|
21348
|
+
useEffect(() => {
|
|
21349
|
+
fetchTimeline();
|
|
21350
|
+
}, [fetchTimeline]);
|
|
21351
|
+
useEffect(() => {
|
|
21352
|
+
if (!refreshInterval || refreshInterval <= 0 || !enabled) {
|
|
21353
|
+
return;
|
|
21354
|
+
}
|
|
21355
|
+
intervalRef.current = setInterval(() => {
|
|
21356
|
+
fetchTimeline();
|
|
21357
|
+
}, refreshInterval);
|
|
21358
|
+
return () => {
|
|
21359
|
+
if (intervalRef.current) {
|
|
21360
|
+
clearInterval(intervalRef.current);
|
|
21361
|
+
}
|
|
21362
|
+
};
|
|
21363
|
+
}, [refreshInterval, enabled, fetchTimeline]);
|
|
21364
|
+
return {
|
|
21365
|
+
timeline,
|
|
21366
|
+
loading: loading || shiftConfigPending,
|
|
21367
|
+
error,
|
|
21368
|
+
refetch: fetchTimeline
|
|
21369
|
+
};
|
|
21370
|
+
};
|
|
21371
|
+
var useWorkspaceLightTimeline = (options) => {
|
|
21372
|
+
const {
|
|
21373
|
+
workspaceId,
|
|
21374
|
+
lineId,
|
|
21375
|
+
enabled = true,
|
|
21376
|
+
refreshInterval,
|
|
21377
|
+
shiftConfig: passedShiftConfig,
|
|
21378
|
+
timezone: passedTimezone,
|
|
21379
|
+
date: overrideDate,
|
|
21380
|
+
shiftId: overrideShiftId
|
|
21381
|
+
} = options;
|
|
21382
|
+
const { shiftConfig: dynamicShiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
|
|
21383
|
+
const appTimezone = useAppTimezone();
|
|
21384
|
+
const shiftConfigPending = !passedShiftConfig && (!lineId || isShiftConfigLoading || !dynamicShiftConfig);
|
|
21385
|
+
const effectiveShiftConfig = passedShiftConfig ?? (shiftConfigPending ? void 0 : dynamicShiftConfig);
|
|
21386
|
+
const effectiveTimezone = passedTimezone || appTimezone || effectiveShiftConfig?.timezone || "UTC";
|
|
21387
|
+
const [timeline, setTimeline] = useState(null);
|
|
21388
|
+
const [loading, setLoading] = useState(false);
|
|
21389
|
+
const [error, setError] = useState(null);
|
|
21390
|
+
const isFetchingRef = useRef(false);
|
|
21391
|
+
const intervalRef = useRef(null);
|
|
21392
|
+
useEffect(() => {
|
|
21393
|
+
setTimeline(null);
|
|
21394
|
+
setError(null);
|
|
21395
|
+
setLoading(enabled && Boolean(workspaceId));
|
|
21396
|
+
}, [workspaceId, lineId, enabled]);
|
|
21397
|
+
const fetchTimeline = useCallback(async () => {
|
|
21398
|
+
if (!enabled) return;
|
|
21399
|
+
if (shiftConfigPending) {
|
|
21400
|
+
setLoading(true);
|
|
21401
|
+
return;
|
|
21402
|
+
}
|
|
21403
|
+
if (!effectiveShiftConfig || !workspaceId) {
|
|
21404
|
+
setLoading(false);
|
|
21405
|
+
setTimeline(null);
|
|
21406
|
+
return;
|
|
21407
|
+
}
|
|
21408
|
+
if (isFetchingRef.current) return;
|
|
21409
|
+
try {
|
|
21410
|
+
isFetchingRef.current = true;
|
|
21411
|
+
setLoading(true);
|
|
21412
|
+
setError(null);
|
|
21413
|
+
const data = await workspaceHealthService.getWorkspaceLightTimeline(
|
|
21414
|
+
workspaceId,
|
|
21415
|
+
lineId,
|
|
21416
|
+
effectiveShiftConfig,
|
|
21417
|
+
effectiveTimezone,
|
|
21418
|
+
overrideDate,
|
|
21419
|
+
overrideShiftId
|
|
21420
|
+
);
|
|
21421
|
+
setTimeline(data);
|
|
21422
|
+
} catch (err) {
|
|
21423
|
+
console.error("[useWorkspaceLightTimeline] Failed to fetch light timeline:", err);
|
|
21424
|
+
setError({ message: err?.message || "Failed to load light timeline", code: err?.code || "FETCH_ERROR" });
|
|
21425
|
+
} finally {
|
|
21426
|
+
setLoading(false);
|
|
21427
|
+
isFetchingRef.current = false;
|
|
21428
|
+
}
|
|
21429
|
+
}, [enabled, workspaceId, lineId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
|
|
20789
21430
|
useEffect(() => {
|
|
20790
21431
|
fetchTimeline();
|
|
20791
21432
|
}, [fetchTimeline]);
|
|
@@ -58212,7 +58853,14 @@ var WorkspaceHealthCard = ({
|
|
|
58212
58853
|
event.stopPropagation();
|
|
58213
58854
|
event.preventDefault();
|
|
58214
58855
|
if (onViewDetails) {
|
|
58215
|
-
onViewDetails(workspace);
|
|
58856
|
+
onViewDetails(workspace, "camera", "card_button");
|
|
58857
|
+
}
|
|
58858
|
+
};
|
|
58859
|
+
const handleLightChipClick = (event) => {
|
|
58860
|
+
event.stopPropagation();
|
|
58861
|
+
event.preventDefault();
|
|
58862
|
+
if (onViewDetails) {
|
|
58863
|
+
onViewDetails(workspace, "light", "light_chip");
|
|
58216
58864
|
}
|
|
58217
58865
|
};
|
|
58218
58866
|
const handleKeyDown = (event) => {
|
|
@@ -58290,6 +58938,22 @@ var WorkspaceHealthCard = ({
|
|
|
58290
58938
|
};
|
|
58291
58939
|
};
|
|
58292
58940
|
const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
|
|
58941
|
+
const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary.bulbIp);
|
|
58942
|
+
const lightStatus = workspace.lightSummary?.currentStatus || null;
|
|
58943
|
+
const lightChipLabel = (() => {
|
|
58944
|
+
if (!workspace.lightSummary) return "Light --";
|
|
58945
|
+
if (lightStatus === "down") return "Light offline";
|
|
58946
|
+
if (lightStatus === "unknown") return "Light unknown";
|
|
58947
|
+
if (typeof workspace.lightSummary.uptimePercent === "number") {
|
|
58948
|
+
return `Light ${workspace.lightSummary.uptimePercent.toFixed(1)}%`;
|
|
58949
|
+
}
|
|
58950
|
+
if (lightStatus === "up") return "Light operational";
|
|
58951
|
+
return "Light --";
|
|
58952
|
+
})();
|
|
58953
|
+
const lightChipClassName = clsx(
|
|
58954
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-semibold transition-all",
|
|
58955
|
+
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"
|
|
58956
|
+
);
|
|
58293
58957
|
return /* @__PURE__ */ jsx(
|
|
58294
58958
|
Card2,
|
|
58295
58959
|
{
|
|
@@ -58359,6 +59023,20 @@ var WorkspaceHealthCard = ({
|
|
|
58359
59023
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Camera IP:" }),
|
|
58360
59024
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "N/A" })
|
|
58361
59025
|
] }),
|
|
59026
|
+
hasLightConfig && /* @__PURE__ */ jsxs(
|
|
59027
|
+
"button",
|
|
59028
|
+
{
|
|
59029
|
+
type: "button",
|
|
59030
|
+
onClick: handleLightChipClick,
|
|
59031
|
+
className: lightChipClassName,
|
|
59032
|
+
"aria-label": `Open light timeline for ${workspace.workspace_display_name || workspace.workspace_id}`,
|
|
59033
|
+
title: workspace.lightSummary?.bulbIp ? `Bulb IP ${workspace.lightSummary.bulbIp}` : "Light timeline",
|
|
59034
|
+
children: [
|
|
59035
|
+
/* @__PURE__ */ jsx(Lightbulb, { className: "h-3.5 w-3.5" }),
|
|
59036
|
+
/* @__PURE__ */ jsx("span", { children: lightChipLabel })
|
|
59037
|
+
]
|
|
59038
|
+
}
|
|
59039
|
+
),
|
|
58362
59040
|
/* @__PURE__ */ jsx("div", { className: "mt-3 pt-3 border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
58363
59041
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: downtimeConfig.label }),
|
|
58364
59042
|
/* @__PURE__ */ jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
|
|
@@ -58428,7 +59106,7 @@ var CompactWorkspaceHealthCard = ({
|
|
|
58428
59106
|
event.stopPropagation();
|
|
58429
59107
|
event.preventDefault();
|
|
58430
59108
|
if (onViewDetails) {
|
|
58431
|
-
onViewDetails(workspace);
|
|
59109
|
+
onViewDetails(workspace, "camera", "card_button");
|
|
58432
59110
|
}
|
|
58433
59111
|
};
|
|
58434
59112
|
return /* @__PURE__ */ jsxs(
|
|
@@ -84157,11 +84835,13 @@ var useWorkspaceHealth = (options) => {
|
|
|
84157
84835
|
var STATUS_COLORS = {
|
|
84158
84836
|
up: "bg-emerald-500",
|
|
84159
84837
|
down: "bg-rose-500",
|
|
84838
|
+
unknown: "bg-amber-400",
|
|
84160
84839
|
pending: "bg-gray-200"
|
|
84161
84840
|
};
|
|
84162
84841
|
var STATUS_TITLES = {
|
|
84163
84842
|
up: "Uptime",
|
|
84164
84843
|
down: "Downtime",
|
|
84844
|
+
unknown: "Unknown",
|
|
84165
84845
|
pending: "Pending"
|
|
84166
84846
|
};
|
|
84167
84847
|
var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
|
|
@@ -84204,7 +84884,9 @@ var UptimeTimelineStrip = ({
|
|
|
84204
84884
|
timezone,
|
|
84205
84885
|
className = "",
|
|
84206
84886
|
uptimePercentage = null,
|
|
84207
|
-
downtimeMinutes = 0
|
|
84887
|
+
downtimeMinutes = 0,
|
|
84888
|
+
metricLabel = "uptime",
|
|
84889
|
+
summaryText
|
|
84208
84890
|
}) => {
|
|
84209
84891
|
const segments = useMemo(() => {
|
|
84210
84892
|
if (!points.length || totalMinutes <= 0) return [];
|
|
@@ -84268,9 +84950,11 @@ var UptimeTimelineStrip = ({
|
|
|
84268
84950
|
return /* @__PURE__ */ jsx("div", { className: "w-full rounded-xl border border-dashed border-gray-200 bg-gray-50/50 p-6 text-center text-sm text-gray-600", children: "No uptime data available for this shift yet." });
|
|
84269
84951
|
}
|
|
84270
84952
|
return /* @__PURE__ */ jsxs("div", { className: `relative w-full ${className}`, children: [
|
|
84271
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs("span", { className: "text-gray-900 font-semibold", children: [
|
|
84953
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: summaryText ? /* @__PURE__ */ jsx("span", { className: "text-gray-900 font-semibold", children: summaryText }) : typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs("span", { className: "text-gray-900 font-semibold", children: [
|
|
84272
84954
|
uptimePercentage.toFixed(1),
|
|
84273
|
-
" %
|
|
84955
|
+
" % ",
|
|
84956
|
+
metricLabel,
|
|
84957
|
+
" ",
|
|
84274
84958
|
downtimeMinutes > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
84275
84959
|
"(",
|
|
84276
84960
|
formatDowntimeLabel(downtimeMinutes),
|
|
@@ -84338,6 +85022,27 @@ var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
|
|
|
84338
85022
|
const label = formatDuration4(minutes);
|
|
84339
85023
|
return includeSuffix ? `${label} down` : label;
|
|
84340
85024
|
};
|
|
85025
|
+
var formatSecondsDuration = (seconds) => {
|
|
85026
|
+
if (!seconds || seconds <= 0) return "0 min";
|
|
85027
|
+
return formatDuration4(Math.max(1, Math.ceil(seconds / 60)));
|
|
85028
|
+
};
|
|
85029
|
+
var getLightStatusLabel = (status) => {
|
|
85030
|
+
if (status === "up") return "operational";
|
|
85031
|
+
if (status === "down") return "offline";
|
|
85032
|
+
if (status === "unknown") return "unknown";
|
|
85033
|
+
return "status unavailable";
|
|
85034
|
+
};
|
|
85035
|
+
var getLightDurationPrefix = (status) => {
|
|
85036
|
+
if (status === "up") return "Operational";
|
|
85037
|
+
if (status === "down") return "Offline";
|
|
85038
|
+
if (status === "unknown") return "Unknown";
|
|
85039
|
+
return "Current";
|
|
85040
|
+
};
|
|
85041
|
+
var formatLightError = (error) => {
|
|
85042
|
+
if (!error) return null;
|
|
85043
|
+
const cleaned = error.replace(/\s*\(after retry\)\s*$/i, "").trim();
|
|
85044
|
+
return cleaned || null;
|
|
85045
|
+
};
|
|
84341
85046
|
var formatTimeRange = (start, end, timezone) => {
|
|
84342
85047
|
const formatter = new Intl.DateTimeFormat("en-IN", {
|
|
84343
85048
|
hour: "numeric",
|
|
@@ -84353,12 +85058,15 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84353
85058
|
onClose,
|
|
84354
85059
|
shiftConfig: passedShiftConfig,
|
|
84355
85060
|
date,
|
|
84356
|
-
shiftId
|
|
85061
|
+
shiftId,
|
|
85062
|
+
initialMode = "camera"
|
|
84357
85063
|
}) => {
|
|
84358
85064
|
const timezone = useAppTimezone() || "UTC";
|
|
84359
85065
|
const logsContainerRef = useRef(null);
|
|
84360
85066
|
const [showScrollIndicator, setShowScrollIndicator] = useState(false);
|
|
85067
|
+
const [activeMode, setActiveMode] = useState(initialMode);
|
|
84361
85068
|
const isHistorical = Boolean(date);
|
|
85069
|
+
const hasLightTimeline = Boolean(workspace?.lightSummary?.hasLightConfig && workspace?.lightSummary?.bulbIp);
|
|
84362
85070
|
const {
|
|
84363
85071
|
timeline,
|
|
84364
85072
|
loading,
|
|
@@ -84367,7 +85075,7 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84367
85075
|
} = useWorkspaceUptimeTimeline({
|
|
84368
85076
|
workspaceId: workspace?.workspace_id,
|
|
84369
85077
|
companyId: workspace?.company_id,
|
|
84370
|
-
enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
|
|
85078
|
+
enabled: isOpen && activeMode === "camera" && Boolean(workspace?.workspace_id && workspace?.company_id),
|
|
84371
85079
|
refreshInterval: isHistorical ? void 0 : 6e4,
|
|
84372
85080
|
// Disable auto-refresh for historical
|
|
84373
85081
|
lineId: workspace?.line_id,
|
|
@@ -84380,6 +85088,25 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84380
85088
|
shiftId
|
|
84381
85089
|
// Pass override shift ID for historical queries
|
|
84382
85090
|
});
|
|
85091
|
+
const {
|
|
85092
|
+
timeline: lightTimeline,
|
|
85093
|
+
loading: lightLoading,
|
|
85094
|
+
error: lightError,
|
|
85095
|
+
refetch: refetchLight
|
|
85096
|
+
} = useWorkspaceLightTimeline({
|
|
85097
|
+
workspaceId: workspace?.workspace_id,
|
|
85098
|
+
enabled: isOpen && activeMode === "light" && hasLightTimeline && Boolean(workspace?.workspace_id),
|
|
85099
|
+
refreshInterval: isHistorical ? void 0 : 6e4,
|
|
85100
|
+
lineId: workspace?.line_id,
|
|
85101
|
+
shiftConfig: passedShiftConfig || void 0,
|
|
85102
|
+
timezone,
|
|
85103
|
+
date,
|
|
85104
|
+
shiftId
|
|
85105
|
+
});
|
|
85106
|
+
useEffect(() => {
|
|
85107
|
+
if (!isOpen) return;
|
|
85108
|
+
setActiveMode(initialMode === "light" && hasLightTimeline ? "light" : "camera");
|
|
85109
|
+
}, [hasLightTimeline, initialMode, isOpen]);
|
|
84383
85110
|
useEffect(() => {
|
|
84384
85111
|
if (!isOpen || !workspace) return;
|
|
84385
85112
|
const handleKeyDown = (event) => {
|
|
@@ -84392,13 +85119,21 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84392
85119
|
window.removeEventListener("keydown", handleKeyDown);
|
|
84393
85120
|
};
|
|
84394
85121
|
}, [isOpen, onClose, workspace]);
|
|
84395
|
-
const
|
|
84396
|
-
const
|
|
85122
|
+
const activeTimeline = activeMode === "light" ? lightTimeline : timeline;
|
|
85123
|
+
const activeLoading = activeMode === "light" ? lightLoading : loading;
|
|
85124
|
+
const activeError = activeMode === "light" ? lightError : error;
|
|
85125
|
+
const activeRefetch = activeMode === "light" ? refetchLight : refetch;
|
|
85126
|
+
const shiftStart = activeTimeline ? new Date(activeTimeline.shiftStart) : null;
|
|
85127
|
+
const shiftEnd = activeTimeline ? new Date(activeTimeline.shiftEnd) : null;
|
|
84397
85128
|
const downtimeSegments = timeline?.downtimeSegments || [];
|
|
84398
85129
|
downtimeSegments.length;
|
|
84399
85130
|
const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
|
|
84400
|
-
const hasTimelineData = Boolean(timeline?.hasData);
|
|
84401
|
-
const uptimePercentage = hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
|
|
85131
|
+
const hasTimelineData = activeMode === "light" ? Boolean(lightTimeline?.hasData) : Boolean(timeline?.hasData);
|
|
85132
|
+
const uptimePercentage = activeMode === "light" ? lightTimeline?.uptimePercentage ?? null : hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
|
|
85133
|
+
const lightStatus = lightTimeline?.currentStatus || workspace?.lightSummary?.currentStatus || null;
|
|
85134
|
+
const lightStatusText = `Light ${getLightStatusLabel(lightStatus)}`;
|
|
85135
|
+
const lightDurationText = lightTimeline?.currentDurationSeconds ? `${getLightDurationPrefix(lightStatus)} for ${formatSecondsDuration(lightTimeline.currentDurationSeconds)}` : null;
|
|
85136
|
+
const lightLastError = formatLightError(lightTimeline?.lastError);
|
|
84402
85137
|
const allInterruptionsSorted = useMemo(
|
|
84403
85138
|
() => [...downtimeSegments].sort(
|
|
84404
85139
|
(a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
|
|
@@ -84420,7 +85155,7 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84420
85155
|
container.addEventListener("scroll", checkScroll);
|
|
84421
85156
|
return () => container.removeEventListener("scroll", checkScroll);
|
|
84422
85157
|
}
|
|
84423
|
-
}, [downtimeSegments]);
|
|
85158
|
+
}, [downtimeSegments, lightTimeline?.statusSegments, activeMode]);
|
|
84424
85159
|
const renderSegment = (segment) => {
|
|
84425
85160
|
const start = new Date(segment.startTime);
|
|
84426
85161
|
const end = new Date(segment.endTime);
|
|
@@ -84441,6 +85176,63 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84441
85176
|
`${segment.startMinuteIndex}-${segment.endMinuteIndex}`
|
|
84442
85177
|
);
|
|
84443
85178
|
};
|
|
85179
|
+
const renderLightSegment = (segment) => {
|
|
85180
|
+
const start = new Date(segment.startTime);
|
|
85181
|
+
const end = new Date(segment.endTime);
|
|
85182
|
+
const startLabel = new Intl.DateTimeFormat("en-IN", {
|
|
85183
|
+
hour: "numeric",
|
|
85184
|
+
minute: "2-digit",
|
|
85185
|
+
hour12: true,
|
|
85186
|
+
timeZone: timezone
|
|
85187
|
+
}).format(start);
|
|
85188
|
+
const endLabel = segment.isCurrent ? "Now" : new Intl.DateTimeFormat("en-IN", {
|
|
85189
|
+
hour: "numeric",
|
|
85190
|
+
minute: "2-digit",
|
|
85191
|
+
hour12: true,
|
|
85192
|
+
timeZone: timezone
|
|
85193
|
+
}).format(end);
|
|
85194
|
+
const containerClasses = segment.status === "down" ? "border-rose-200 bg-rose-50" : "border-amber-200 bg-amber-50";
|
|
85195
|
+
return /* @__PURE__ */ jsxs(
|
|
85196
|
+
"div",
|
|
85197
|
+
{
|
|
85198
|
+
className: `rounded-lg border px-5 py-3 ${containerClasses}`,
|
|
85199
|
+
children: [
|
|
85200
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-semibold text-gray-900", children: [
|
|
85201
|
+
startLabel,
|
|
85202
|
+
" - ",
|
|
85203
|
+
endLabel
|
|
85204
|
+
] }),
|
|
85205
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 mt-1", children: [
|
|
85206
|
+
getLightDurationPrefix(segment.status),
|
|
85207
|
+
" for ",
|
|
85208
|
+
formatSecondsDuration(segment.durationSeconds)
|
|
85209
|
+
] }),
|
|
85210
|
+
formatLightError(segment.lastError) && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs font-medium text-rose-700", children: formatLightError(segment.lastError) })
|
|
85211
|
+
]
|
|
85212
|
+
},
|
|
85213
|
+
`${segment.status}-${segment.startMinuteIndex}-${segment.endMinuteIndex}`
|
|
85214
|
+
);
|
|
85215
|
+
};
|
|
85216
|
+
const handleModeChange = (mode) => {
|
|
85217
|
+
if (mode === activeMode) return;
|
|
85218
|
+
const previousMode = activeMode;
|
|
85219
|
+
setActiveMode(mode);
|
|
85220
|
+
trackCoreEvent("Health Timeline Mode Changed", {
|
|
85221
|
+
previous_mode: previousMode,
|
|
85222
|
+
selected_mode: mode,
|
|
85223
|
+
source: "segmented_control",
|
|
85224
|
+
workspace_id: workspace?.workspace_id,
|
|
85225
|
+
line_id: workspace?.line_id
|
|
85226
|
+
});
|
|
85227
|
+
};
|
|
85228
|
+
const handleRefresh = () => {
|
|
85229
|
+
trackCoreEvent("Health Timeline Refreshed", {
|
|
85230
|
+
mode: activeMode,
|
|
85231
|
+
workspace_id: workspace?.workspace_id,
|
|
85232
|
+
line_id: workspace?.line_id
|
|
85233
|
+
});
|
|
85234
|
+
activeRefetch();
|
|
85235
|
+
};
|
|
84444
85236
|
if (!isOpen || !workspace) {
|
|
84445
85237
|
return null;
|
|
84446
85238
|
}
|
|
@@ -84464,13 +85256,35 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84464
85256
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
|
|
84465
85257
|
/* @__PURE__ */ jsx("h2", { id: "uptime-detail-title", className: "text-2xl font-semibold text-gray-900 truncate mb-3", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 6)}` }),
|
|
84466
85258
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
|
|
84467
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children:
|
|
85259
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: activeTimeline?.shiftLabel || "Current Shift" }),
|
|
84468
85260
|
shiftStart && shiftEnd && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84469
85261
|
/* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
84470
85262
|
/* @__PURE__ */ jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
|
|
84471
85263
|
] })
|
|
84472
85264
|
] }),
|
|
84473
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-
|
|
85265
|
+
hasLightTimeline && /* @__PURE__ */ jsxs("div", { className: "mt-3 inline-flex rounded-lg border border-gray-200 bg-gray-50 p-0.5", children: [
|
|
85266
|
+
/* @__PURE__ */ jsx(
|
|
85267
|
+
"button",
|
|
85268
|
+
{
|
|
85269
|
+
type: "button",
|
|
85270
|
+
onClick: () => handleModeChange("camera"),
|
|
85271
|
+
"aria-pressed": activeMode === "camera",
|
|
85272
|
+
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"}`,
|
|
85273
|
+
children: "Camera"
|
|
85274
|
+
}
|
|
85275
|
+
),
|
|
85276
|
+
/* @__PURE__ */ jsx(
|
|
85277
|
+
"button",
|
|
85278
|
+
{
|
|
85279
|
+
type: "button",
|
|
85280
|
+
onClick: () => handleModeChange("light"),
|
|
85281
|
+
"aria-pressed": activeMode === "light",
|
|
85282
|
+
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"}`,
|
|
85283
|
+
children: "Light"
|
|
85284
|
+
}
|
|
85285
|
+
)
|
|
85286
|
+
] }),
|
|
85287
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: activeMode === "camera" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84474
85288
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
84475
85289
|
/* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${workspace.status === "healthy" ? "bg-emerald-500 animate-pulse" : workspace.status === "warning" ? "bg-amber-500" : "bg-rose-500"}` }),
|
|
84476
85290
|
/* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${workspace.status === "healthy" ? "text-emerald-700" : workspace.status === "warning" ? "text-amber-700" : "text-rose-700"}`, children: workspace.status === "healthy" ? "Operational" : workspace.status === "warning" ? "Intermittent" : "Down" })
|
|
@@ -84487,17 +85301,37 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84487
85301
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-600 dark:text-slate-300 select-all", children: workspace.cameraIp })
|
|
84488
85302
|
] })
|
|
84489
85303
|
] })
|
|
84490
|
-
] })
|
|
85304
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85305
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
85306
|
+
/* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${lightStatus === "up" ? "bg-emerald-500 animate-pulse" : lightStatus === "unknown" ? "bg-amber-500" : "bg-rose-500"}` }),
|
|
85307
|
+
/* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${lightStatus === "up" ? "text-emerald-700" : lightStatus === "unknown" ? "text-amber-700" : "text-rose-700"}`, children: lightStatusText })
|
|
85308
|
+
] }),
|
|
85309
|
+
lightTimeline?.bulbIp && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85310
|
+
/* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
85311
|
+
/* @__PURE__ */ 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: [
|
|
85312
|
+
/* @__PURE__ */ jsx(Lightbulb, { className: "h-3 w-3 text-slate-400" }),
|
|
85313
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-600 select-all", children: lightTimeline.bulbIp })
|
|
85314
|
+
] })
|
|
85315
|
+
] }),
|
|
85316
|
+
lightDurationText && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85317
|
+
/* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
85318
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: lightDurationText })
|
|
85319
|
+
] }),
|
|
85320
|
+
lightLastError && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85321
|
+
/* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
85322
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-rose-700", children: lightLastError })
|
|
85323
|
+
] })
|
|
85324
|
+
] }) })
|
|
84491
85325
|
] }),
|
|
84492
85326
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
84493
85327
|
/* @__PURE__ */ jsxs(
|
|
84494
85328
|
"button",
|
|
84495
85329
|
{
|
|
84496
|
-
onClick:
|
|
84497
|
-
disabled:
|
|
85330
|
+
onClick: handleRefresh,
|
|
85331
|
+
disabled: activeLoading,
|
|
84498
85332
|
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",
|
|
84499
85333
|
children: [
|
|
84500
|
-
/* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${
|
|
85334
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${activeLoading ? "animate-spin" : ""}` }),
|
|
84501
85335
|
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Refresh" })
|
|
84502
85336
|
]
|
|
84503
85337
|
}
|
|
@@ -84513,31 +85347,63 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84513
85347
|
)
|
|
84514
85348
|
] })
|
|
84515
85349
|
] }),
|
|
84516
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-8 py-6 space-y-6", children:
|
|
85350
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-8 py-6 space-y-6", children: activeError ? /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-rose-100 bg-rose-50 p-5 text-sm text-rose-700", children: [
|
|
84517
85351
|
/* @__PURE__ */ jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
|
|
84518
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-rose-600/90", children:
|
|
85352
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-rose-600/90", children: activeError.message })
|
|
84519
85353
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84520
85354
|
/* @__PURE__ */ jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
|
|
84521
85355
|
/* @__PURE__ */ jsx(
|
|
84522
85356
|
UptimeTimelineStrip_default,
|
|
84523
85357
|
{
|
|
84524
|
-
points:
|
|
84525
|
-
totalMinutes:
|
|
84526
|
-
shiftStart:
|
|
84527
|
-
shiftEnd:
|
|
85358
|
+
points: activeTimeline?.points || [],
|
|
85359
|
+
totalMinutes: activeTimeline?.totalMinutes || 0,
|
|
85360
|
+
shiftStart: activeTimeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
|
|
85361
|
+
shiftEnd: activeTimeline?.shiftEnd || (/* @__PURE__ */ new Date()).toISOString(),
|
|
84528
85362
|
timezone,
|
|
84529
85363
|
uptimePercentage,
|
|
84530
|
-
downtimeMinutes: hasTimelineData ? downtimeMinutes : 0
|
|
85364
|
+
downtimeMinutes: activeMode === "light" ? 0 : hasTimelineData ? downtimeMinutes : 0,
|
|
85365
|
+
metricLabel: activeMode === "light" ? "light uptime" : "uptime",
|
|
85366
|
+
summaryText: activeMode === "light" && typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85367
|
+
uptimePercentage.toFixed(1),
|
|
85368
|
+
" % light uptime",
|
|
85369
|
+
(lightTimeline?.downSeconds || 0) > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
85370
|
+
" (",
|
|
85371
|
+
formatSecondsDuration(lightTimeline?.downSeconds || 0),
|
|
85372
|
+
" offline)"
|
|
85373
|
+
] }),
|
|
85374
|
+
(lightTimeline?.unknownSeconds || 0) > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
85375
|
+
" (",
|
|
85376
|
+
formatSecondsDuration(lightTimeline?.unknownSeconds || 0),
|
|
85377
|
+
" unknown)"
|
|
85378
|
+
] })
|
|
85379
|
+
] }) : void 0
|
|
84531
85380
|
}
|
|
84532
85381
|
),
|
|
84533
|
-
|
|
85382
|
+
activeLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
|
|
84534
85383
|
/* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
|
|
84535
85384
|
/* @__PURE__ */ jsx("span", { children: "Updating timeline\u2026" })
|
|
84536
85385
|
] })
|
|
84537
85386
|
] }),
|
|
84538
85387
|
/* @__PURE__ */ jsxs("div", { className: "pt-4", children: [
|
|
84539
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
|
|
84540
|
-
!hasTimelineData ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No
|
|
85388
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: activeMode === "light" ? "Light Status Logs" : "Downtime Logs" }),
|
|
85389
|
+
activeMode === "light" ? !hasTimelineData ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No light status data available for this shift." }) }) : !lightTimeline?.statusSegments.length ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No light down or unknown events recorded for this shift." }) }) : /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
85390
|
+
/* @__PURE__ */ jsx(
|
|
85391
|
+
"div",
|
|
85392
|
+
{
|
|
85393
|
+
ref: logsContainerRef,
|
|
85394
|
+
className: "max-h-[400px] overflow-y-auto space-y-2 pr-2",
|
|
85395
|
+
style: {
|
|
85396
|
+
scrollbarWidth: "thin",
|
|
85397
|
+
scrollbarColor: "#CBD5E0 #F7FAFC"
|
|
85398
|
+
},
|
|
85399
|
+
children: lightTimeline.statusSegments.map((segment) => renderLightSegment(segment))
|
|
85400
|
+
}
|
|
85401
|
+
),
|
|
85402
|
+
showScrollIndicator && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white via-white/80 to-transparent pointer-events-none flex items-end justify-center pb-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-gray-500 animate-bounce", children: [
|
|
85403
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Scroll for more" }),
|
|
85404
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
|
|
85405
|
+
] }) })
|
|
85406
|
+
] }) : !hasTimelineData ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No uptime data available for this shift." }) }) : downtimeSegments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No downtime events recorded for this shift." }) }) : /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
84541
85407
|
/* @__PURE__ */ jsx(
|
|
84542
85408
|
"div",
|
|
84543
85409
|
{
|
|
@@ -84584,6 +85450,7 @@ var WorkspaceHealthView = ({
|
|
|
84584
85450
|
const timezone = useAppTimezone();
|
|
84585
85451
|
const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(effectiveLineIdForShiftConfig);
|
|
84586
85452
|
const [selectedWorkspace, setSelectedWorkspace] = useState(null);
|
|
85453
|
+
const [selectedTimelineMode, setSelectedTimelineMode] = useState("camera");
|
|
84587
85454
|
const [selectedDate, setSelectedDate] = useState(void 0);
|
|
84588
85455
|
const [selectedShiftId, setSelectedShiftId] = useState(void 0);
|
|
84589
85456
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
|
@@ -84643,11 +85510,33 @@ var WorkspaceHealthView = ({
|
|
|
84643
85510
|
},
|
|
84644
85511
|
[router, onNavigate]
|
|
84645
85512
|
);
|
|
84646
|
-
const handleViewDetails = useCallback((workspace) => {
|
|
85513
|
+
const handleViewDetails = useCallback((workspace, mode = "camera", source = "card_button") => {
|
|
85514
|
+
const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary?.bulbIp);
|
|
85515
|
+
if (source === "light_chip") {
|
|
85516
|
+
trackCoreEvent("Health Light Chip Clicked", {
|
|
85517
|
+
workspace_id: workspace.workspace_id,
|
|
85518
|
+
line_id: workspace.line_id,
|
|
85519
|
+
light_status: workspace.lightSummary?.currentStatus || null,
|
|
85520
|
+
uptime_percent: workspace.lightSummary?.uptimePercent ?? null,
|
|
85521
|
+
bulb_ip_present: Boolean(workspace.lightSummary?.bulbIp)
|
|
85522
|
+
});
|
|
85523
|
+
}
|
|
85524
|
+
trackCoreEvent("Health Timeline Opened", {
|
|
85525
|
+
source,
|
|
85526
|
+
initial_mode: mode,
|
|
85527
|
+
workspace_id: workspace.workspace_id,
|
|
85528
|
+
line_id: workspace.line_id,
|
|
85529
|
+
has_light_config: hasLightConfig,
|
|
85530
|
+
light_status: workspace.lightSummary?.currentStatus || null,
|
|
85531
|
+
selected_date: selectedDate || operationalDate,
|
|
85532
|
+
selected_shift_id: selectedShiftId ?? currentShiftDetails.shiftId
|
|
85533
|
+
});
|
|
85534
|
+
setSelectedTimelineMode(mode);
|
|
84647
85535
|
setSelectedWorkspace(workspace);
|
|
84648
|
-
}, []);
|
|
85536
|
+
}, [currentShiftDetails.shiftId, operationalDate, selectedDate, selectedShiftId]);
|
|
84649
85537
|
const handleCloseDetails = useCallback(() => {
|
|
84650
85538
|
setSelectedWorkspace(null);
|
|
85539
|
+
setSelectedTimelineMode("camera");
|
|
84651
85540
|
}, []);
|
|
84652
85541
|
const getStatusIcon = (status) => {
|
|
84653
85542
|
switch (status) {
|
|
@@ -84905,7 +85794,8 @@ var WorkspaceHealthView = ({
|
|
|
84905
85794
|
onClose: handleCloseDetails,
|
|
84906
85795
|
shiftConfig: modalShiftConfig,
|
|
84907
85796
|
date: selectedDate,
|
|
84908
|
-
shiftId: selectedShiftId
|
|
85797
|
+
shiftId: selectedShiftId,
|
|
85798
|
+
initialMode: selectedTimelineMode
|
|
84909
85799
|
}
|
|
84910
85800
|
)
|
|
84911
85801
|
] });
|
|
@@ -93158,4 +94048,4 @@ var RecentFlowSnapshotGrid = ({
|
|
|
93158
94048
|
);
|
|
93159
94049
|
};
|
|
93160
94050
|
|
|
93161
|
-
export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPI_SIGNAL_LABELS, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LineOvertakeNotificationManager, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProductionPlanApiError, ProductionPlanView_default as ProductionPlanView, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RecentFlowSnapshotGrid, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SENTRY_HANDLED_EVENT_SESSION_LIMIT, SENTRY_HANDLED_EVENT_WINDOW_MS, SENTRY_QUOTA_STORAGE_KEY, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, addSentryBreadcrumb, aggregateKPIsFromLineMetricsRows, aggregateLineSignals, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildKpiLineHierarchy, buildLegacyLineOvertakeEventKey, buildLineLeaderboardRows, buildLineOvertakeEventKey, buildLineSkuBreakdown, buildShiftGroupsKey, canPermissionEditProductionPlan, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleEditProductionPlan, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, detectLineOvertakeEvents, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getKpiSignalLabel, getKpiSignalStatus, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeDateKeyRangeUnbounded, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, productionPlanService, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSentryQuotaForTests, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useCompanyWorkspaceHourAiSummaryEnabled, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceHourSummary, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
|
|
94051
|
+
export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPI_SIGNAL_LABELS, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LineOvertakeNotificationManager, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProductionPlanApiError, ProductionPlanView_default as ProductionPlanView, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RecentFlowSnapshotGrid, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SENTRY_HANDLED_EVENT_SESSION_LIMIT, SENTRY_HANDLED_EVENT_WINDOW_MS, SENTRY_QUOTA_STORAGE_KEY, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, addSentryBreadcrumb, aggregateKPIsFromLineMetricsRows, aggregateLineSignals, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildKpiLineHierarchy, buildLegacyLineOvertakeEventKey, buildLineLeaderboardRows, buildLineOvertakeEventKey, buildLineSkuBreakdown, buildShiftGroupsKey, canPermissionEditProductionPlan, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleEditProductionPlan, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, detectLineOvertakeEvents, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getKpiSignalLabel, getKpiSignalStatus, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeDateKeyRangeUnbounded, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, productionPlanService, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSentryQuotaForTests, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useCompanyWorkspaceHourAiSummaryEnabled, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceHourSummary, useWorkspaceLightTimeline, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
|