@optifye/dashboard-core 6.12.49 → 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 +26 -0
- package/dist/index.d.mts +120 -14
- package/dist/index.d.ts +120 -14
- package/dist/index.js +1519 -373
- package/dist/index.mjs +1520 -375
- 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
|
}
|
|
@@ -13791,12 +14349,22 @@ var stripSeconds = (timeStr) => {
|
|
|
13791
14349
|
if (!timeStr) return timeStr;
|
|
13792
14350
|
return timeStr.substring(0, 5);
|
|
13793
14351
|
};
|
|
14352
|
+
var normalizeOffDays = (value) => {
|
|
14353
|
+
if (!value) return [];
|
|
14354
|
+
if (Array.isArray(value)) {
|
|
14355
|
+
return value.map((day) => String(day || "").trim().toLowerCase()).filter(Boolean);
|
|
14356
|
+
}
|
|
14357
|
+
if (Array.isArray(value.off_days)) return normalizeOffDays(value.off_days);
|
|
14358
|
+
if (Array.isArray(value.offDays)) return normalizeOffDays(value.offDays);
|
|
14359
|
+
return [];
|
|
14360
|
+
};
|
|
13794
14361
|
var buildShiftConfigFromOperatingHoursRows = (rows, fallback) => {
|
|
13795
14362
|
const mapped = (rows || []).map((row) => ({
|
|
13796
14363
|
shiftId: row.shift_id,
|
|
13797
14364
|
shiftName: row.shift_name || `Shift ${row.shift_id}`,
|
|
13798
14365
|
startTime: stripSeconds(row.start_time),
|
|
13799
14366
|
endTime: stripSeconds(row.end_time),
|
|
14367
|
+
offDays: normalizeOffDays(row.off_days),
|
|
13800
14368
|
breaks: (() => {
|
|
13801
14369
|
const raw = Array.isArray(row.breaks) ? row.breaks : Array.isArray(row.breaks?.breaks) ? row.breaks.breaks : [];
|
|
13802
14370
|
return raw.map((b) => ({
|
|
@@ -13856,7 +14424,7 @@ var fetchAndStoreShiftConfig = async (supabase, lineId, fallback) => {
|
|
|
13856
14424
|
if (existing) return existing;
|
|
13857
14425
|
const promise = (async () => {
|
|
13858
14426
|
try {
|
|
13859
|
-
const { data, error } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").eq("line_id", lineId);
|
|
14427
|
+
const { data, error } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone, off_days").eq("line_id", lineId);
|
|
13860
14428
|
if (error) {
|
|
13861
14429
|
throw new Error(`Failed to fetch shift config: ${error.message}`);
|
|
13862
14430
|
}
|
|
@@ -14549,7 +15117,7 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
|
14549
15117
|
setError(null);
|
|
14550
15118
|
}
|
|
14551
15119
|
console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${missingLineIds.length} lines`);
|
|
14552
|
-
const { data, error: fetchError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", missingLineIds);
|
|
15120
|
+
const { data, error: fetchError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone, off_days").in("line_id", missingLineIds);
|
|
14553
15121
|
if (fetchError) {
|
|
14554
15122
|
console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
|
|
14555
15123
|
throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
|
|
@@ -20765,7 +21333,8 @@ var useWorkspaceUptimeTimeline = (options) => {
|
|
|
20765
21333
|
effectiveShiftConfig,
|
|
20766
21334
|
effectiveTimezone,
|
|
20767
21335
|
overrideDate,
|
|
20768
|
-
overrideShiftId
|
|
21336
|
+
overrideShiftId,
|
|
21337
|
+
lineId
|
|
20769
21338
|
);
|
|
20770
21339
|
setTimeline(data);
|
|
20771
21340
|
} catch (err) {
|
|
@@ -20775,7 +21344,89 @@ var useWorkspaceUptimeTimeline = (options) => {
|
|
|
20775
21344
|
setLoading(false);
|
|
20776
21345
|
isFetchingRef.current = false;
|
|
20777
21346
|
}
|
|
20778
|
-
}, [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]);
|
|
20779
21430
|
useEffect(() => {
|
|
20780
21431
|
fetchTimeline();
|
|
20781
21432
|
}, [fetchTimeline]);
|
|
@@ -37779,7 +38430,12 @@ var HourlyOutputChartComponent = ({
|
|
|
37779
38430
|
}, [idleBarState.visible, idleBarState.key, idleBarState.shouldAnimate]);
|
|
37780
38431
|
const maxDataValue = Math.max(...data, 0);
|
|
37781
38432
|
const numericChartTargets = chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target));
|
|
37782
|
-
const
|
|
38433
|
+
const hasAuthoritativeNumericTargets = hasHourlyTargetOutputProp && numericChartTargets.length > 0;
|
|
38434
|
+
const maxTargetValue = Math.max(
|
|
38435
|
+
...numericChartTargets,
|
|
38436
|
+
hasAuthoritativeNumericTargets ? 0 : pphThreshold,
|
|
38437
|
+
0
|
|
38438
|
+
);
|
|
37783
38439
|
const maxYValue = Math.max(
|
|
37784
38440
|
Math.ceil(maxTargetValue * 1.5),
|
|
37785
38441
|
Math.ceil(maxDataValue * 1.15)
|
|
@@ -37934,14 +38590,9 @@ var HourlyOutputChartComponent = ({
|
|
|
37934
38590
|
return /* @__PURE__ */ jsx("g", { children: lines });
|
|
37935
38591
|
}, [hourlyTargetSegments, targetTimelineSegments, SHIFT_DURATION, pphThreshold, targetLineEndOffset, hasHourlyTargetOutputProp]);
|
|
37936
38592
|
const renderLegend = () => {
|
|
37937
|
-
const uniqueTargets = [...new Set(
|
|
37938
|
-
chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
|
|
37939
|
-
)].sort((a, b) => a - b);
|
|
37940
|
-
const unitLabel = hasHourlyTargetOutputProp ? "units" : "units/hr";
|
|
37941
|
-
const targetText = uniqueTargets.length === 0 ? `Target` : uniqueTargets.length === 1 ? `Target: ${uniqueTargets[0]} ${unitLabel}` : `Target: ${uniqueTargets[0]} - ${uniqueTargets[uniqueTargets.length - 1]} ${unitLabel}`;
|
|
37942
38593
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 bg-white py-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
|
|
37943
38594
|
/* @__PURE__ */ jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
|
|
37944
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
38595
|
+
/* @__PURE__ */ jsx("span", { children: "Target" })
|
|
37945
38596
|
] }) });
|
|
37946
38597
|
};
|
|
37947
38598
|
return /* @__PURE__ */ jsxs(
|
|
@@ -52459,6 +53110,106 @@ var getOrdinal = (n) => {
|
|
|
52459
53110
|
const v = n % 100;
|
|
52460
53111
|
return n + (suffix[(v - 20) % 10] || suffix[v] || suffix[0]);
|
|
52461
53112
|
};
|
|
53113
|
+
var resolveDailyTargetOutput = (shiftData) => {
|
|
53114
|
+
if (!shiftData || shiftData.hasData === false) return 0;
|
|
53115
|
+
const target = Number(shiftData?.targetOutput || shiftData?.idealOutput || 0);
|
|
53116
|
+
return Number.isFinite(target) && target > 0 ? target : 0;
|
|
53117
|
+
};
|
|
53118
|
+
var getUniqueRoundedTargets = (data) => Array.from(new Set(
|
|
53119
|
+
data.map((entry) => Number(entry.targetOutput || 0)).filter((target) => Number.isFinite(target) && target > 0).map((target) => Math.round(target))
|
|
53120
|
+
)).sort((a, b) => a - b);
|
|
53121
|
+
var formatDailyTargetLegend = (targets) => {
|
|
53122
|
+
if (targets.length === 0) return "";
|
|
53123
|
+
if (targets.length === 1) return `Target: ${targets[0].toLocaleString()} units/day`;
|
|
53124
|
+
return `Target: ${targets[0].toLocaleString()} - ${targets[targets.length - 1].toLocaleString()} units/day`;
|
|
53125
|
+
};
|
|
53126
|
+
var WEEKDAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
53127
|
+
var getShiftOffDays = (shiftConfig, selectedShiftId) => {
|
|
53128
|
+
const shift = shiftConfig?.shifts?.find((candidate) => candidate.shiftId === selectedShiftId);
|
|
53129
|
+
const raw = shift?.offDays || shift?.off_days || [];
|
|
53130
|
+
return Array.isArray(raw) ? raw.map((day) => String(day || "").trim().toLowerCase()).filter(Boolean) : [];
|
|
53131
|
+
};
|
|
53132
|
+
var isScheduledOffDay = (dateKey, shiftConfig, selectedShiftId) => {
|
|
53133
|
+
const offDays = getShiftOffDays(shiftConfig, selectedShiftId);
|
|
53134
|
+
if (offDays.length === 0) return false;
|
|
53135
|
+
const date = parseDateKeyToDate(dateKey);
|
|
53136
|
+
return offDays.includes(WEEKDAY_NAMES[date.getDay()]);
|
|
53137
|
+
};
|
|
53138
|
+
var renderDailyOutputCapsules = (props, data) => {
|
|
53139
|
+
const { offset, yAxisMap } = props;
|
|
53140
|
+
if (!offset || !yAxisMap || data.length === 0) return null;
|
|
53141
|
+
const { left, width } = offset;
|
|
53142
|
+
const yAxis = yAxisMap.default || yAxisMap[0];
|
|
53143
|
+
if (!Number.isFinite(left) || !Number.isFinite(width) || width <= 0 || !yAxis?.scale) {
|
|
53144
|
+
return null;
|
|
53145
|
+
}
|
|
53146
|
+
const slotWidth = width / data.length;
|
|
53147
|
+
const capsuleWidth = Math.max(10, Math.min(28, slotWidth * 0.44));
|
|
53148
|
+
const radius = capsuleWidth / 2;
|
|
53149
|
+
const baseY = yAxis.scale(0);
|
|
53150
|
+
const capsules = [];
|
|
53151
|
+
data.forEach((entry, index) => {
|
|
53152
|
+
const target = Number(entry.targetOutput || 0);
|
|
53153
|
+
const output = Number(entry.output || 0);
|
|
53154
|
+
if ((!Number.isFinite(target) || target <= 0) && (!Number.isFinite(output) || output <= 0)) return;
|
|
53155
|
+
const x = left + index * slotWidth + (slotWidth - capsuleWidth) / 2;
|
|
53156
|
+
const targetTop = target > 0 ? yAxis.scale(target) : yAxis.scale(output);
|
|
53157
|
+
const capsuleTop = Math.min(targetTop, baseY - 4);
|
|
53158
|
+
const capsuleHeight = Math.max(baseY - capsuleTop, 4);
|
|
53159
|
+
const fillValue = target > 0 ? Math.min(Math.max(output, 0), target) : Math.max(output, 0);
|
|
53160
|
+
const fillTop = fillValue > 0 ? Math.max(yAxis.scale(fillValue), capsuleTop) : baseY;
|
|
53161
|
+
const fillHeight = Math.max(baseY - fillTop, 0);
|
|
53162
|
+
const fillColor = target > 0 ? output >= target ? "#00AB45" : "#E34329" : entry.color || "#6b7280";
|
|
53163
|
+
const trackFill = output >= target ? "#f0fdf4" : "#fff5f3";
|
|
53164
|
+
const trackStroke = output >= target ? "#00AB45" : "#E34329";
|
|
53165
|
+
const clipId = `line-daily-output-capsule-${index}`;
|
|
53166
|
+
capsules.push(
|
|
53167
|
+
/* @__PURE__ */ jsxs("g", { children: [
|
|
53168
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx(
|
|
53169
|
+
"rect",
|
|
53170
|
+
{
|
|
53171
|
+
x,
|
|
53172
|
+
y: capsuleTop,
|
|
53173
|
+
width: capsuleWidth,
|
|
53174
|
+
height: capsuleHeight,
|
|
53175
|
+
rx: radius,
|
|
53176
|
+
ry: radius
|
|
53177
|
+
}
|
|
53178
|
+
) }) }),
|
|
53179
|
+
target > 0 && /* @__PURE__ */ jsx(
|
|
53180
|
+
"rect",
|
|
53181
|
+
{
|
|
53182
|
+
"data-testid": "daily-target-capsule-track",
|
|
53183
|
+
x,
|
|
53184
|
+
y: capsuleTop,
|
|
53185
|
+
width: capsuleWidth,
|
|
53186
|
+
height: capsuleHeight,
|
|
53187
|
+
rx: radius,
|
|
53188
|
+
ry: radius,
|
|
53189
|
+
fill: trackFill,
|
|
53190
|
+
stroke: trackStroke,
|
|
53191
|
+
strokeWidth: 1.5
|
|
53192
|
+
}
|
|
53193
|
+
),
|
|
53194
|
+
/* @__PURE__ */ jsx(
|
|
53195
|
+
"rect",
|
|
53196
|
+
{
|
|
53197
|
+
"data-testid": "daily-output-capsule-fill",
|
|
53198
|
+
x,
|
|
53199
|
+
y: fillTop,
|
|
53200
|
+
width: capsuleWidth,
|
|
53201
|
+
height: fillHeight,
|
|
53202
|
+
rx: fillHeight >= capsuleHeight ? radius : 0,
|
|
53203
|
+
ry: fillHeight >= capsuleHeight ? radius : 0,
|
|
53204
|
+
fill: fillColor,
|
|
53205
|
+
clipPath: target > 0 ? `url(#${clipId})` : void 0
|
|
53206
|
+
}
|
|
53207
|
+
)
|
|
53208
|
+
] }, `daily-output-capsule-${index}`)
|
|
53209
|
+
);
|
|
53210
|
+
});
|
|
53211
|
+
return capsules.length > 0 ? /* @__PURE__ */ jsx("g", { children: capsules }) : null;
|
|
53212
|
+
};
|
|
52462
53213
|
var CustomTooltip2 = ({ active, payload, label, isUptimeMode }) => {
|
|
52463
53214
|
if (!active || !payload || payload.length === 0) return null;
|
|
52464
53215
|
if (isUptimeMode) {
|
|
@@ -52542,6 +53293,7 @@ var LineMonthlyHistory = ({
|
|
|
52542
53293
|
legend,
|
|
52543
53294
|
monitoringMode,
|
|
52544
53295
|
lineAssembly = false,
|
|
53296
|
+
shiftConfig,
|
|
52545
53297
|
underperformingWorkspaces = {},
|
|
52546
53298
|
lineId,
|
|
52547
53299
|
selectedShiftId = 0,
|
|
@@ -52559,6 +53311,13 @@ var LineMonthlyHistory = ({
|
|
|
52559
53311
|
const { isIdleTimeVlmEnabled } = useIdleTimeVlmConfig();
|
|
52560
53312
|
const idleTimeVlmEnabled = isIdleTimeVlmEnabled(lineId);
|
|
52561
53313
|
const isUptimeMode = monitoringMode === "uptime";
|
|
53314
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
53315
|
+
useEffect(() => {
|
|
53316
|
+
const checkMobile = () => setIsMobile(window.innerWidth < 640);
|
|
53317
|
+
checkMobile();
|
|
53318
|
+
window.addEventListener("resize", checkMobile);
|
|
53319
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
53320
|
+
}, []);
|
|
52562
53321
|
const chartKey = useMemo(() => `${lineId}-${month}-${year}-${selectedShiftId}-${rangeStart}-${rangeEnd}`, [lineId, month, year, selectedShiftId, rangeStart, rangeEnd]);
|
|
52563
53322
|
const monthBounds = useMemo(() => getMonthKeyBounds(year, month), [year, month]);
|
|
52564
53323
|
const normalizedRange = useMemo(() => {
|
|
@@ -52720,29 +53479,21 @@ var LineMonthlyHistory = ({
|
|
|
52720
53479
|
});
|
|
52721
53480
|
}
|
|
52722
53481
|
const yAxisMax2 = maxHours > 0 ? 100 : 1;
|
|
52723
|
-
return { data: dailyData2, maxOutput: 0,
|
|
53482
|
+
return { data: dailyData2, maxOutput: 0, targetValues: [], yAxisMax: yAxisMax2, targetLegend: "" };
|
|
52724
53483
|
}
|
|
52725
53484
|
const dailyData = [];
|
|
52726
53485
|
let maxOutput = 0;
|
|
52727
|
-
let
|
|
52728
|
-
for (let i = rangeDateKeys.length - 1; i >= 0; i--) {
|
|
52729
|
-
const dayKey = rangeDateKeys[i];
|
|
52730
|
-
const dayData = analysisMonthlyDataByKey.get(dayKey);
|
|
52731
|
-
const shiftData = dayData ? getShiftData2(dayData, selectedShiftId) : null;
|
|
52732
|
-
const idealOutput = shiftData ? shiftData.idealOutput || 0 : 0;
|
|
52733
|
-
if (idealOutput > 0) {
|
|
52734
|
-
lastSetTarget = idealOutput;
|
|
52735
|
-
break;
|
|
52736
|
-
}
|
|
52737
|
-
}
|
|
53486
|
+
let maxTarget = 0;
|
|
52738
53487
|
for (const dayKey of rangeDateKeys) {
|
|
52739
53488
|
const day = Number(dayKey.slice(-2));
|
|
52740
53489
|
const dayData = analysisMonthlyDataByKey.get(dayKey);
|
|
53490
|
+
const isOffDay = isScheduledOffDay(dayKey, shiftConfig, selectedShiftId);
|
|
52741
53491
|
const shiftData = dayData ? getShiftData2(dayData, selectedShiftId) : null;
|
|
52742
|
-
const output = shiftData && hasRealData(shiftData) ? shiftData.output || 0 : 0;
|
|
52743
|
-
const
|
|
53492
|
+
const output = !isOffDay && shiftData && hasRealData(shiftData) ? shiftData.output || 0 : 0;
|
|
53493
|
+
const targetOutput = isOffDay ? 0 : resolveDailyTargetOutput(shiftData);
|
|
52744
53494
|
if (output > maxOutput) maxOutput = output;
|
|
52745
|
-
|
|
53495
|
+
if (targetOutput > maxTarget) maxTarget = targetOutput;
|
|
53496
|
+
const color2 = targetOutput > 0 && output >= targetOutput ? "#00AB45" : "#E34329";
|
|
52746
53497
|
dailyData.push({
|
|
52747
53498
|
hour: getOrdinal(day),
|
|
52748
53499
|
// Using ordinal format (1st, 2nd, 3rd, etc.)
|
|
@@ -52750,17 +53501,25 @@ var LineMonthlyHistory = ({
|
|
|
52750
53501
|
output,
|
|
52751
53502
|
originalOutput: output,
|
|
52752
53503
|
// For label display
|
|
52753
|
-
idealOutput,
|
|
53504
|
+
idealOutput: targetOutput,
|
|
53505
|
+
targetOutput,
|
|
52754
53506
|
color: color2
|
|
52755
53507
|
});
|
|
52756
53508
|
}
|
|
52757
|
-
const calculatedMax = Math.max(maxOutput,
|
|
53509
|
+
const calculatedMax = Math.max(maxOutput, maxTarget);
|
|
52758
53510
|
const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
|
|
52759
|
-
|
|
52760
|
-
|
|
53511
|
+
const targetValues = getUniqueRoundedTargets(dailyData);
|
|
53512
|
+
return {
|
|
53513
|
+
data: dailyData,
|
|
53514
|
+
maxOutput,
|
|
53515
|
+
targetValues,
|
|
53516
|
+
yAxisMax,
|
|
53517
|
+
targetLegend: formatDailyTargetLegend(targetValues)
|
|
53518
|
+
};
|
|
53519
|
+
}, [analysisMonthlyDataByKey, normalizedRange.endKey, normalizedRange.startKey, selectedShiftId, isUptimeMode, timezone, shiftConfig]);
|
|
52761
53520
|
const yAxisTicks = useMemo(() => {
|
|
52762
53521
|
const max = chartData.yAxisMax;
|
|
52763
|
-
const
|
|
53522
|
+
const targets = chartData.targetValues || [];
|
|
52764
53523
|
if (!max || max <= 0) return void 0;
|
|
52765
53524
|
const desiredIntervals = 4;
|
|
52766
53525
|
const roughStep = max / desiredIntervals;
|
|
@@ -52774,11 +53533,18 @@ var LineMonthlyHistory = ({
|
|
|
52774
53533
|
for (let v = 0; v <= max; v += step) {
|
|
52775
53534
|
ticks.push(Math.round(v));
|
|
52776
53535
|
}
|
|
52777
|
-
|
|
52778
|
-
ticks.push(Math.round(target));
|
|
52779
|
-
}
|
|
53536
|
+
targets.forEach((target) => ticks.push(Math.round(target)));
|
|
52780
53537
|
return Array.from(new Set(ticks)).filter((v) => v >= 0 && v <= max).sort((a, b) => a - b);
|
|
52781
|
-
}, [chartData.yAxisMax, chartData.
|
|
53538
|
+
}, [chartData.yAxisMax, chartData.targetValues]);
|
|
53539
|
+
const visibleYAxisTicks = useMemo(() => {
|
|
53540
|
+
if (!isMobile || isUptimeMode || !yAxisTicks || yAxisTicks.length <= 3) return yAxisTicks;
|
|
53541
|
+
const importantTicks = /* @__PURE__ */ new Set([
|
|
53542
|
+
0,
|
|
53543
|
+
Math.round(chartData.yAxisMax),
|
|
53544
|
+
...chartData.targetValues || []
|
|
53545
|
+
]);
|
|
53546
|
+
return yAxisTicks.filter((tick) => importantTicks.has(Math.round(tick))).sort((a, b) => a - b).slice(-3);
|
|
53547
|
+
}, [chartData.targetValues, chartData.yAxisMax, isMobile, isUptimeMode, yAxisTicks]);
|
|
52782
53548
|
const pieChartData = useMemo(() => {
|
|
52783
53549
|
if (!isUptimeMode) return [];
|
|
52784
53550
|
const validShifts = (analysisMonthlyData || []).map((day) => getShiftData2(day, selectedShiftId)).filter(
|
|
@@ -53097,36 +53863,35 @@ var LineMonthlyHistory = ({
|
|
|
53097
53863
|
] }),
|
|
53098
53864
|
/* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg sm:rounded-xl shadow-sm border border-gray-100 p-2 sm:p-3 lg:p-4", children: [
|
|
53099
53865
|
/* @__PURE__ */ jsx("h3", { className: "text-xs sm:text-sm font-bold text-gray-700 mb-1 sm:mb-2 text-left", children: isUptimeMode ? "Daily Utilization" : "Daily Output" }),
|
|
53100
|
-
/* @__PURE__ */ jsx("div", { className: "h-[
|
|
53866
|
+
/* @__PURE__ */ jsx("div", { className: "h-[140px] sm:h-[180px] lg:h-[220px]", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
|
|
53101
53867
|
BarChart$1,
|
|
53102
53868
|
{
|
|
53103
53869
|
data: chartData.data,
|
|
53104
|
-
margin: { top: 20, right: 10, bottom: 40, left: 10 },
|
|
53870
|
+
margin: isMobile ? { top: 8, right: 4, bottom: 28, left: 0 } : { top: 20, right: 10, bottom: 40, left: 10 },
|
|
53105
53871
|
children: [
|
|
53106
53872
|
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#f3f4f6" }),
|
|
53107
53873
|
/* @__PURE__ */ jsx(
|
|
53108
53874
|
XAxis,
|
|
53109
53875
|
{
|
|
53110
53876
|
dataKey: "hour",
|
|
53111
|
-
tick: { fontSize: 10, fill: "#6b7280" },
|
|
53112
|
-
interval: 0,
|
|
53877
|
+
tick: { fontSize: isMobile ? 9 : 10, fill: "#6b7280" },
|
|
53878
|
+
interval: isMobile ? 3 : 0,
|
|
53113
53879
|
angle: -45,
|
|
53114
53880
|
textAnchor: "end",
|
|
53115
|
-
height: 60
|
|
53881
|
+
height: isMobile ? 42 : 60
|
|
53116
53882
|
}
|
|
53117
53883
|
),
|
|
53118
53884
|
/* @__PURE__ */ jsx(
|
|
53119
53885
|
YAxis,
|
|
53120
53886
|
{
|
|
53121
53887
|
domain: [0, chartData.yAxisMax],
|
|
53122
|
-
width: 40,
|
|
53123
|
-
ticks: isUptimeMode ? [0, 25, 50, 75, 100] :
|
|
53888
|
+
width: isMobile ? 34 : 40,
|
|
53889
|
+
ticks: isUptimeMode ? [0, 25, 50, 75, 100] : visibleYAxisTicks,
|
|
53124
53890
|
tickFormatter: isUptimeMode ? (value) => `${value}%` : void 0,
|
|
53125
53891
|
tick: isUptimeMode ? void 0 : (props) => {
|
|
53126
53892
|
const { x, y, payload } = props;
|
|
53127
53893
|
const value = Math.round(payload.value);
|
|
53128
|
-
const
|
|
53129
|
-
const isTarget = value === targetValue && targetValue > 0;
|
|
53894
|
+
const isTarget = (chartData.targetValues || []).includes(value);
|
|
53130
53895
|
return /* @__PURE__ */ jsx(
|
|
53131
53896
|
"text",
|
|
53132
53897
|
{
|
|
@@ -53149,15 +53914,7 @@ var LineMonthlyHistory = ({
|
|
|
53149
53914
|
content: (props) => /* @__PURE__ */ jsx(CustomTooltip2, { ...props, isUptimeMode })
|
|
53150
53915
|
}
|
|
53151
53916
|
),
|
|
53152
|
-
!isUptimeMode &&
|
|
53153
|
-
ReferenceLine,
|
|
53154
|
-
{
|
|
53155
|
-
y: chartData.lastSetTarget,
|
|
53156
|
-
stroke: "#E34329",
|
|
53157
|
-
strokeDasharray: "5 5",
|
|
53158
|
-
strokeWidth: 2
|
|
53159
|
-
}
|
|
53160
|
-
),
|
|
53917
|
+
!isUptimeMode && /* @__PURE__ */ jsx(Customized, { component: (props) => renderDailyOutputCapsules(props, chartData.data) }),
|
|
53161
53918
|
isUptimeMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
53162
53919
|
/* @__PURE__ */ jsx(
|
|
53163
53920
|
Bar,
|
|
@@ -53190,6 +53947,8 @@ var LineMonthlyHistory = ({
|
|
|
53190
53947
|
{
|
|
53191
53948
|
dataKey: "output",
|
|
53192
53949
|
radius: [4, 4, 0, 0],
|
|
53950
|
+
fill: "transparent",
|
|
53951
|
+
opacity: 0,
|
|
53193
53952
|
isAnimationActive: true,
|
|
53194
53953
|
animationBegin: 0,
|
|
53195
53954
|
animationDuration: 1e3,
|
|
@@ -53208,13 +53967,9 @@ var LineMonthlyHistory = ({
|
|
|
53208
53967
|
},
|
|
53209
53968
|
chartKey
|
|
53210
53969
|
) }) }),
|
|
53211
|
-
!isUptimeMode && chartData.
|
|
53212
|
-
/* @__PURE__ */ jsx("div", { className: "w-
|
|
53213
|
-
/* @__PURE__ */
|
|
53214
|
-
"Target: ",
|
|
53215
|
-
Math.round(chartData.lastSetTarget),
|
|
53216
|
-
" units/day"
|
|
53217
|
-
] })
|
|
53970
|
+
!isUptimeMode && chartData.targetValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 bg-white py-1 pt-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
|
|
53971
|
+
/* @__PURE__ */ jsx("div", { className: "relative h-5 w-3 rounded-full border border-[#E34329] bg-[#fff5f3] overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 h-1/2 bg-[#E34329]" }) }),
|
|
53972
|
+
/* @__PURE__ */ jsx("span", { children: chartData.targetLegend })
|
|
53218
53973
|
] }) })
|
|
53219
53974
|
] })
|
|
53220
53975
|
] })
|
|
@@ -55137,6 +55892,106 @@ var formatCycleSeconds = (value) => {
|
|
|
55137
55892
|
if (!Number.isFinite(value)) return "0.0s";
|
|
55138
55893
|
return `${value.toFixed(1)}s`;
|
|
55139
55894
|
};
|
|
55895
|
+
var resolveDailyTargetOutput2 = (shiftData) => {
|
|
55896
|
+
if (!shiftData || shiftData.hasData === false) return 0;
|
|
55897
|
+
const target = Number(shiftData?.targetOutput || shiftData?.idealOutput || 0);
|
|
55898
|
+
return Number.isFinite(target) && target > 0 ? target : 0;
|
|
55899
|
+
};
|
|
55900
|
+
var getUniqueRoundedTargets2 = (data) => Array.from(new Set(
|
|
55901
|
+
data.map((entry) => Number(entry.targetOutput || 0)).filter((target) => Number.isFinite(target) && target > 0).map((target) => Math.round(target))
|
|
55902
|
+
)).sort((a, b) => a - b);
|
|
55903
|
+
var formatDailyTargetLegend2 = (targets) => {
|
|
55904
|
+
if (targets.length === 0) return "";
|
|
55905
|
+
if (targets.length === 1) return `Target: ${targets[0].toLocaleString()} units/day`;
|
|
55906
|
+
return `Target: ${targets[0].toLocaleString()} - ${targets[targets.length - 1].toLocaleString()} units/day`;
|
|
55907
|
+
};
|
|
55908
|
+
var WEEKDAY_NAMES2 = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
55909
|
+
var getShiftOffDays2 = (shiftConfig, selectedShiftId) => {
|
|
55910
|
+
const shift = shiftConfig?.shifts?.find((candidate) => candidate.shiftId === selectedShiftId);
|
|
55911
|
+
const raw = shift?.offDays || shift?.off_days || [];
|
|
55912
|
+
return Array.isArray(raw) ? raw.map((day) => String(day || "").trim().toLowerCase()).filter(Boolean) : [];
|
|
55913
|
+
};
|
|
55914
|
+
var isScheduledOffDay2 = (dateKey, shiftConfig, selectedShiftId) => {
|
|
55915
|
+
const offDays = getShiftOffDays2(shiftConfig, selectedShiftId);
|
|
55916
|
+
if (offDays.length === 0) return false;
|
|
55917
|
+
const date = parseDateKeyToDate(dateKey);
|
|
55918
|
+
return offDays.includes(WEEKDAY_NAMES2[date.getDay()]);
|
|
55919
|
+
};
|
|
55920
|
+
var renderDailyOutputCapsules2 = (props, data) => {
|
|
55921
|
+
const { offset, yAxisMap } = props;
|
|
55922
|
+
if (!offset || !yAxisMap || data.length === 0) return null;
|
|
55923
|
+
const { left, width } = offset;
|
|
55924
|
+
const yAxis = yAxisMap.default || yAxisMap[0];
|
|
55925
|
+
if (!Number.isFinite(left) || !Number.isFinite(width) || width <= 0 || !yAxis?.scale) {
|
|
55926
|
+
return null;
|
|
55927
|
+
}
|
|
55928
|
+
const slotWidth = width / data.length;
|
|
55929
|
+
const capsuleWidth = Math.max(10, Math.min(28, slotWidth * 0.44));
|
|
55930
|
+
const radius = capsuleWidth / 2;
|
|
55931
|
+
const baseY = yAxis.scale(0);
|
|
55932
|
+
const capsules = [];
|
|
55933
|
+
data.forEach((entry, index) => {
|
|
55934
|
+
const target = Number(entry.targetOutput || 0);
|
|
55935
|
+
const output = Number(entry.output || 0);
|
|
55936
|
+
if ((!Number.isFinite(target) || target <= 0) && (!Number.isFinite(output) || output <= 0)) return;
|
|
55937
|
+
const x = left + index * slotWidth + (slotWidth - capsuleWidth) / 2;
|
|
55938
|
+
const targetTop = target > 0 ? yAxis.scale(target) : yAxis.scale(output);
|
|
55939
|
+
const capsuleTop = Math.min(targetTop, baseY - 4);
|
|
55940
|
+
const capsuleHeight = Math.max(baseY - capsuleTop, 4);
|
|
55941
|
+
const fillValue = target > 0 ? Math.min(Math.max(output, 0), target) : Math.max(output, 0);
|
|
55942
|
+
const fillTop = fillValue > 0 ? Math.max(yAxis.scale(fillValue), capsuleTop) : baseY;
|
|
55943
|
+
const fillHeight = Math.max(baseY - fillTop, 0);
|
|
55944
|
+
const fillColor = target > 0 ? output >= target ? "#00AB45" : "#E34329" : entry.color || "#6b7280";
|
|
55945
|
+
const trackFill = output >= target ? "#f0fdf4" : "#fff5f3";
|
|
55946
|
+
const trackStroke = output >= target ? "#00AB45" : "#E34329";
|
|
55947
|
+
const clipId = `workspace-daily-output-capsule-${index}`;
|
|
55948
|
+
capsules.push(
|
|
55949
|
+
/* @__PURE__ */ jsxs("g", { children: [
|
|
55950
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx(
|
|
55951
|
+
"rect",
|
|
55952
|
+
{
|
|
55953
|
+
x,
|
|
55954
|
+
y: capsuleTop,
|
|
55955
|
+
width: capsuleWidth,
|
|
55956
|
+
height: capsuleHeight,
|
|
55957
|
+
rx: radius,
|
|
55958
|
+
ry: radius
|
|
55959
|
+
}
|
|
55960
|
+
) }) }),
|
|
55961
|
+
target > 0 && /* @__PURE__ */ jsx(
|
|
55962
|
+
"rect",
|
|
55963
|
+
{
|
|
55964
|
+
"data-testid": "daily-target-capsule-track",
|
|
55965
|
+
x,
|
|
55966
|
+
y: capsuleTop,
|
|
55967
|
+
width: capsuleWidth,
|
|
55968
|
+
height: capsuleHeight,
|
|
55969
|
+
rx: radius,
|
|
55970
|
+
ry: radius,
|
|
55971
|
+
fill: trackFill,
|
|
55972
|
+
stroke: trackStroke,
|
|
55973
|
+
strokeWidth: 1.5
|
|
55974
|
+
}
|
|
55975
|
+
),
|
|
55976
|
+
/* @__PURE__ */ jsx(
|
|
55977
|
+
"rect",
|
|
55978
|
+
{
|
|
55979
|
+
"data-testid": "daily-output-capsule-fill",
|
|
55980
|
+
x,
|
|
55981
|
+
y: fillTop,
|
|
55982
|
+
width: capsuleWidth,
|
|
55983
|
+
height: fillHeight,
|
|
55984
|
+
rx: fillHeight >= capsuleHeight ? radius : 0,
|
|
55985
|
+
ry: fillHeight >= capsuleHeight ? radius : 0,
|
|
55986
|
+
fill: fillColor,
|
|
55987
|
+
clipPath: target > 0 ? `url(#${clipId})` : void 0
|
|
55988
|
+
}
|
|
55989
|
+
)
|
|
55990
|
+
] }, `daily-output-capsule-${index}`)
|
|
55991
|
+
);
|
|
55992
|
+
});
|
|
55993
|
+
return capsules.length > 0 ? /* @__PURE__ */ jsx("g", { children: capsules }) : null;
|
|
55994
|
+
};
|
|
55140
55995
|
var CustomTooltip3 = ({ active, payload, label, isUptimeMode }) => {
|
|
55141
55996
|
if (!active || !payload || payload.length === 0) return null;
|
|
55142
55997
|
if (isUptimeMode) {
|
|
@@ -55316,28 +56171,20 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55316
56171
|
});
|
|
55317
56172
|
}
|
|
55318
56173
|
const yAxisMax2 = maxHours > 0 ? maxHours * 1.1 : 1;
|
|
55319
|
-
return { data: dailyData, yAxisMax: yAxisMax2,
|
|
56174
|
+
return { data: dailyData, yAxisMax: yAxisMax2, targetValues: [], targetLegend: "" };
|
|
55320
56175
|
}
|
|
55321
56176
|
let maxOutput = 0;
|
|
55322
|
-
let
|
|
55323
|
-
for (let i = rangeDateKeys.length - 1; i >= 0; i--) {
|
|
55324
|
-
const dateKey = rangeDateKeys[i];
|
|
55325
|
-
const dayData = analysisMonthlyDataByKey.get(dateKey);
|
|
55326
|
-
const shiftData = dayData ? getShiftData(dayData, selectedShiftId) : null;
|
|
55327
|
-
const idealOutput = shiftData ? shiftData.idealOutput : 0;
|
|
55328
|
-
if (idealOutput > 0) {
|
|
55329
|
-
lastSetTarget = idealOutput;
|
|
55330
|
-
break;
|
|
55331
|
-
}
|
|
55332
|
-
}
|
|
56177
|
+
let maxTarget = 0;
|
|
55333
56178
|
for (const dateKey of rangeDateKeys) {
|
|
55334
56179
|
const dayData = analysisMonthlyDataByKey.get(dateKey);
|
|
55335
56180
|
const dayNumber = Number(dateKey.slice(-2));
|
|
56181
|
+
const isOffDay = isScheduledOffDay2(dateKey, shiftConfig, selectedShiftId);
|
|
55336
56182
|
const shiftData = dayData ? getShiftData(dayData, selectedShiftId) : null;
|
|
55337
|
-
const output = shiftData && hasRealData(shiftData) ? shiftData.output : 0;
|
|
55338
|
-
const
|
|
56183
|
+
const output = !isOffDay && shiftData && hasRealData(shiftData) ? shiftData.output : 0;
|
|
56184
|
+
const targetOutput = isOffDay ? 0 : resolveDailyTargetOutput2(shiftData);
|
|
55339
56185
|
if (output > maxOutput) maxOutput = output;
|
|
55340
|
-
|
|
56186
|
+
if (targetOutput > maxTarget) maxTarget = targetOutput;
|
|
56187
|
+
const color2 = targetOutput > 0 && output >= targetOutput ? "#00AB45" : "#E34329";
|
|
55341
56188
|
dailyData.push({
|
|
55342
56189
|
hour: getOrdinal2(dayNumber),
|
|
55343
56190
|
// Using ordinal format (1st, 2nd, 3rd, etc.)
|
|
@@ -55345,21 +56192,29 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55345
56192
|
output,
|
|
55346
56193
|
originalOutput: output,
|
|
55347
56194
|
// For label display
|
|
55348
|
-
idealOutput,
|
|
56195
|
+
idealOutput: targetOutput,
|
|
56196
|
+
targetOutput,
|
|
55349
56197
|
efficiency: shiftData && hasRealData(shiftData) ? shiftData.efficiency : 0,
|
|
55350
56198
|
color: color2,
|
|
55351
56199
|
idleMinutes: 0
|
|
55352
56200
|
// Not used but keeps structure consistent
|
|
55353
56201
|
});
|
|
55354
56202
|
}
|
|
55355
|
-
const calculatedMax = Math.max(maxOutput,
|
|
56203
|
+
const calculatedMax = Math.max(maxOutput, maxTarget);
|
|
55356
56204
|
const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
|
|
55357
|
-
|
|
55358
|
-
|
|
56205
|
+
const targetValues = getUniqueRoundedTargets2(dailyData);
|
|
56206
|
+
return {
|
|
56207
|
+
data: dailyData,
|
|
56208
|
+
maxOutput,
|
|
56209
|
+
targetValues,
|
|
56210
|
+
yAxisMax,
|
|
56211
|
+
targetLegend: formatDailyTargetLegend2(targetValues)
|
|
56212
|
+
};
|
|
56213
|
+
}, [analysisMonthlyDataByKey, rangeDateKeys, selectedShiftId, isUptimeMode, shiftConfig, shiftWorkSeconds]);
|
|
55359
56214
|
const yAxisTicks = useMemo(() => {
|
|
55360
56215
|
if (isUptimeMode) return void 0;
|
|
55361
56216
|
const max = chartData.yAxisMax;
|
|
55362
|
-
const
|
|
56217
|
+
const targets = chartData.targetValues || [];
|
|
55363
56218
|
if (!max || max <= 0) return void 0;
|
|
55364
56219
|
const desiredIntervals = 4;
|
|
55365
56220
|
const roughStep = max / desiredIntervals;
|
|
@@ -55373,7 +56228,7 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55373
56228
|
for (let v = 0; v <= max; v += step) {
|
|
55374
56229
|
ticks.push(Math.round(v));
|
|
55375
56230
|
}
|
|
55376
|
-
|
|
56231
|
+
targets.forEach((target) => {
|
|
55377
56232
|
const roundedTarget = Math.round(target);
|
|
55378
56233
|
if (!ticks.includes(roundedTarget)) {
|
|
55379
56234
|
let nearestIndex = -1;
|
|
@@ -55392,9 +56247,18 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55392
56247
|
ticks.push(roundedTarget);
|
|
55393
56248
|
}
|
|
55394
56249
|
}
|
|
55395
|
-
}
|
|
56250
|
+
});
|
|
55396
56251
|
return ticks.filter((v) => v >= 0 && v <= max * 1.05).sort((a, b) => a - b);
|
|
55397
|
-
}, [chartData.yAxisMax, chartData.
|
|
56252
|
+
}, [chartData.yAxisMax, chartData.targetValues, isUptimeMode]);
|
|
56253
|
+
const visibleYAxisTicks = useMemo(() => {
|
|
56254
|
+
if (!isMobile || isUptimeMode || !yAxisTicks || yAxisTicks.length <= 3) return yAxisTicks;
|
|
56255
|
+
const importantTicks = /* @__PURE__ */ new Set([
|
|
56256
|
+
0,
|
|
56257
|
+
Math.round(chartData.yAxisMax),
|
|
56258
|
+
...chartData.targetValues || []
|
|
56259
|
+
]);
|
|
56260
|
+
return yAxisTicks.filter((tick) => importantTicks.has(Math.round(tick))).sort((a, b) => a - b).slice(-3);
|
|
56261
|
+
}, [chartData.targetValues, chartData.yAxisMax, isMobile, isUptimeMode, yAxisTicks]);
|
|
55398
56262
|
const pieChartData = useMemo(() => {
|
|
55399
56263
|
const aggregateMode = isUptimeMode ? "uptime" : "output";
|
|
55400
56264
|
const validShifts = analysisMonthlyData.map((d) => getShiftData(d, selectedShiftId)).filter(
|
|
@@ -55784,22 +56648,22 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55784
56648
|
] }),
|
|
55785
56649
|
(!isAssemblyWorkspace || isUptimeMode) && /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-sm border border-gray-100 p-4 flex-1", children: [
|
|
55786
56650
|
/* @__PURE__ */ jsx("h3", { className: "text-lg font-bold text-gray-700 mb-3 text-left", children: isUptimeMode ? "Daily Utilization" : "Daily Output" }),
|
|
55787
|
-
/* @__PURE__ */ jsx("div", { style: { height: "220px" }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
|
|
56651
|
+
/* @__PURE__ */ jsx("div", { style: { height: isMobile ? "150px" : "220px" }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
|
|
55788
56652
|
BarChart$1,
|
|
55789
56653
|
{
|
|
55790
56654
|
data: chartData.data,
|
|
55791
|
-
margin: { top: 20, right: 10, bottom: 40, left: 10 },
|
|
56655
|
+
margin: isMobile ? { top: 8, right: 4, bottom: 28, left: 0 } : { top: 20, right: 10, bottom: 40, left: 10 },
|
|
55792
56656
|
children: [
|
|
55793
56657
|
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#f3f4f6" }),
|
|
55794
56658
|
/* @__PURE__ */ jsx(
|
|
55795
56659
|
XAxis,
|
|
55796
56660
|
{
|
|
55797
56661
|
dataKey: "hour",
|
|
55798
|
-
tick: { fontSize: 10, fill: "#6b7280" },
|
|
55799
|
-
interval: isMobile ?
|
|
56662
|
+
tick: { fontSize: isMobile ? 9 : 10, fill: "#6b7280" },
|
|
56663
|
+
interval: isMobile ? 3 : 0,
|
|
55800
56664
|
angle: -45,
|
|
55801
56665
|
textAnchor: "end",
|
|
55802
|
-
height: 60
|
|
56666
|
+
height: isMobile ? 42 : 60
|
|
55803
56667
|
}
|
|
55804
56668
|
),
|
|
55805
56669
|
isUptimeMode ? /* @__PURE__ */ jsx(
|
|
@@ -55814,13 +56678,12 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55814
56678
|
YAxis,
|
|
55815
56679
|
{
|
|
55816
56680
|
domain: [0, chartData.yAxisMax],
|
|
55817
|
-
width: 40,
|
|
55818
|
-
ticks:
|
|
56681
|
+
width: isMobile ? 34 : 40,
|
|
56682
|
+
ticks: visibleYAxisTicks,
|
|
55819
56683
|
tick: (props) => {
|
|
55820
56684
|
const { x, y, payload } = props;
|
|
55821
56685
|
const value = Math.round(payload.value);
|
|
55822
|
-
const
|
|
55823
|
-
const isTarget = value === targetValue && targetValue > 0;
|
|
56686
|
+
const isTarget = (chartData.targetValues || []).includes(value);
|
|
55824
56687
|
return /* @__PURE__ */ jsx(
|
|
55825
56688
|
"text",
|
|
55826
56689
|
{
|
|
@@ -55843,15 +56706,7 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55843
56706
|
content: (props) => /* @__PURE__ */ jsx(CustomTooltip3, { ...props, isUptimeMode })
|
|
55844
56707
|
}
|
|
55845
56708
|
),
|
|
55846
|
-
!isUptimeMode &&
|
|
55847
|
-
ReferenceLine,
|
|
55848
|
-
{
|
|
55849
|
-
y: chartData.lastSetTarget,
|
|
55850
|
-
stroke: "#E34329",
|
|
55851
|
-
strokeDasharray: "5 5",
|
|
55852
|
-
strokeWidth: 2
|
|
55853
|
-
}
|
|
55854
|
-
),
|
|
56709
|
+
!isUptimeMode && /* @__PURE__ */ jsx(Customized, { component: (props) => renderDailyOutputCapsules2(props, chartData.data) }),
|
|
55855
56710
|
isUptimeMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
55856
56711
|
/* @__PURE__ */ jsx(
|
|
55857
56712
|
Bar,
|
|
@@ -55884,6 +56739,8 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55884
56739
|
{
|
|
55885
56740
|
dataKey: "output",
|
|
55886
56741
|
radius: [4, 4, 0, 0],
|
|
56742
|
+
fill: "transparent",
|
|
56743
|
+
opacity: 0,
|
|
55887
56744
|
isAnimationActive: true,
|
|
55888
56745
|
animationBegin: 0,
|
|
55889
56746
|
animationDuration: 1e3,
|
|
@@ -55927,13 +56784,9 @@ var WorkspaceMonthlyHistory = ({
|
|
|
55927
56784
|
/* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full", style: { backgroundColor: "#e5e7eb" } }),
|
|
55928
56785
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600", children: "Idle" })
|
|
55929
56786
|
] })
|
|
55930
|
-
] }) : chartData.
|
|
55931
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
55932
|
-
/* @__PURE__ */
|
|
55933
|
-
"Target: ",
|
|
55934
|
-
Math.round(chartData.lastSetTarget),
|
|
55935
|
-
" units/day"
|
|
55936
|
-
] })
|
|
56787
|
+
] }) : chartData.targetValues.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
56788
|
+
/* @__PURE__ */ jsx("div", { className: "relative h-5 w-3 rounded-full border border-[#E34329] bg-[#fff5f3] overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 h-1/2 bg-[#E34329]" }) }),
|
|
56789
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600", children: chartData.targetLegend })
|
|
55937
56790
|
] }) })
|
|
55938
56791
|
] })
|
|
55939
56792
|
] })
|
|
@@ -58000,7 +58853,14 @@ var WorkspaceHealthCard = ({
|
|
|
58000
58853
|
event.stopPropagation();
|
|
58001
58854
|
event.preventDefault();
|
|
58002
58855
|
if (onViewDetails) {
|
|
58003
|
-
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");
|
|
58004
58864
|
}
|
|
58005
58865
|
};
|
|
58006
58866
|
const handleKeyDown = (event) => {
|
|
@@ -58078,6 +58938,22 @@ var WorkspaceHealthCard = ({
|
|
|
58078
58938
|
};
|
|
58079
58939
|
};
|
|
58080
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
|
+
);
|
|
58081
58957
|
return /* @__PURE__ */ jsx(
|
|
58082
58958
|
Card2,
|
|
58083
58959
|
{
|
|
@@ -58147,6 +59023,20 @@ var WorkspaceHealthCard = ({
|
|
|
58147
59023
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Camera IP:" }),
|
|
58148
59024
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "N/A" })
|
|
58149
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
|
+
),
|
|
58150
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: [
|
|
58151
59041
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: downtimeConfig.label }),
|
|
58152
59042
|
/* @__PURE__ */ jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
|
|
@@ -58216,7 +59106,7 @@ var CompactWorkspaceHealthCard = ({
|
|
|
58216
59106
|
event.stopPropagation();
|
|
58217
59107
|
event.preventDefault();
|
|
58218
59108
|
if (onViewDetails) {
|
|
58219
|
-
onViewDetails(workspace);
|
|
59109
|
+
onViewDetails(workspace, "camera", "card_button");
|
|
58220
59110
|
}
|
|
58221
59111
|
};
|
|
58222
59112
|
return /* @__PURE__ */ jsxs(
|
|
@@ -67563,6 +68453,17 @@ var setSessionSeenValue = (key) => {
|
|
|
67563
68453
|
};
|
|
67564
68454
|
var buildAllGreenCelebrationSeenKey = (identity) => `${ALL_GREEN_CELEBRATION_SEEN_PREFIX}${identity}`;
|
|
67565
68455
|
var buildAllGreenMilestoneSeenKey = (identity, milestoneSeconds) => `${ALL_GREEN_MILESTONE_SEEN_PREFIX}${identity}:${milestoneSeconds}`;
|
|
68456
|
+
var LINE_SELECTOR_INDICATOR_VERSION = "incident_exclamation_v1";
|
|
68457
|
+
var LineSelectorIncidentIcon = ({ lineId }) => /* @__PURE__ */ jsx(
|
|
68458
|
+
"span",
|
|
68459
|
+
{
|
|
68460
|
+
"data-testid": `line-selector-incident-icon-${lineId}`,
|
|
68461
|
+
"aria-label": "Line needs attention",
|
|
68462
|
+
role: "img",
|
|
68463
|
+
className: "inline-flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full border border-rose-200 bg-white text-[13px] font-semibold leading-none text-rose-600 shadow-[0_1px_2px_rgba(15,23,42,0.06)]",
|
|
68464
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "-mt-px", children: "!" })
|
|
68465
|
+
}
|
|
68466
|
+
);
|
|
67566
68467
|
var LoadingPageCmp = LoadingPage_default;
|
|
67567
68468
|
var LoadingOverlayCmp = LoadingOverlay_default;
|
|
67568
68469
|
function HomeView({
|
|
@@ -67902,25 +68803,49 @@ function HomeView({
|
|
|
67902
68803
|
const currentIsCurrentScopeResolved = isBootstrapMonitorMode ? bootstrapMonitor.isCurrentScopeResolved : legacyIsCurrentScopeResolved;
|
|
67903
68804
|
const currentMetricsError = isBootstrapMonitorMode ? bootstrapMonitor.error : legacyMetricsError;
|
|
67904
68805
|
const currentRefetchMetrics = isBootstrapMonitorMode ? bootstrapMonitor.refetch : refetchLegacyMetrics;
|
|
67905
|
-
const
|
|
67906
|
-
const
|
|
68806
|
+
const lineSelectorIndicatorByLine = useMemo(() => {
|
|
68807
|
+
const indicatorByLine = /* @__PURE__ */ new Map();
|
|
67907
68808
|
const legend = currentEfficiencyLegend || DEFAULT_EFFICIENCY_LEGEND;
|
|
67908
68809
|
const addRows = (rows) => {
|
|
67909
68810
|
(rows || []).forEach((row) => {
|
|
67910
68811
|
const lineId = typeof row?.line_id === "string" ? row.line_id : "";
|
|
67911
|
-
if (!lineId
|
|
68812
|
+
if (!lineId) {
|
|
68813
|
+
return;
|
|
68814
|
+
}
|
|
68815
|
+
if (row?.red_flow_incident?.active === true) {
|
|
68816
|
+
indicatorByLine.set(lineId, "incident");
|
|
68817
|
+
return;
|
|
68818
|
+
}
|
|
68819
|
+
if (indicatorByLine.get(lineId) === "incident") {
|
|
67912
68820
|
return;
|
|
67913
68821
|
}
|
|
67914
68822
|
const status = getKpiSignalStatus(row?.line_signal, legend);
|
|
67915
|
-
if (status) {
|
|
67916
|
-
|
|
68823
|
+
if (status === "attention") {
|
|
68824
|
+
indicatorByLine.set(lineId, "red");
|
|
67917
68825
|
}
|
|
67918
68826
|
});
|
|
67919
68827
|
};
|
|
67920
68828
|
addRows(currentSelectorLineMetrics);
|
|
67921
68829
|
addRows(currentLineMetrics);
|
|
67922
|
-
return
|
|
68830
|
+
return indicatorByLine;
|
|
67923
68831
|
}, [currentEfficiencyLegend, currentLineMetrics, currentSelectorLineMetrics]);
|
|
68832
|
+
const lineSelectorIndicatorStats = useMemo(() => {
|
|
68833
|
+
let incidentLineCount = 0;
|
|
68834
|
+
let redFlowLineCount = 0;
|
|
68835
|
+
visibleLineIds.forEach((lineId) => {
|
|
68836
|
+
const indicator = lineSelectorIndicatorByLine.get(lineId);
|
|
68837
|
+
if (indicator === "incident") {
|
|
68838
|
+
incidentLineCount += 1;
|
|
68839
|
+
} else if (indicator === "red") {
|
|
68840
|
+
redFlowLineCount += 1;
|
|
68841
|
+
}
|
|
68842
|
+
});
|
|
68843
|
+
return {
|
|
68844
|
+
incidentLineCount,
|
|
68845
|
+
redFlowLineCount,
|
|
68846
|
+
hasAnyIncident: incidentLineCount > 0
|
|
68847
|
+
};
|
|
68848
|
+
}, [lineSelectorIndicatorByLine, visibleLineIds]);
|
|
67924
68849
|
const metricsDisplayNames = useMemo(() => {
|
|
67925
68850
|
const nextDisplayNames = {};
|
|
67926
68851
|
currentWorkspaceMetrics.forEach((workspace) => {
|
|
@@ -68777,9 +69702,12 @@ function HomeView({
|
|
|
68777
69702
|
new_line_ids: normalizedLineIds,
|
|
68778
69703
|
selected_line_count: normalizedLineIds.length,
|
|
68779
69704
|
selection_mode: isAllLinesSelection(normalizedLineIds) ? "all" : normalizedLineIds.length === 1 ? "single" : "custom",
|
|
69705
|
+
incident_line_count: lineSelectorIndicatorStats.incidentLineCount,
|
|
69706
|
+
red_flow_line_count: lineSelectorIndicatorStats.redFlowLineCount,
|
|
69707
|
+
selector_indicator_version: LINE_SELECTOR_INDICATOR_VERSION,
|
|
68780
69708
|
line_name: getLineSelectionLabel(normalizedLineIds)
|
|
68781
69709
|
});
|
|
68782
|
-
}, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
|
|
69710
|
+
}, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, lineSelectorIndicatorStats, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
|
|
68783
69711
|
useCallback(() => {
|
|
68784
69712
|
updateSelectedLineIds(visibleLineIds);
|
|
68785
69713
|
}, [updateSelectedLineIds, visibleLineIds]);
|
|
@@ -68837,7 +69765,10 @@ function HomeView({
|
|
|
68837
69765
|
current_display_mode_label: getHomeDisplayModeLabel(displayMode),
|
|
68838
69766
|
selected_line_ids: selectedLineIds,
|
|
68839
69767
|
selected_line_count: selectedLineIds.length,
|
|
68840
|
-
is_all_lines: isAllLinesSelection(selectedLineIds)
|
|
69768
|
+
is_all_lines: isAllLinesSelection(selectedLineIds),
|
|
69769
|
+
incident_line_count: lineSelectorIndicatorStats.incidentLineCount,
|
|
69770
|
+
red_flow_line_count: lineSelectorIndicatorStats.redFlowLineCount,
|
|
69771
|
+
selector_indicator_version: LINE_SELECTOR_INDICATOR_VERSION
|
|
68841
69772
|
});
|
|
68842
69773
|
}
|
|
68843
69774
|
},
|
|
@@ -68878,8 +69809,8 @@ function HomeView({
|
|
|
68878
69809
|
] }),
|
|
68879
69810
|
/* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-0.5 overflow-y-auto pr-1", children: visibleLineIds.map((lineId) => {
|
|
68880
69811
|
const isChecked = pendingSelectedLineIds.includes(lineId);
|
|
68881
|
-
const
|
|
68882
|
-
const
|
|
69812
|
+
const selectorIndicator = lineSelectorIndicatorByLine.get(lineId);
|
|
69813
|
+
const lineLabel = mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}`;
|
|
68883
69814
|
return /* @__PURE__ */ jsxs(
|
|
68884
69815
|
"label",
|
|
68885
69816
|
{
|
|
@@ -68889,6 +69820,7 @@ function HomeView({
|
|
|
68889
69820
|
"input",
|
|
68890
69821
|
{
|
|
68891
69822
|
type: "checkbox",
|
|
69823
|
+
"aria-label": lineLabel,
|
|
68892
69824
|
className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
|
|
68893
69825
|
checked: isChecked,
|
|
68894
69826
|
onChange: () => {
|
|
@@ -68904,12 +69836,12 @@ function HomeView({
|
|
|
68904
69836
|
}
|
|
68905
69837
|
}
|
|
68906
69838
|
),
|
|
68907
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children:
|
|
68908
|
-
/* @__PURE__ */ jsx("span", { className: "flex h-
|
|
69839
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: lineLabel }),
|
|
69840
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center", children: selectorIndicator === "incident" ? /* @__PURE__ */ jsx(LineSelectorIncidentIcon, { lineId }) : selectorIndicator === "red" && !lineSelectorIndicatorStats.hasAnyIncident ? /* @__PURE__ */ jsx(
|
|
68909
69841
|
"span",
|
|
68910
69842
|
{
|
|
68911
69843
|
"data-testid": `line-selector-signal-dot-${lineId}`,
|
|
68912
|
-
className:
|
|
69844
|
+
className: "h-2 w-2 rounded-full bg-red-500",
|
|
68913
69845
|
"aria-hidden": "true"
|
|
68914
69846
|
}
|
|
68915
69847
|
) : null })
|
|
@@ -68941,7 +69873,8 @@ function HomeView({
|
|
|
68941
69873
|
mergedLineNames,
|
|
68942
69874
|
selectedLineIds,
|
|
68943
69875
|
pendingSelectedLineIds,
|
|
68944
|
-
|
|
69876
|
+
lineSelectorIndicatorByLine,
|
|
69877
|
+
lineSelectorIndicatorStats,
|
|
68945
69878
|
displayMode,
|
|
68946
69879
|
slideshowActiveLineId,
|
|
68947
69880
|
visibleLineIds,
|
|
@@ -83902,11 +84835,13 @@ var useWorkspaceHealth = (options) => {
|
|
|
83902
84835
|
var STATUS_COLORS = {
|
|
83903
84836
|
up: "bg-emerald-500",
|
|
83904
84837
|
down: "bg-rose-500",
|
|
84838
|
+
unknown: "bg-amber-400",
|
|
83905
84839
|
pending: "bg-gray-200"
|
|
83906
84840
|
};
|
|
83907
84841
|
var STATUS_TITLES = {
|
|
83908
84842
|
up: "Uptime",
|
|
83909
84843
|
down: "Downtime",
|
|
84844
|
+
unknown: "Unknown",
|
|
83910
84845
|
pending: "Pending"
|
|
83911
84846
|
};
|
|
83912
84847
|
var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
|
|
@@ -83949,7 +84884,9 @@ var UptimeTimelineStrip = ({
|
|
|
83949
84884
|
timezone,
|
|
83950
84885
|
className = "",
|
|
83951
84886
|
uptimePercentage = null,
|
|
83952
|
-
downtimeMinutes = 0
|
|
84887
|
+
downtimeMinutes = 0,
|
|
84888
|
+
metricLabel = "uptime",
|
|
84889
|
+
summaryText
|
|
83953
84890
|
}) => {
|
|
83954
84891
|
const segments = useMemo(() => {
|
|
83955
84892
|
if (!points.length || totalMinutes <= 0) return [];
|
|
@@ -84013,9 +84950,11 @@ var UptimeTimelineStrip = ({
|
|
|
84013
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." });
|
|
84014
84951
|
}
|
|
84015
84952
|
return /* @__PURE__ */ jsxs("div", { className: `relative w-full ${className}`, children: [
|
|
84016
|
-
/* @__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: [
|
|
84017
84954
|
uptimePercentage.toFixed(1),
|
|
84018
|
-
" %
|
|
84955
|
+
" % ",
|
|
84956
|
+
metricLabel,
|
|
84957
|
+
" ",
|
|
84019
84958
|
downtimeMinutes > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
|
|
84020
84959
|
"(",
|
|
84021
84960
|
formatDowntimeLabel(downtimeMinutes),
|
|
@@ -84083,6 +85022,27 @@ var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
|
|
|
84083
85022
|
const label = formatDuration4(minutes);
|
|
84084
85023
|
return includeSuffix ? `${label} down` : label;
|
|
84085
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
|
+
};
|
|
84086
85046
|
var formatTimeRange = (start, end, timezone) => {
|
|
84087
85047
|
const formatter = new Intl.DateTimeFormat("en-IN", {
|
|
84088
85048
|
hour: "numeric",
|
|
@@ -84098,12 +85058,15 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84098
85058
|
onClose,
|
|
84099
85059
|
shiftConfig: passedShiftConfig,
|
|
84100
85060
|
date,
|
|
84101
|
-
shiftId
|
|
85061
|
+
shiftId,
|
|
85062
|
+
initialMode = "camera"
|
|
84102
85063
|
}) => {
|
|
84103
85064
|
const timezone = useAppTimezone() || "UTC";
|
|
84104
85065
|
const logsContainerRef = useRef(null);
|
|
84105
85066
|
const [showScrollIndicator, setShowScrollIndicator] = useState(false);
|
|
85067
|
+
const [activeMode, setActiveMode] = useState(initialMode);
|
|
84106
85068
|
const isHistorical = Boolean(date);
|
|
85069
|
+
const hasLightTimeline = Boolean(workspace?.lightSummary?.hasLightConfig && workspace?.lightSummary?.bulbIp);
|
|
84107
85070
|
const {
|
|
84108
85071
|
timeline,
|
|
84109
85072
|
loading,
|
|
@@ -84112,7 +85075,7 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84112
85075
|
} = useWorkspaceUptimeTimeline({
|
|
84113
85076
|
workspaceId: workspace?.workspace_id,
|
|
84114
85077
|
companyId: workspace?.company_id,
|
|
84115
|
-
enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
|
|
85078
|
+
enabled: isOpen && activeMode === "camera" && Boolean(workspace?.workspace_id && workspace?.company_id),
|
|
84116
85079
|
refreshInterval: isHistorical ? void 0 : 6e4,
|
|
84117
85080
|
// Disable auto-refresh for historical
|
|
84118
85081
|
lineId: workspace?.line_id,
|
|
@@ -84125,6 +85088,25 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84125
85088
|
shiftId
|
|
84126
85089
|
// Pass override shift ID for historical queries
|
|
84127
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]);
|
|
84128
85110
|
useEffect(() => {
|
|
84129
85111
|
if (!isOpen || !workspace) return;
|
|
84130
85112
|
const handleKeyDown = (event) => {
|
|
@@ -84137,13 +85119,21 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84137
85119
|
window.removeEventListener("keydown", handleKeyDown);
|
|
84138
85120
|
};
|
|
84139
85121
|
}, [isOpen, onClose, workspace]);
|
|
84140
|
-
const
|
|
84141
|
-
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;
|
|
84142
85128
|
const downtimeSegments = timeline?.downtimeSegments || [];
|
|
84143
85129
|
downtimeSegments.length;
|
|
84144
85130
|
const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
|
|
84145
|
-
const hasTimelineData = Boolean(timeline?.hasData);
|
|
84146
|
-
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);
|
|
84147
85137
|
const allInterruptionsSorted = useMemo(
|
|
84148
85138
|
() => [...downtimeSegments].sort(
|
|
84149
85139
|
(a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
|
|
@@ -84165,7 +85155,7 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84165
85155
|
container.addEventListener("scroll", checkScroll);
|
|
84166
85156
|
return () => container.removeEventListener("scroll", checkScroll);
|
|
84167
85157
|
}
|
|
84168
|
-
}, [downtimeSegments]);
|
|
85158
|
+
}, [downtimeSegments, lightTimeline?.statusSegments, activeMode]);
|
|
84169
85159
|
const renderSegment = (segment) => {
|
|
84170
85160
|
const start = new Date(segment.startTime);
|
|
84171
85161
|
const end = new Date(segment.endTime);
|
|
@@ -84186,6 +85176,63 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84186
85176
|
`${segment.startMinuteIndex}-${segment.endMinuteIndex}`
|
|
84187
85177
|
);
|
|
84188
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
|
+
};
|
|
84189
85236
|
if (!isOpen || !workspace) {
|
|
84190
85237
|
return null;
|
|
84191
85238
|
}
|
|
@@ -84209,13 +85256,35 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84209
85256
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
|
|
84210
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)}` }),
|
|
84211
85258
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
|
|
84212
|
-
/* @__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" }),
|
|
84213
85260
|
shiftStart && shiftEnd && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84214
85261
|
/* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
|
|
84215
85262
|
/* @__PURE__ */ jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
|
|
84216
85263
|
] })
|
|
84217
85264
|
] }),
|
|
84218
|
-
/* @__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: [
|
|
84219
85288
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
84220
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"}` }),
|
|
84221
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" })
|
|
@@ -84232,17 +85301,37 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84232
85301
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-600 dark:text-slate-300 select-all", children: workspace.cameraIp })
|
|
84233
85302
|
] })
|
|
84234
85303
|
] })
|
|
84235
|
-
] })
|
|
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
|
+
] }) })
|
|
84236
85325
|
] }),
|
|
84237
85326
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
84238
85327
|
/* @__PURE__ */ jsxs(
|
|
84239
85328
|
"button",
|
|
84240
85329
|
{
|
|
84241
|
-
onClick:
|
|
84242
|
-
disabled:
|
|
85330
|
+
onClick: handleRefresh,
|
|
85331
|
+
disabled: activeLoading,
|
|
84243
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",
|
|
84244
85333
|
children: [
|
|
84245
|
-
/* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${
|
|
85334
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${activeLoading ? "animate-spin" : ""}` }),
|
|
84246
85335
|
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Refresh" })
|
|
84247
85336
|
]
|
|
84248
85337
|
}
|
|
@@ -84258,31 +85347,63 @@ var WorkspaceUptimeDetailModal = ({
|
|
|
84258
85347
|
)
|
|
84259
85348
|
] })
|
|
84260
85349
|
] }),
|
|
84261
|
-
/* @__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: [
|
|
84262
85351
|
/* @__PURE__ */ jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
|
|
84263
|
-
/* @__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 })
|
|
84264
85353
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84265
85354
|
/* @__PURE__ */ jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
|
|
84266
85355
|
/* @__PURE__ */ jsx(
|
|
84267
85356
|
UptimeTimelineStrip_default,
|
|
84268
85357
|
{
|
|
84269
|
-
points:
|
|
84270
|
-
totalMinutes:
|
|
84271
|
-
shiftStart:
|
|
84272
|
-
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(),
|
|
84273
85362
|
timezone,
|
|
84274
85363
|
uptimePercentage,
|
|
84275
|
-
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
|
|
84276
85380
|
}
|
|
84277
85381
|
),
|
|
84278
|
-
|
|
85382
|
+
activeLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
|
|
84279
85383
|
/* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
|
|
84280
85384
|
/* @__PURE__ */ jsx("span", { children: "Updating timeline\u2026" })
|
|
84281
85385
|
] })
|
|
84282
85386
|
] }),
|
|
84283
85387
|
/* @__PURE__ */ jsxs("div", { className: "pt-4", children: [
|
|
84284
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
|
|
84285
|
-
!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: [
|
|
84286
85407
|
/* @__PURE__ */ jsx(
|
|
84287
85408
|
"div",
|
|
84288
85409
|
{
|
|
@@ -84329,6 +85450,7 @@ var WorkspaceHealthView = ({
|
|
|
84329
85450
|
const timezone = useAppTimezone();
|
|
84330
85451
|
const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(effectiveLineIdForShiftConfig);
|
|
84331
85452
|
const [selectedWorkspace, setSelectedWorkspace] = useState(null);
|
|
85453
|
+
const [selectedTimelineMode, setSelectedTimelineMode] = useState("camera");
|
|
84332
85454
|
const [selectedDate, setSelectedDate] = useState(void 0);
|
|
84333
85455
|
const [selectedShiftId, setSelectedShiftId] = useState(void 0);
|
|
84334
85456
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
|
@@ -84388,11 +85510,33 @@ var WorkspaceHealthView = ({
|
|
|
84388
85510
|
},
|
|
84389
85511
|
[router, onNavigate]
|
|
84390
85512
|
);
|
|
84391
|
-
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);
|
|
84392
85535
|
setSelectedWorkspace(workspace);
|
|
84393
|
-
}, []);
|
|
85536
|
+
}, [currentShiftDetails.shiftId, operationalDate, selectedDate, selectedShiftId]);
|
|
84394
85537
|
const handleCloseDetails = useCallback(() => {
|
|
84395
85538
|
setSelectedWorkspace(null);
|
|
85539
|
+
setSelectedTimelineMode("camera");
|
|
84396
85540
|
}, []);
|
|
84397
85541
|
const getStatusIcon = (status) => {
|
|
84398
85542
|
switch (status) {
|
|
@@ -84650,7 +85794,8 @@ var WorkspaceHealthView = ({
|
|
|
84650
85794
|
onClose: handleCloseDetails,
|
|
84651
85795
|
shiftConfig: modalShiftConfig,
|
|
84652
85796
|
date: selectedDate,
|
|
84653
|
-
shiftId: selectedShiftId
|
|
85797
|
+
shiftId: selectedShiftId,
|
|
85798
|
+
initialMode: selectedTimelineMode
|
|
84654
85799
|
}
|
|
84655
85800
|
)
|
|
84656
85801
|
] });
|
|
@@ -92903,4 +94048,4 @@ var RecentFlowSnapshotGrid = ({
|
|
|
92903
94048
|
);
|
|
92904
94049
|
};
|
|
92905
94050
|
|
|
92906
|
-
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 };
|