@optifye/dashboard-core 6.12.50 → 6.12.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
- normalizeHourBucket(bucket) {
6766
- if (Array.isArray(bucket)) return bucket;
6767
- if (bucket && Array.isArray(bucket.values)) return bucket.values;
6768
- return void 0;
6769
- }
6770
- normalizeOutputHourly(outputHourlyRaw) {
6771
- return outputHourlyRaw && typeof outputHourlyRaw === "object" ? Object.fromEntries(
6772
- Object.entries(outputHourlyRaw).map(([key, value]) => [key, this.normalizeHourBucket(value) || []])
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: this.normalizeOutputHourly(record.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
- let sum = 0;
6817
- for (const arr of arrays) {
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
- interpretUptimeValue(value) {
6832
- if (value === null || value === void 0) return "down";
6833
- if (typeof value === "string") {
6834
- return value.trim().toLowerCase() === "x" ? "down" : "up";
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 "up";
7125
+ return duration;
6837
7126
  }
6838
- deriveStatusForMinute(minuteOffset, minuteDate, outputHourly, outputArray, timezone) {
6839
- const hourKey = formatInTimeZone(minuteDate, timezone, "H");
6840
- const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
6841
- const hourBucket = outputHourly[hourKey];
6842
- if (Array.isArray(hourBucket)) {
6843
- const value = hourBucket[minuteKey];
6844
- if (value !== void 0) {
6845
- return this.interpretUptimeValue(value);
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
- if (minuteOffset < outputArray.length) {
6849
- return this.interpretUptimeValue(outputArray[minuteOffset]);
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 "down";
7289
+ return Array.isArray(data) ? data : [];
6852
7290
  }
6853
- hasCompletedTimelineData(completedMinutes, shiftStartDate, outputHourly, outputArray, timezone) {
6854
- if (completedMinutes <= 0) {
6855
- return false;
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
- for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex++) {
6858
- const minuteDate = addMinutes(shiftStartDate, minuteIndex);
6859
- const hourKey = formatInTimeZone(minuteDate, timezone, "H");
6860
- const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
6861
- const hourBucket = outputHourly[hourKey];
6862
- if (Array.isArray(hourBucket) && hourBucket[minuteKey] !== void 0) {
6863
- return true;
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
- if (minuteIndex < outputArray.length) {
6866
- return true;
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
- return false;
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 = this.normalizeOutputHourly(record?.output_hourly || {});
7724
+ const outputHourly = normalizeOutputHourly(record?.output_hourly || {});
7065
7725
  const outputArray = Array.isArray(record?.output_array) ? record.output_array : [];
7066
- const hasData = Boolean(
7067
- record && this.hasCompletedTimelineData(
7068
- completedMinutes,
7069
- shiftStartDate,
7070
- outputHourly,
7071
- outputArray,
7072
- timezone
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: filteredSegments
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 = this.normalizeOutputHourly(record.output_hourly || {});
7933
+ const outputHourly = normalizeOutputHourly(record.output_hourly || {});
7339
7934
  const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
7340
- let uptimeMinutes = 0;
7341
- let downtimeMinutes = 0;
7342
- const MIN_DOWNTIME_MINUTES = 2;
7343
- let currentDownRun = 0;
7344
- for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex++) {
7345
- const minuteDate = addMinutes(shiftStartDate, minuteIndex);
7346
- const status = this.deriveStatusForMinute(
7347
- minuteIndex,
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 = this.normalizeOutputHourly(record.output_hourly || {});
8071
+ const outputHourly = normalizeOutputHourly(record.output_hourly || {});
7486
8072
  const outputArray = Array.isArray(record.output_array) ? record.output_array : [];
7487
- let uptimeMinutes = 0;
7488
- let downtimeMinutes = 0;
7489
- const MIN_DOWNTIME_MINUTES = 2;
7490
- let currentDownRun = 0;
7491
- for (let minuteIndex = 0; minuteIndex < completedMinutes; minuteIndex++) {
7492
- const minuteDate = addMinutes(shiftStartDate, minuteIndex);
7493
- const status = this.deriveStatusForMinute(
7494
- minuteIndex,
7495
- minuteDate,
7496
- outputHourly,
7497
- outputArray,
7498
- timezone
7499
- );
7500
- if (status === "down") {
7501
- currentDownRun += 1;
7502
- } else {
7503
- if (currentDownRun > 0) {
7504
- if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
7505
- downtimeMinutes += currentDownRun;
7506
- } else {
7507
- uptimeMinutes += currentDownRun;
7508
- }
7509
- currentDownRun = 0;
7510
- }
7511
- uptimeMinutes += 1;
7512
- }
7513
- }
7514
- if (currentDownRun > 0) {
7515
- if (currentDownRun >= MIN_DOWNTIME_MINUTES) {
7516
- downtimeMinutes += currentDownRun;
7517
- } else {
7518
- uptimeMinutes += currentDownRun;
7519
- }
7520
- }
7521
- const completedWindow = uptimeMinutes + downtimeMinutes;
7522
- const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
8073
+ const uptime = computeWorkspaceUptime({
8074
+ totalMinutes: completedMinutes,
8075
+ completedMinutes,
8076
+ shiftStartDate,
8077
+ outputHourly,
8078
+ outputArray,
8079
+ timezone
8080
+ });
7523
8081
  const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
7524
8082
  uptimeMap.set(mapKey, {
7525
8083
  expectedMinutes: completedMinutes,
7526
- actualMinutes: uptimeMinutes,
7527
- percentage,
8084
+ actualMinutes: uptime.uptimeMinutes,
8085
+ percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100,
7528
8086
  lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
7529
8087
  });
7530
8088
  console.log(`[calculateWorkspaceUptimeMultiLine] Storing uptime:`, {
@@ -7533,8 +8091,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
7533
8091
  workspaceId: record.workspace_id,
7534
8092
  workspaceDisplayName: record.workspace_display_name,
7535
8093
  expectedMinutes: completedMinutes,
7536
- actualMinutes: uptimeMinutes,
7537
- percentage
8094
+ actualMinutes: uptime.uptimeMinutes,
8095
+ percentage: completedMinutes > 0 ? uptime.uptimePercentage : 100
7538
8096
  });
7539
8097
  }
7540
8098
  }
@@ -20775,7 +21333,8 @@ var useWorkspaceUptimeTimeline = (options) => {
20775
21333
  effectiveShiftConfig,
20776
21334
  effectiveTimezone,
20777
21335
  overrideDate,
20778
- overrideShiftId
21336
+ overrideShiftId,
21337
+ lineId
20779
21338
  );
20780
21339
  setTimeline(data);
20781
21340
  } catch (err) {
@@ -20785,7 +21344,89 @@ var useWorkspaceUptimeTimeline = (options) => {
20785
21344
  setLoading(false);
20786
21345
  isFetchingRef.current = false;
20787
21346
  }
20788
- }, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
21347
+ }, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId, lineId]);
21348
+ useEffect(() => {
21349
+ fetchTimeline();
21350
+ }, [fetchTimeline]);
21351
+ useEffect(() => {
21352
+ if (!refreshInterval || refreshInterval <= 0 || !enabled) {
21353
+ return;
21354
+ }
21355
+ intervalRef.current = setInterval(() => {
21356
+ fetchTimeline();
21357
+ }, refreshInterval);
21358
+ return () => {
21359
+ if (intervalRef.current) {
21360
+ clearInterval(intervalRef.current);
21361
+ }
21362
+ };
21363
+ }, [refreshInterval, enabled, fetchTimeline]);
21364
+ return {
21365
+ timeline,
21366
+ loading: loading || shiftConfigPending,
21367
+ error,
21368
+ refetch: fetchTimeline
21369
+ };
21370
+ };
21371
+ var useWorkspaceLightTimeline = (options) => {
21372
+ const {
21373
+ workspaceId,
21374
+ lineId,
21375
+ enabled = true,
21376
+ refreshInterval,
21377
+ shiftConfig: passedShiftConfig,
21378
+ timezone: passedTimezone,
21379
+ date: overrideDate,
21380
+ shiftId: overrideShiftId
21381
+ } = options;
21382
+ const { shiftConfig: dynamicShiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
21383
+ const appTimezone = useAppTimezone();
21384
+ const shiftConfigPending = !passedShiftConfig && (!lineId || isShiftConfigLoading || !dynamicShiftConfig);
21385
+ const effectiveShiftConfig = passedShiftConfig ?? (shiftConfigPending ? void 0 : dynamicShiftConfig);
21386
+ const effectiveTimezone = passedTimezone || appTimezone || effectiveShiftConfig?.timezone || "UTC";
21387
+ const [timeline, setTimeline] = useState(null);
21388
+ const [loading, setLoading] = useState(false);
21389
+ const [error, setError] = useState(null);
21390
+ const isFetchingRef = useRef(false);
21391
+ const intervalRef = useRef(null);
21392
+ useEffect(() => {
21393
+ setTimeline(null);
21394
+ setError(null);
21395
+ setLoading(enabled && Boolean(workspaceId));
21396
+ }, [workspaceId, lineId, enabled]);
21397
+ const fetchTimeline = useCallback(async () => {
21398
+ if (!enabled) return;
21399
+ if (shiftConfigPending) {
21400
+ setLoading(true);
21401
+ return;
21402
+ }
21403
+ if (!effectiveShiftConfig || !workspaceId) {
21404
+ setLoading(false);
21405
+ setTimeline(null);
21406
+ return;
21407
+ }
21408
+ if (isFetchingRef.current) return;
21409
+ try {
21410
+ isFetchingRef.current = true;
21411
+ setLoading(true);
21412
+ setError(null);
21413
+ const data = await workspaceHealthService.getWorkspaceLightTimeline(
21414
+ workspaceId,
21415
+ lineId,
21416
+ effectiveShiftConfig,
21417
+ effectiveTimezone,
21418
+ overrideDate,
21419
+ overrideShiftId
21420
+ );
21421
+ setTimeline(data);
21422
+ } catch (err) {
21423
+ console.error("[useWorkspaceLightTimeline] Failed to fetch light timeline:", err);
21424
+ setError({ message: err?.message || "Failed to load light timeline", code: err?.code || "FETCH_ERROR" });
21425
+ } finally {
21426
+ setLoading(false);
21427
+ isFetchingRef.current = false;
21428
+ }
21429
+ }, [enabled, workspaceId, lineId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
20789
21430
  useEffect(() => {
20790
21431
  fetchTimeline();
20791
21432
  }, [fetchTimeline]);
@@ -58212,7 +58853,14 @@ var WorkspaceHealthCard = ({
58212
58853
  event.stopPropagation();
58213
58854
  event.preventDefault();
58214
58855
  if (onViewDetails) {
58215
- onViewDetails(workspace);
58856
+ onViewDetails(workspace, "camera", "card_button");
58857
+ }
58858
+ };
58859
+ const handleLightChipClick = (event) => {
58860
+ event.stopPropagation();
58861
+ event.preventDefault();
58862
+ if (onViewDetails) {
58863
+ onViewDetails(workspace, "light", "light_chip");
58216
58864
  }
58217
58865
  };
58218
58866
  const handleKeyDown = (event) => {
@@ -58290,6 +58938,22 @@ var WorkspaceHealthCard = ({
58290
58938
  };
58291
58939
  };
58292
58940
  const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
58941
+ const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary.bulbIp);
58942
+ const lightStatus = workspace.lightSummary?.currentStatus || null;
58943
+ const lightChipLabel = (() => {
58944
+ if (!workspace.lightSummary) return "Light --";
58945
+ if (lightStatus === "down") return "Light offline";
58946
+ if (lightStatus === "unknown") return "Light unknown";
58947
+ if (typeof workspace.lightSummary.uptimePercent === "number") {
58948
+ return `Light ${workspace.lightSummary.uptimePercent.toFixed(1)}%`;
58949
+ }
58950
+ if (lightStatus === "up") return "Light operational";
58951
+ return "Light --";
58952
+ })();
58953
+ const lightChipClassName = clsx(
58954
+ "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-semibold transition-all",
58955
+ lightStatus === "down" ? "border-rose-200 bg-rose-50 text-rose-700 hover:bg-rose-100" : lightStatus === "unknown" ? "border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100" : "border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100"
58956
+ );
58293
58957
  return /* @__PURE__ */ jsx(
58294
58958
  Card2,
58295
58959
  {
@@ -58359,6 +59023,20 @@ var WorkspaceHealthCard = ({
58359
59023
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Camera IP:" }),
58360
59024
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "N/A" })
58361
59025
  ] }),
59026
+ hasLightConfig && /* @__PURE__ */ jsxs(
59027
+ "button",
59028
+ {
59029
+ type: "button",
59030
+ onClick: handleLightChipClick,
59031
+ className: lightChipClassName,
59032
+ "aria-label": `Open light timeline for ${workspace.workspace_display_name || workspace.workspace_id}`,
59033
+ title: workspace.lightSummary?.bulbIp ? `Bulb IP ${workspace.lightSummary.bulbIp}` : "Light timeline",
59034
+ children: [
59035
+ /* @__PURE__ */ jsx(Lightbulb, { className: "h-3.5 w-3.5" }),
59036
+ /* @__PURE__ */ jsx("span", { children: lightChipLabel })
59037
+ ]
59038
+ }
59039
+ ),
58362
59040
  /* @__PURE__ */ jsx("div", { className: "mt-3 pt-3 border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
58363
59041
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: downtimeConfig.label }),
58364
59042
  /* @__PURE__ */ jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
@@ -58428,7 +59106,7 @@ var CompactWorkspaceHealthCard = ({
58428
59106
  event.stopPropagation();
58429
59107
  event.preventDefault();
58430
59108
  if (onViewDetails) {
58431
- onViewDetails(workspace);
59109
+ onViewDetails(workspace, "camera", "card_button");
58432
59110
  }
58433
59111
  };
58434
59112
  return /* @__PURE__ */ jsxs(
@@ -84157,11 +84835,13 @@ var useWorkspaceHealth = (options) => {
84157
84835
  var STATUS_COLORS = {
84158
84836
  up: "bg-emerald-500",
84159
84837
  down: "bg-rose-500",
84838
+ unknown: "bg-amber-400",
84160
84839
  pending: "bg-gray-200"
84161
84840
  };
84162
84841
  var STATUS_TITLES = {
84163
84842
  up: "Uptime",
84164
84843
  down: "Downtime",
84844
+ unknown: "Unknown",
84165
84845
  pending: "Pending"
84166
84846
  };
84167
84847
  var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
@@ -84204,7 +84884,9 @@ var UptimeTimelineStrip = ({
84204
84884
  timezone,
84205
84885
  className = "",
84206
84886
  uptimePercentage = null,
84207
- downtimeMinutes = 0
84887
+ downtimeMinutes = 0,
84888
+ metricLabel = "uptime",
84889
+ summaryText
84208
84890
  }) => {
84209
84891
  const segments = useMemo(() => {
84210
84892
  if (!points.length || totalMinutes <= 0) return [];
@@ -84268,9 +84950,11 @@ var UptimeTimelineStrip = ({
84268
84950
  return /* @__PURE__ */ jsx("div", { className: "w-full rounded-xl border border-dashed border-gray-200 bg-gray-50/50 p-6 text-center text-sm text-gray-600", children: "No uptime data available for this shift yet." });
84269
84951
  }
84270
84952
  return /* @__PURE__ */ jsxs("div", { className: `relative w-full ${className}`, children: [
84271
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs("span", { className: "text-gray-900 font-semibold", children: [
84953
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: summaryText ? /* @__PURE__ */ jsx("span", { className: "text-gray-900 font-semibold", children: summaryText }) : typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs("span", { className: "text-gray-900 font-semibold", children: [
84272
84954
  uptimePercentage.toFixed(1),
84273
- " % uptime ",
84955
+ " % ",
84956
+ metricLabel,
84957
+ " ",
84274
84958
  downtimeMinutes > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
84275
84959
  "(",
84276
84960
  formatDowntimeLabel(downtimeMinutes),
@@ -84338,6 +85022,27 @@ var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
84338
85022
  const label = formatDuration4(minutes);
84339
85023
  return includeSuffix ? `${label} down` : label;
84340
85024
  };
85025
+ var formatSecondsDuration = (seconds) => {
85026
+ if (!seconds || seconds <= 0) return "0 min";
85027
+ return formatDuration4(Math.max(1, Math.ceil(seconds / 60)));
85028
+ };
85029
+ var getLightStatusLabel = (status) => {
85030
+ if (status === "up") return "operational";
85031
+ if (status === "down") return "offline";
85032
+ if (status === "unknown") return "unknown";
85033
+ return "status unavailable";
85034
+ };
85035
+ var getLightDurationPrefix = (status) => {
85036
+ if (status === "up") return "Operational";
85037
+ if (status === "down") return "Offline";
85038
+ if (status === "unknown") return "Unknown";
85039
+ return "Current";
85040
+ };
85041
+ var formatLightError = (error) => {
85042
+ if (!error) return null;
85043
+ const cleaned = error.replace(/\s*\(after retry\)\s*$/i, "").trim();
85044
+ return cleaned || null;
85045
+ };
84341
85046
  var formatTimeRange = (start, end, timezone) => {
84342
85047
  const formatter = new Intl.DateTimeFormat("en-IN", {
84343
85048
  hour: "numeric",
@@ -84353,12 +85058,15 @@ var WorkspaceUptimeDetailModal = ({
84353
85058
  onClose,
84354
85059
  shiftConfig: passedShiftConfig,
84355
85060
  date,
84356
- shiftId
85061
+ shiftId,
85062
+ initialMode = "camera"
84357
85063
  }) => {
84358
85064
  const timezone = useAppTimezone() || "UTC";
84359
85065
  const logsContainerRef = useRef(null);
84360
85066
  const [showScrollIndicator, setShowScrollIndicator] = useState(false);
85067
+ const [activeMode, setActiveMode] = useState(initialMode);
84361
85068
  const isHistorical = Boolean(date);
85069
+ const hasLightTimeline = Boolean(workspace?.lightSummary?.hasLightConfig && workspace?.lightSummary?.bulbIp);
84362
85070
  const {
84363
85071
  timeline,
84364
85072
  loading,
@@ -84367,7 +85075,7 @@ var WorkspaceUptimeDetailModal = ({
84367
85075
  } = useWorkspaceUptimeTimeline({
84368
85076
  workspaceId: workspace?.workspace_id,
84369
85077
  companyId: workspace?.company_id,
84370
- enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
85078
+ enabled: isOpen && activeMode === "camera" && Boolean(workspace?.workspace_id && workspace?.company_id),
84371
85079
  refreshInterval: isHistorical ? void 0 : 6e4,
84372
85080
  // Disable auto-refresh for historical
84373
85081
  lineId: workspace?.line_id,
@@ -84380,6 +85088,25 @@ var WorkspaceUptimeDetailModal = ({
84380
85088
  shiftId
84381
85089
  // Pass override shift ID for historical queries
84382
85090
  });
85091
+ const {
85092
+ timeline: lightTimeline,
85093
+ loading: lightLoading,
85094
+ error: lightError,
85095
+ refetch: refetchLight
85096
+ } = useWorkspaceLightTimeline({
85097
+ workspaceId: workspace?.workspace_id,
85098
+ enabled: isOpen && activeMode === "light" && hasLightTimeline && Boolean(workspace?.workspace_id),
85099
+ refreshInterval: isHistorical ? void 0 : 6e4,
85100
+ lineId: workspace?.line_id,
85101
+ shiftConfig: passedShiftConfig || void 0,
85102
+ timezone,
85103
+ date,
85104
+ shiftId
85105
+ });
85106
+ useEffect(() => {
85107
+ if (!isOpen) return;
85108
+ setActiveMode(initialMode === "light" && hasLightTimeline ? "light" : "camera");
85109
+ }, [hasLightTimeline, initialMode, isOpen]);
84383
85110
  useEffect(() => {
84384
85111
  if (!isOpen || !workspace) return;
84385
85112
  const handleKeyDown = (event) => {
@@ -84392,13 +85119,21 @@ var WorkspaceUptimeDetailModal = ({
84392
85119
  window.removeEventListener("keydown", handleKeyDown);
84393
85120
  };
84394
85121
  }, [isOpen, onClose, workspace]);
84395
- const shiftStart = timeline ? new Date(timeline.shiftStart) : null;
84396
- const shiftEnd = timeline ? new Date(timeline.shiftEnd) : null;
85122
+ const activeTimeline = activeMode === "light" ? lightTimeline : timeline;
85123
+ const activeLoading = activeMode === "light" ? lightLoading : loading;
85124
+ const activeError = activeMode === "light" ? lightError : error;
85125
+ const activeRefetch = activeMode === "light" ? refetchLight : refetch;
85126
+ const shiftStart = activeTimeline ? new Date(activeTimeline.shiftStart) : null;
85127
+ const shiftEnd = activeTimeline ? new Date(activeTimeline.shiftEnd) : null;
84397
85128
  const downtimeSegments = timeline?.downtimeSegments || [];
84398
85129
  downtimeSegments.length;
84399
85130
  const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
84400
- const hasTimelineData = Boolean(timeline?.hasData);
84401
- const uptimePercentage = hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
85131
+ const hasTimelineData = activeMode === "light" ? Boolean(lightTimeline?.hasData) : Boolean(timeline?.hasData);
85132
+ const uptimePercentage = activeMode === "light" ? lightTimeline?.uptimePercentage ?? null : hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
85133
+ const lightStatus = lightTimeline?.currentStatus || workspace?.lightSummary?.currentStatus || null;
85134
+ const lightStatusText = `Light ${getLightStatusLabel(lightStatus)}`;
85135
+ const lightDurationText = lightTimeline?.currentDurationSeconds ? `${getLightDurationPrefix(lightStatus)} for ${formatSecondsDuration(lightTimeline.currentDurationSeconds)}` : null;
85136
+ const lightLastError = formatLightError(lightTimeline?.lastError);
84402
85137
  const allInterruptionsSorted = useMemo(
84403
85138
  () => [...downtimeSegments].sort(
84404
85139
  (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
@@ -84420,7 +85155,7 @@ var WorkspaceUptimeDetailModal = ({
84420
85155
  container.addEventListener("scroll", checkScroll);
84421
85156
  return () => container.removeEventListener("scroll", checkScroll);
84422
85157
  }
84423
- }, [downtimeSegments]);
85158
+ }, [downtimeSegments, lightTimeline?.statusSegments, activeMode]);
84424
85159
  const renderSegment = (segment) => {
84425
85160
  const start = new Date(segment.startTime);
84426
85161
  const end = new Date(segment.endTime);
@@ -84441,6 +85176,63 @@ var WorkspaceUptimeDetailModal = ({
84441
85176
  `${segment.startMinuteIndex}-${segment.endMinuteIndex}`
84442
85177
  );
84443
85178
  };
85179
+ const renderLightSegment = (segment) => {
85180
+ const start = new Date(segment.startTime);
85181
+ const end = new Date(segment.endTime);
85182
+ const startLabel = new Intl.DateTimeFormat("en-IN", {
85183
+ hour: "numeric",
85184
+ minute: "2-digit",
85185
+ hour12: true,
85186
+ timeZone: timezone
85187
+ }).format(start);
85188
+ const endLabel = segment.isCurrent ? "Now" : new Intl.DateTimeFormat("en-IN", {
85189
+ hour: "numeric",
85190
+ minute: "2-digit",
85191
+ hour12: true,
85192
+ timeZone: timezone
85193
+ }).format(end);
85194
+ const containerClasses = segment.status === "down" ? "border-rose-200 bg-rose-50" : "border-amber-200 bg-amber-50";
85195
+ return /* @__PURE__ */ jsxs(
85196
+ "div",
85197
+ {
85198
+ className: `rounded-lg border px-5 py-3 ${containerClasses}`,
85199
+ children: [
85200
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-semibold text-gray-900", children: [
85201
+ startLabel,
85202
+ " - ",
85203
+ endLabel
85204
+ ] }),
85205
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 mt-1", children: [
85206
+ getLightDurationPrefix(segment.status),
85207
+ " for ",
85208
+ formatSecondsDuration(segment.durationSeconds)
85209
+ ] }),
85210
+ formatLightError(segment.lastError) && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs font-medium text-rose-700", children: formatLightError(segment.lastError) })
85211
+ ]
85212
+ },
85213
+ `${segment.status}-${segment.startMinuteIndex}-${segment.endMinuteIndex}`
85214
+ );
85215
+ };
85216
+ const handleModeChange = (mode) => {
85217
+ if (mode === activeMode) return;
85218
+ const previousMode = activeMode;
85219
+ setActiveMode(mode);
85220
+ trackCoreEvent("Health Timeline Mode Changed", {
85221
+ previous_mode: previousMode,
85222
+ selected_mode: mode,
85223
+ source: "segmented_control",
85224
+ workspace_id: workspace?.workspace_id,
85225
+ line_id: workspace?.line_id
85226
+ });
85227
+ };
85228
+ const handleRefresh = () => {
85229
+ trackCoreEvent("Health Timeline Refreshed", {
85230
+ mode: activeMode,
85231
+ workspace_id: workspace?.workspace_id,
85232
+ line_id: workspace?.line_id
85233
+ });
85234
+ activeRefetch();
85235
+ };
84444
85236
  if (!isOpen || !workspace) {
84445
85237
  return null;
84446
85238
  }
@@ -84464,13 +85256,35 @@ var WorkspaceUptimeDetailModal = ({
84464
85256
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
84465
85257
  /* @__PURE__ */ jsx("h2", { id: "uptime-detail-title", className: "text-2xl font-semibold text-gray-900 truncate mb-3", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 6)}` }),
84466
85258
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
84467
- /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: timeline?.shiftLabel || "Current Shift" }),
85259
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: activeTimeline?.shiftLabel || "Current Shift" }),
84468
85260
  shiftStart && shiftEnd && /* @__PURE__ */ jsxs(Fragment, { children: [
84469
85261
  /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
84470
85262
  /* @__PURE__ */ jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
84471
85263
  ] })
84472
85264
  ] }),
84473
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
85265
+ hasLightTimeline && /* @__PURE__ */ jsxs("div", { className: "mt-3 inline-flex rounded-lg border border-gray-200 bg-gray-50 p-0.5", children: [
85266
+ /* @__PURE__ */ jsx(
85267
+ "button",
85268
+ {
85269
+ type: "button",
85270
+ onClick: () => handleModeChange("camera"),
85271
+ "aria-pressed": activeMode === "camera",
85272
+ className: `rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${activeMode === "camera" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
85273
+ children: "Camera"
85274
+ }
85275
+ ),
85276
+ /* @__PURE__ */ jsx(
85277
+ "button",
85278
+ {
85279
+ type: "button",
85280
+ onClick: () => handleModeChange("light"),
85281
+ "aria-pressed": activeMode === "light",
85282
+ className: `rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${activeMode === "light" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
85283
+ children: "Light"
85284
+ }
85285
+ )
85286
+ ] }),
85287
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: activeMode === "camera" ? /* @__PURE__ */ jsxs(Fragment, { children: [
84474
85288
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
84475
85289
  /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${workspace.status === "healthy" ? "bg-emerald-500 animate-pulse" : workspace.status === "warning" ? "bg-amber-500" : "bg-rose-500"}` }),
84476
85290
  /* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${workspace.status === "healthy" ? "text-emerald-700" : workspace.status === "warning" ? "text-amber-700" : "text-rose-700"}`, children: workspace.status === "healthy" ? "Operational" : workspace.status === "warning" ? "Intermittent" : "Down" })
@@ -84487,17 +85301,37 @@ var WorkspaceUptimeDetailModal = ({
84487
85301
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-600 dark:text-slate-300 select-all", children: workspace.cameraIp })
84488
85302
  ] })
84489
85303
  ] })
84490
- ] })
85304
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
85305
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
85306
+ /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${lightStatus === "up" ? "bg-emerald-500 animate-pulse" : lightStatus === "unknown" ? "bg-amber-500" : "bg-rose-500"}` }),
85307
+ /* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${lightStatus === "up" ? "text-emerald-700" : lightStatus === "unknown" ? "text-amber-700" : "text-rose-700"}`, children: lightStatusText })
85308
+ ] }),
85309
+ lightTimeline?.bulbIp && /* @__PURE__ */ jsxs(Fragment, { children: [
85310
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
85311
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 bg-slate-50 px-2 py-0.5 rounded border border-slate-100", title: "Bulb IP", children: [
85312
+ /* @__PURE__ */ jsx(Lightbulb, { className: "h-3 w-3 text-slate-400" }),
85313
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-600 select-all", children: lightTimeline.bulbIp })
85314
+ ] })
85315
+ ] }),
85316
+ lightDurationText && /* @__PURE__ */ jsxs(Fragment, { children: [
85317
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
85318
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: lightDurationText })
85319
+ ] }),
85320
+ lightLastError && /* @__PURE__ */ jsxs(Fragment, { children: [
85321
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
85322
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-rose-700", children: lightLastError })
85323
+ ] })
85324
+ ] }) })
84491
85325
  ] }),
84492
85326
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
84493
85327
  /* @__PURE__ */ jsxs(
84494
85328
  "button",
84495
85329
  {
84496
- onClick: refetch,
84497
- disabled: loading,
85330
+ onClick: handleRefresh,
85331
+ disabled: activeLoading,
84498
85332
  className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 transition-all duration-200 shadow-sm",
84499
85333
  children: [
84500
- /* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }),
85334
+ /* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${activeLoading ? "animate-spin" : ""}` }),
84501
85335
  /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Refresh" })
84502
85336
  ]
84503
85337
  }
@@ -84513,31 +85347,63 @@ var WorkspaceUptimeDetailModal = ({
84513
85347
  )
84514
85348
  ] })
84515
85349
  ] }),
84516
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-8 py-6 space-y-6", children: error ? /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-rose-100 bg-rose-50 p-5 text-sm text-rose-700", children: [
85350
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-8 py-6 space-y-6", children: activeError ? /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-rose-100 bg-rose-50 p-5 text-sm text-rose-700", children: [
84517
85351
  /* @__PURE__ */ jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
84518
- /* @__PURE__ */ jsx("p", { className: "text-xs text-rose-600/90", children: error.message })
85352
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-rose-600/90", children: activeError.message })
84519
85353
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
84520
85354
  /* @__PURE__ */ jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
84521
85355
  /* @__PURE__ */ jsx(
84522
85356
  UptimeTimelineStrip_default,
84523
85357
  {
84524
- points: timeline?.points || [],
84525
- totalMinutes: timeline?.totalMinutes || 0,
84526
- shiftStart: timeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
84527
- shiftEnd: timeline?.shiftEnd || (/* @__PURE__ */ new Date()).toISOString(),
85358
+ points: activeTimeline?.points || [],
85359
+ totalMinutes: activeTimeline?.totalMinutes || 0,
85360
+ shiftStart: activeTimeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
85361
+ shiftEnd: activeTimeline?.shiftEnd || (/* @__PURE__ */ new Date()).toISOString(),
84528
85362
  timezone,
84529
85363
  uptimePercentage,
84530
- downtimeMinutes: hasTimelineData ? downtimeMinutes : 0
85364
+ downtimeMinutes: activeMode === "light" ? 0 : hasTimelineData ? downtimeMinutes : 0,
85365
+ metricLabel: activeMode === "light" ? "light uptime" : "uptime",
85366
+ summaryText: activeMode === "light" && typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs(Fragment, { children: [
85367
+ uptimePercentage.toFixed(1),
85368
+ " % light uptime",
85369
+ (lightTimeline?.downSeconds || 0) > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
85370
+ " (",
85371
+ formatSecondsDuration(lightTimeline?.downSeconds || 0),
85372
+ " offline)"
85373
+ ] }),
85374
+ (lightTimeline?.unknownSeconds || 0) > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
85375
+ " (",
85376
+ formatSecondsDuration(lightTimeline?.unknownSeconds || 0),
85377
+ " unknown)"
85378
+ ] })
85379
+ ] }) : void 0
84531
85380
  }
84532
85381
  ),
84533
- loading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
85382
+ activeLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-600 mt-4", children: [
84534
85383
  /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
84535
85384
  /* @__PURE__ */ jsx("span", { children: "Updating timeline\u2026" })
84536
85385
  ] })
84537
85386
  ] }),
84538
85387
  /* @__PURE__ */ jsxs("div", { className: "pt-4", children: [
84539
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
84540
- !hasTimelineData ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No 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: [
85388
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: activeMode === "light" ? "Light Status Logs" : "Downtime Logs" }),
85389
+ activeMode === "light" ? !hasTimelineData ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No light status data available for this shift." }) }) : !lightTimeline?.statusSegments.length ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No light down or unknown events recorded for this shift." }) }) : /* @__PURE__ */ jsxs("div", { className: "relative", children: [
85390
+ /* @__PURE__ */ jsx(
85391
+ "div",
85392
+ {
85393
+ ref: logsContainerRef,
85394
+ className: "max-h-[400px] overflow-y-auto space-y-2 pr-2",
85395
+ style: {
85396
+ scrollbarWidth: "thin",
85397
+ scrollbarColor: "#CBD5E0 #F7FAFC"
85398
+ },
85399
+ children: lightTimeline.statusSegments.map((segment) => renderLightSegment(segment))
85400
+ }
85401
+ ),
85402
+ showScrollIndicator && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white via-white/80 to-transparent pointer-events-none flex items-end justify-center pb-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-gray-500 animate-bounce", children: [
85403
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Scroll for more" }),
85404
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
85405
+ ] }) })
85406
+ ] }) : !hasTimelineData ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No uptime data available for this shift." }) }) : downtimeSegments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No downtime events recorded for this shift." }) }) : /* @__PURE__ */ jsxs("div", { className: "relative", children: [
84541
85407
  /* @__PURE__ */ jsx(
84542
85408
  "div",
84543
85409
  {
@@ -84584,6 +85450,7 @@ var WorkspaceHealthView = ({
84584
85450
  const timezone = useAppTimezone();
84585
85451
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(effectiveLineIdForShiftConfig);
84586
85452
  const [selectedWorkspace, setSelectedWorkspace] = useState(null);
85453
+ const [selectedTimelineMode, setSelectedTimelineMode] = useState("camera");
84587
85454
  const [selectedDate, setSelectedDate] = useState(void 0);
84588
85455
  const [selectedShiftId, setSelectedShiftId] = useState(void 0);
84589
85456
  const [showDatePicker, setShowDatePicker] = useState(false);
@@ -84643,11 +85510,33 @@ var WorkspaceHealthView = ({
84643
85510
  },
84644
85511
  [router, onNavigate]
84645
85512
  );
84646
- const handleViewDetails = useCallback((workspace) => {
85513
+ const handleViewDetails = useCallback((workspace, mode = "camera", source = "card_button") => {
85514
+ const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary?.bulbIp);
85515
+ if (source === "light_chip") {
85516
+ trackCoreEvent("Health Light Chip Clicked", {
85517
+ workspace_id: workspace.workspace_id,
85518
+ line_id: workspace.line_id,
85519
+ light_status: workspace.lightSummary?.currentStatus || null,
85520
+ uptime_percent: workspace.lightSummary?.uptimePercent ?? null,
85521
+ bulb_ip_present: Boolean(workspace.lightSummary?.bulbIp)
85522
+ });
85523
+ }
85524
+ trackCoreEvent("Health Timeline Opened", {
85525
+ source,
85526
+ initial_mode: mode,
85527
+ workspace_id: workspace.workspace_id,
85528
+ line_id: workspace.line_id,
85529
+ has_light_config: hasLightConfig,
85530
+ light_status: workspace.lightSummary?.currentStatus || null,
85531
+ selected_date: selectedDate || operationalDate,
85532
+ selected_shift_id: selectedShiftId ?? currentShiftDetails.shiftId
85533
+ });
85534
+ setSelectedTimelineMode(mode);
84647
85535
  setSelectedWorkspace(workspace);
84648
- }, []);
85536
+ }, [currentShiftDetails.shiftId, operationalDate, selectedDate, selectedShiftId]);
84649
85537
  const handleCloseDetails = useCallback(() => {
84650
85538
  setSelectedWorkspace(null);
85539
+ setSelectedTimelineMode("camera");
84651
85540
  }, []);
84652
85541
  const getStatusIcon = (status) => {
84653
85542
  switch (status) {
@@ -84905,7 +85794,8 @@ var WorkspaceHealthView = ({
84905
85794
  onClose: handleCloseDetails,
84906
85795
  shiftConfig: modalShiftConfig,
84907
85796
  date: selectedDate,
84908
- shiftId: selectedShiftId
85797
+ shiftId: selectedShiftId,
85798
+ initialMode: selectedTimelineMode
84909
85799
  }
84910
85800
  )
84911
85801
  ] });
@@ -93158,4 +94048,4 @@ var RecentFlowSnapshotGrid = ({
93158
94048
  );
93159
94049
  };
93160
94050
 
93161
- export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPI_SIGNAL_LABELS, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LineOvertakeNotificationManager, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProductionPlanApiError, ProductionPlanView_default as ProductionPlanView, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RecentFlowSnapshotGrid, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SENTRY_HANDLED_EVENT_SESSION_LIMIT, SENTRY_HANDLED_EVENT_WINDOW_MS, SENTRY_QUOTA_STORAGE_KEY, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, addSentryBreadcrumb, aggregateKPIsFromLineMetricsRows, aggregateLineSignals, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildKpiLineHierarchy, buildLegacyLineOvertakeEventKey, buildLineLeaderboardRows, buildLineOvertakeEventKey, buildLineSkuBreakdown, buildShiftGroupsKey, canPermissionEditProductionPlan, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleEditProductionPlan, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, detectLineOvertakeEvents, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getKpiSignalLabel, getKpiSignalStatus, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeDateKeyRangeUnbounded, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, productionPlanService, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSentryQuotaForTests, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useCompanyWorkspaceHourAiSummaryEnabled, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceHourSummary, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
94051
+ export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPI_SIGNAL_LABELS, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LineOvertakeNotificationManager, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProductionPlanApiError, ProductionPlanView_default as ProductionPlanView, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RecentFlowSnapshotGrid, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SENTRY_HANDLED_EVENT_SESSION_LIMIT, SENTRY_HANDLED_EVENT_WINDOW_MS, SENTRY_QUOTA_STORAGE_KEY, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, addSentryBreadcrumb, aggregateKPIsFromLineMetricsRows, aggregateLineSignals, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildKpiLineHierarchy, buildLegacyLineOvertakeEventKey, buildLineLeaderboardRows, buildLineOvertakeEventKey, buildLineSkuBreakdown, buildShiftGroupsKey, canPermissionEditProductionPlan, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleEditProductionPlan, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, detectLineOvertakeEvents, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getKpiSignalLabel, getKpiSignalStatus, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeDateKeyRangeUnbounded, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, productionPlanService, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSentryQuotaForTests, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useCompanyWorkspaceHourAiSummaryEnabled, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceHourSummary, useWorkspaceLightTimeline, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };