@optifye/dashboard-core 6.12.49 → 6.12.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
  }
@@ -13791,12 +14349,22 @@ var stripSeconds = (timeStr) => {
13791
14349
  if (!timeStr) return timeStr;
13792
14350
  return timeStr.substring(0, 5);
13793
14351
  };
14352
+ var normalizeOffDays = (value) => {
14353
+ if (!value) return [];
14354
+ if (Array.isArray(value)) {
14355
+ return value.map((day) => String(day || "").trim().toLowerCase()).filter(Boolean);
14356
+ }
14357
+ if (Array.isArray(value.off_days)) return normalizeOffDays(value.off_days);
14358
+ if (Array.isArray(value.offDays)) return normalizeOffDays(value.offDays);
14359
+ return [];
14360
+ };
13794
14361
  var buildShiftConfigFromOperatingHoursRows = (rows, fallback) => {
13795
14362
  const mapped = (rows || []).map((row) => ({
13796
14363
  shiftId: row.shift_id,
13797
14364
  shiftName: row.shift_name || `Shift ${row.shift_id}`,
13798
14365
  startTime: stripSeconds(row.start_time),
13799
14366
  endTime: stripSeconds(row.end_time),
14367
+ offDays: normalizeOffDays(row.off_days),
13800
14368
  breaks: (() => {
13801
14369
  const raw = Array.isArray(row.breaks) ? row.breaks : Array.isArray(row.breaks?.breaks) ? row.breaks.breaks : [];
13802
14370
  return raw.map((b) => ({
@@ -13856,7 +14424,7 @@ var fetchAndStoreShiftConfig = async (supabase, lineId, fallback) => {
13856
14424
  if (existing) return existing;
13857
14425
  const promise = (async () => {
13858
14426
  try {
13859
- const { data, error } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").eq("line_id", lineId);
14427
+ const { data, error } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone, off_days").eq("line_id", lineId);
13860
14428
  if (error) {
13861
14429
  throw new Error(`Failed to fetch shift config: ${error.message}`);
13862
14430
  }
@@ -14549,7 +15117,7 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
14549
15117
  setError(null);
14550
15118
  }
14551
15119
  console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${missingLineIds.length} lines`);
14552
- const { data, error: fetchError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", missingLineIds);
15120
+ const { data, error: fetchError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone, off_days").in("line_id", missingLineIds);
14553
15121
  if (fetchError) {
14554
15122
  console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
14555
15123
  throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
@@ -20765,7 +21333,8 @@ var useWorkspaceUptimeTimeline = (options) => {
20765
21333
  effectiveShiftConfig,
20766
21334
  effectiveTimezone,
20767
21335
  overrideDate,
20768
- overrideShiftId
21336
+ overrideShiftId,
21337
+ lineId
20769
21338
  );
20770
21339
  setTimeline(data);
20771
21340
  } catch (err) {
@@ -20775,7 +21344,89 @@ var useWorkspaceUptimeTimeline = (options) => {
20775
21344
  setLoading(false);
20776
21345
  isFetchingRef.current = false;
20777
21346
  }
20778
- }, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
21347
+ }, [enabled, workspaceId, companyId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId, lineId]);
21348
+ useEffect(() => {
21349
+ fetchTimeline();
21350
+ }, [fetchTimeline]);
21351
+ useEffect(() => {
21352
+ if (!refreshInterval || refreshInterval <= 0 || !enabled) {
21353
+ return;
21354
+ }
21355
+ intervalRef.current = setInterval(() => {
21356
+ fetchTimeline();
21357
+ }, refreshInterval);
21358
+ return () => {
21359
+ if (intervalRef.current) {
21360
+ clearInterval(intervalRef.current);
21361
+ }
21362
+ };
21363
+ }, [refreshInterval, enabled, fetchTimeline]);
21364
+ return {
21365
+ timeline,
21366
+ loading: loading || shiftConfigPending,
21367
+ error,
21368
+ refetch: fetchTimeline
21369
+ };
21370
+ };
21371
+ var useWorkspaceLightTimeline = (options) => {
21372
+ const {
21373
+ workspaceId,
21374
+ lineId,
21375
+ enabled = true,
21376
+ refreshInterval,
21377
+ shiftConfig: passedShiftConfig,
21378
+ timezone: passedTimezone,
21379
+ date: overrideDate,
21380
+ shiftId: overrideShiftId
21381
+ } = options;
21382
+ const { shiftConfig: dynamicShiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
21383
+ const appTimezone = useAppTimezone();
21384
+ const shiftConfigPending = !passedShiftConfig && (!lineId || isShiftConfigLoading || !dynamicShiftConfig);
21385
+ const effectiveShiftConfig = passedShiftConfig ?? (shiftConfigPending ? void 0 : dynamicShiftConfig);
21386
+ const effectiveTimezone = passedTimezone || appTimezone || effectiveShiftConfig?.timezone || "UTC";
21387
+ const [timeline, setTimeline] = useState(null);
21388
+ const [loading, setLoading] = useState(false);
21389
+ const [error, setError] = useState(null);
21390
+ const isFetchingRef = useRef(false);
21391
+ const intervalRef = useRef(null);
21392
+ useEffect(() => {
21393
+ setTimeline(null);
21394
+ setError(null);
21395
+ setLoading(enabled && Boolean(workspaceId));
21396
+ }, [workspaceId, lineId, enabled]);
21397
+ const fetchTimeline = useCallback(async () => {
21398
+ if (!enabled) return;
21399
+ if (shiftConfigPending) {
21400
+ setLoading(true);
21401
+ return;
21402
+ }
21403
+ if (!effectiveShiftConfig || !workspaceId) {
21404
+ setLoading(false);
21405
+ setTimeline(null);
21406
+ return;
21407
+ }
21408
+ if (isFetchingRef.current) return;
21409
+ try {
21410
+ isFetchingRef.current = true;
21411
+ setLoading(true);
21412
+ setError(null);
21413
+ const data = await workspaceHealthService.getWorkspaceLightTimeline(
21414
+ workspaceId,
21415
+ lineId,
21416
+ effectiveShiftConfig,
21417
+ effectiveTimezone,
21418
+ overrideDate,
21419
+ overrideShiftId
21420
+ );
21421
+ setTimeline(data);
21422
+ } catch (err) {
21423
+ console.error("[useWorkspaceLightTimeline] Failed to fetch light timeline:", err);
21424
+ setError({ message: err?.message || "Failed to load light timeline", code: err?.code || "FETCH_ERROR" });
21425
+ } finally {
21426
+ setLoading(false);
21427
+ isFetchingRef.current = false;
21428
+ }
21429
+ }, [enabled, workspaceId, lineId, effectiveShiftConfig, effectiveTimezone, shiftConfigPending, overrideDate, overrideShiftId]);
20779
21430
  useEffect(() => {
20780
21431
  fetchTimeline();
20781
21432
  }, [fetchTimeline]);
@@ -37779,7 +38430,12 @@ var HourlyOutputChartComponent = ({
37779
38430
  }, [idleBarState.visible, idleBarState.key, idleBarState.shouldAnimate]);
37780
38431
  const maxDataValue = Math.max(...data, 0);
37781
38432
  const numericChartTargets = chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target));
37782
- const maxTargetValue = Math.max(...numericChartTargets, pphThreshold, 0);
38433
+ const hasAuthoritativeNumericTargets = hasHourlyTargetOutputProp && numericChartTargets.length > 0;
38434
+ const maxTargetValue = Math.max(
38435
+ ...numericChartTargets,
38436
+ hasAuthoritativeNumericTargets ? 0 : pphThreshold,
38437
+ 0
38438
+ );
37783
38439
  const maxYValue = Math.max(
37784
38440
  Math.ceil(maxTargetValue * 1.5),
37785
38441
  Math.ceil(maxDataValue * 1.15)
@@ -37934,14 +38590,9 @@ var HourlyOutputChartComponent = ({
37934
38590
  return /* @__PURE__ */ jsx("g", { children: lines });
37935
38591
  }, [hourlyTargetSegments, targetTimelineSegments, SHIFT_DURATION, pphThreshold, targetLineEndOffset, hasHourlyTargetOutputProp]);
37936
38592
  const renderLegend = () => {
37937
- const uniqueTargets = [...new Set(
37938
- chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
37939
- )].sort((a, b) => a - b);
37940
- const unitLabel = hasHourlyTargetOutputProp ? "units" : "units/hr";
37941
- const targetText = uniqueTargets.length === 0 ? `Target` : uniqueTargets.length === 1 ? `Target: ${uniqueTargets[0]} ${unitLabel}` : `Target: ${uniqueTargets[0]} - ${uniqueTargets[uniqueTargets.length - 1]} ${unitLabel}`;
37942
38593
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 bg-white py-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
37943
38594
  /* @__PURE__ */ jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
37944
- /* @__PURE__ */ jsx("span", { children: targetText })
38595
+ /* @__PURE__ */ jsx("span", { children: "Target" })
37945
38596
  ] }) });
37946
38597
  };
37947
38598
  return /* @__PURE__ */ jsxs(
@@ -52459,6 +53110,106 @@ var getOrdinal = (n) => {
52459
53110
  const v = n % 100;
52460
53111
  return n + (suffix[(v - 20) % 10] || suffix[v] || suffix[0]);
52461
53112
  };
53113
+ var resolveDailyTargetOutput = (shiftData) => {
53114
+ if (!shiftData || shiftData.hasData === false) return 0;
53115
+ const target = Number(shiftData?.targetOutput || shiftData?.idealOutput || 0);
53116
+ return Number.isFinite(target) && target > 0 ? target : 0;
53117
+ };
53118
+ var getUniqueRoundedTargets = (data) => Array.from(new Set(
53119
+ data.map((entry) => Number(entry.targetOutput || 0)).filter((target) => Number.isFinite(target) && target > 0).map((target) => Math.round(target))
53120
+ )).sort((a, b) => a - b);
53121
+ var formatDailyTargetLegend = (targets) => {
53122
+ if (targets.length === 0) return "";
53123
+ if (targets.length === 1) return `Target: ${targets[0].toLocaleString()} units/day`;
53124
+ return `Target: ${targets[0].toLocaleString()} - ${targets[targets.length - 1].toLocaleString()} units/day`;
53125
+ };
53126
+ var WEEKDAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
53127
+ var getShiftOffDays = (shiftConfig, selectedShiftId) => {
53128
+ const shift = shiftConfig?.shifts?.find((candidate) => candidate.shiftId === selectedShiftId);
53129
+ const raw = shift?.offDays || shift?.off_days || [];
53130
+ return Array.isArray(raw) ? raw.map((day) => String(day || "").trim().toLowerCase()).filter(Boolean) : [];
53131
+ };
53132
+ var isScheduledOffDay = (dateKey, shiftConfig, selectedShiftId) => {
53133
+ const offDays = getShiftOffDays(shiftConfig, selectedShiftId);
53134
+ if (offDays.length === 0) return false;
53135
+ const date = parseDateKeyToDate(dateKey);
53136
+ return offDays.includes(WEEKDAY_NAMES[date.getDay()]);
53137
+ };
53138
+ var renderDailyOutputCapsules = (props, data) => {
53139
+ const { offset, yAxisMap } = props;
53140
+ if (!offset || !yAxisMap || data.length === 0) return null;
53141
+ const { left, width } = offset;
53142
+ const yAxis = yAxisMap.default || yAxisMap[0];
53143
+ if (!Number.isFinite(left) || !Number.isFinite(width) || width <= 0 || !yAxis?.scale) {
53144
+ return null;
53145
+ }
53146
+ const slotWidth = width / data.length;
53147
+ const capsuleWidth = Math.max(10, Math.min(28, slotWidth * 0.44));
53148
+ const radius = capsuleWidth / 2;
53149
+ const baseY = yAxis.scale(0);
53150
+ const capsules = [];
53151
+ data.forEach((entry, index) => {
53152
+ const target = Number(entry.targetOutput || 0);
53153
+ const output = Number(entry.output || 0);
53154
+ if ((!Number.isFinite(target) || target <= 0) && (!Number.isFinite(output) || output <= 0)) return;
53155
+ const x = left + index * slotWidth + (slotWidth - capsuleWidth) / 2;
53156
+ const targetTop = target > 0 ? yAxis.scale(target) : yAxis.scale(output);
53157
+ const capsuleTop = Math.min(targetTop, baseY - 4);
53158
+ const capsuleHeight = Math.max(baseY - capsuleTop, 4);
53159
+ const fillValue = target > 0 ? Math.min(Math.max(output, 0), target) : Math.max(output, 0);
53160
+ const fillTop = fillValue > 0 ? Math.max(yAxis.scale(fillValue), capsuleTop) : baseY;
53161
+ const fillHeight = Math.max(baseY - fillTop, 0);
53162
+ const fillColor = target > 0 ? output >= target ? "#00AB45" : "#E34329" : entry.color || "#6b7280";
53163
+ const trackFill = output >= target ? "#f0fdf4" : "#fff5f3";
53164
+ const trackStroke = output >= target ? "#00AB45" : "#E34329";
53165
+ const clipId = `line-daily-output-capsule-${index}`;
53166
+ capsules.push(
53167
+ /* @__PURE__ */ jsxs("g", { children: [
53168
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx(
53169
+ "rect",
53170
+ {
53171
+ x,
53172
+ y: capsuleTop,
53173
+ width: capsuleWidth,
53174
+ height: capsuleHeight,
53175
+ rx: radius,
53176
+ ry: radius
53177
+ }
53178
+ ) }) }),
53179
+ target > 0 && /* @__PURE__ */ jsx(
53180
+ "rect",
53181
+ {
53182
+ "data-testid": "daily-target-capsule-track",
53183
+ x,
53184
+ y: capsuleTop,
53185
+ width: capsuleWidth,
53186
+ height: capsuleHeight,
53187
+ rx: radius,
53188
+ ry: radius,
53189
+ fill: trackFill,
53190
+ stroke: trackStroke,
53191
+ strokeWidth: 1.5
53192
+ }
53193
+ ),
53194
+ /* @__PURE__ */ jsx(
53195
+ "rect",
53196
+ {
53197
+ "data-testid": "daily-output-capsule-fill",
53198
+ x,
53199
+ y: fillTop,
53200
+ width: capsuleWidth,
53201
+ height: fillHeight,
53202
+ rx: fillHeight >= capsuleHeight ? radius : 0,
53203
+ ry: fillHeight >= capsuleHeight ? radius : 0,
53204
+ fill: fillColor,
53205
+ clipPath: target > 0 ? `url(#${clipId})` : void 0
53206
+ }
53207
+ )
53208
+ ] }, `daily-output-capsule-${index}`)
53209
+ );
53210
+ });
53211
+ return capsules.length > 0 ? /* @__PURE__ */ jsx("g", { children: capsules }) : null;
53212
+ };
52462
53213
  var CustomTooltip2 = ({ active, payload, label, isUptimeMode }) => {
52463
53214
  if (!active || !payload || payload.length === 0) return null;
52464
53215
  if (isUptimeMode) {
@@ -52542,6 +53293,7 @@ var LineMonthlyHistory = ({
52542
53293
  legend,
52543
53294
  monitoringMode,
52544
53295
  lineAssembly = false,
53296
+ shiftConfig,
52545
53297
  underperformingWorkspaces = {},
52546
53298
  lineId,
52547
53299
  selectedShiftId = 0,
@@ -52559,6 +53311,13 @@ var LineMonthlyHistory = ({
52559
53311
  const { isIdleTimeVlmEnabled } = useIdleTimeVlmConfig();
52560
53312
  const idleTimeVlmEnabled = isIdleTimeVlmEnabled(lineId);
52561
53313
  const isUptimeMode = monitoringMode === "uptime";
53314
+ const [isMobile, setIsMobile] = useState(false);
53315
+ useEffect(() => {
53316
+ const checkMobile = () => setIsMobile(window.innerWidth < 640);
53317
+ checkMobile();
53318
+ window.addEventListener("resize", checkMobile);
53319
+ return () => window.removeEventListener("resize", checkMobile);
53320
+ }, []);
52562
53321
  const chartKey = useMemo(() => `${lineId}-${month}-${year}-${selectedShiftId}-${rangeStart}-${rangeEnd}`, [lineId, month, year, selectedShiftId, rangeStart, rangeEnd]);
52563
53322
  const monthBounds = useMemo(() => getMonthKeyBounds(year, month), [year, month]);
52564
53323
  const normalizedRange = useMemo(() => {
@@ -52720,29 +53479,21 @@ var LineMonthlyHistory = ({
52720
53479
  });
52721
53480
  }
52722
53481
  const yAxisMax2 = maxHours > 0 ? 100 : 1;
52723
- return { data: dailyData2, maxOutput: 0, lastSetTarget: 0, yAxisMax: yAxisMax2 };
53482
+ return { data: dailyData2, maxOutput: 0, targetValues: [], yAxisMax: yAxisMax2, targetLegend: "" };
52724
53483
  }
52725
53484
  const dailyData = [];
52726
53485
  let maxOutput = 0;
52727
- let lastSetTarget = 0;
52728
- for (let i = rangeDateKeys.length - 1; i >= 0; i--) {
52729
- const dayKey = rangeDateKeys[i];
52730
- const dayData = analysisMonthlyDataByKey.get(dayKey);
52731
- const shiftData = dayData ? getShiftData2(dayData, selectedShiftId) : null;
52732
- const idealOutput = shiftData ? shiftData.idealOutput || 0 : 0;
52733
- if (idealOutput > 0) {
52734
- lastSetTarget = idealOutput;
52735
- break;
52736
- }
52737
- }
53486
+ let maxTarget = 0;
52738
53487
  for (const dayKey of rangeDateKeys) {
52739
53488
  const day = Number(dayKey.slice(-2));
52740
53489
  const dayData = analysisMonthlyDataByKey.get(dayKey);
53490
+ const isOffDay = isScheduledOffDay(dayKey, shiftConfig, selectedShiftId);
52741
53491
  const shiftData = dayData ? getShiftData2(dayData, selectedShiftId) : null;
52742
- const output = shiftData && hasRealData(shiftData) ? shiftData.output || 0 : 0;
52743
- const idealOutput = shiftData ? shiftData.idealOutput || 0 : 0;
53492
+ const output = !isOffDay && shiftData && hasRealData(shiftData) ? shiftData.output || 0 : 0;
53493
+ const targetOutput = isOffDay ? 0 : resolveDailyTargetOutput(shiftData);
52744
53494
  if (output > maxOutput) maxOutput = output;
52745
- const color2 = output >= lastSetTarget ? "#00AB45" : "#E34329";
53495
+ if (targetOutput > maxTarget) maxTarget = targetOutput;
53496
+ const color2 = targetOutput > 0 && output >= targetOutput ? "#00AB45" : "#E34329";
52746
53497
  dailyData.push({
52747
53498
  hour: getOrdinal(day),
52748
53499
  // Using ordinal format (1st, 2nd, 3rd, etc.)
@@ -52750,17 +53501,25 @@ var LineMonthlyHistory = ({
52750
53501
  output,
52751
53502
  originalOutput: output,
52752
53503
  // For label display
52753
- idealOutput,
53504
+ idealOutput: targetOutput,
53505
+ targetOutput,
52754
53506
  color: color2
52755
53507
  });
52756
53508
  }
52757
- const calculatedMax = Math.max(maxOutput, lastSetTarget);
53509
+ const calculatedMax = Math.max(maxOutput, maxTarget);
52758
53510
  const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
52759
- return { data: dailyData, maxOutput, lastSetTarget, yAxisMax };
52760
- }, [analysisMonthlyDataByKey, normalizedRange.endKey, normalizedRange.startKey, selectedShiftId, isUptimeMode, timezone]);
53511
+ const targetValues = getUniqueRoundedTargets(dailyData);
53512
+ return {
53513
+ data: dailyData,
53514
+ maxOutput,
53515
+ targetValues,
53516
+ yAxisMax,
53517
+ targetLegend: formatDailyTargetLegend(targetValues)
53518
+ };
53519
+ }, [analysisMonthlyDataByKey, normalizedRange.endKey, normalizedRange.startKey, selectedShiftId, isUptimeMode, timezone, shiftConfig]);
52761
53520
  const yAxisTicks = useMemo(() => {
52762
53521
  const max = chartData.yAxisMax;
52763
- const target = chartData.lastSetTarget;
53522
+ const targets = chartData.targetValues || [];
52764
53523
  if (!max || max <= 0) return void 0;
52765
53524
  const desiredIntervals = 4;
52766
53525
  const roughStep = max / desiredIntervals;
@@ -52774,11 +53533,18 @@ var LineMonthlyHistory = ({
52774
53533
  for (let v = 0; v <= max; v += step) {
52775
53534
  ticks.push(Math.round(v));
52776
53535
  }
52777
- if (target > 0) {
52778
- ticks.push(Math.round(target));
52779
- }
53536
+ targets.forEach((target) => ticks.push(Math.round(target)));
52780
53537
  return Array.from(new Set(ticks)).filter((v) => v >= 0 && v <= max).sort((a, b) => a - b);
52781
- }, [chartData.yAxisMax, chartData.lastSetTarget]);
53538
+ }, [chartData.yAxisMax, chartData.targetValues]);
53539
+ const visibleYAxisTicks = useMemo(() => {
53540
+ if (!isMobile || isUptimeMode || !yAxisTicks || yAxisTicks.length <= 3) return yAxisTicks;
53541
+ const importantTicks = /* @__PURE__ */ new Set([
53542
+ 0,
53543
+ Math.round(chartData.yAxisMax),
53544
+ ...chartData.targetValues || []
53545
+ ]);
53546
+ return yAxisTicks.filter((tick) => importantTicks.has(Math.round(tick))).sort((a, b) => a - b).slice(-3);
53547
+ }, [chartData.targetValues, chartData.yAxisMax, isMobile, isUptimeMode, yAxisTicks]);
52782
53548
  const pieChartData = useMemo(() => {
52783
53549
  if (!isUptimeMode) return [];
52784
53550
  const validShifts = (analysisMonthlyData || []).map((day) => getShiftData2(day, selectedShiftId)).filter(
@@ -53097,36 +53863,35 @@ var LineMonthlyHistory = ({
53097
53863
  ] }),
53098
53864
  /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg sm:rounded-xl shadow-sm border border-gray-100 p-2 sm:p-3 lg:p-4", children: [
53099
53865
  /* @__PURE__ */ jsx("h3", { className: "text-xs sm:text-sm font-bold text-gray-700 mb-1 sm:mb-2 text-left", children: isUptimeMode ? "Daily Utilization" : "Daily Output" }),
53100
- /* @__PURE__ */ jsx("div", { className: "h-[160px] sm:h-[180px] lg:h-[220px]", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
53866
+ /* @__PURE__ */ jsx("div", { className: "h-[140px] sm:h-[180px] lg:h-[220px]", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
53101
53867
  BarChart$1,
53102
53868
  {
53103
53869
  data: chartData.data,
53104
- margin: { top: 20, right: 10, bottom: 40, left: 10 },
53870
+ margin: isMobile ? { top: 8, right: 4, bottom: 28, left: 0 } : { top: 20, right: 10, bottom: 40, left: 10 },
53105
53871
  children: [
53106
53872
  /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#f3f4f6" }),
53107
53873
  /* @__PURE__ */ jsx(
53108
53874
  XAxis,
53109
53875
  {
53110
53876
  dataKey: "hour",
53111
- tick: { fontSize: 10, fill: "#6b7280" },
53112
- interval: 0,
53877
+ tick: { fontSize: isMobile ? 9 : 10, fill: "#6b7280" },
53878
+ interval: isMobile ? 3 : 0,
53113
53879
  angle: -45,
53114
53880
  textAnchor: "end",
53115
- height: 60
53881
+ height: isMobile ? 42 : 60
53116
53882
  }
53117
53883
  ),
53118
53884
  /* @__PURE__ */ jsx(
53119
53885
  YAxis,
53120
53886
  {
53121
53887
  domain: [0, chartData.yAxisMax],
53122
- width: 40,
53123
- ticks: isUptimeMode ? [0, 25, 50, 75, 100] : yAxisTicks,
53888
+ width: isMobile ? 34 : 40,
53889
+ ticks: isUptimeMode ? [0, 25, 50, 75, 100] : visibleYAxisTicks,
53124
53890
  tickFormatter: isUptimeMode ? (value) => `${value}%` : void 0,
53125
53891
  tick: isUptimeMode ? void 0 : (props) => {
53126
53892
  const { x, y, payload } = props;
53127
53893
  const value = Math.round(payload.value);
53128
- const targetValue = Math.round(chartData.lastSetTarget);
53129
- const isTarget = value === targetValue && targetValue > 0;
53894
+ const isTarget = (chartData.targetValues || []).includes(value);
53130
53895
  return /* @__PURE__ */ jsx(
53131
53896
  "text",
53132
53897
  {
@@ -53149,15 +53914,7 @@ var LineMonthlyHistory = ({
53149
53914
  content: (props) => /* @__PURE__ */ jsx(CustomTooltip2, { ...props, isUptimeMode })
53150
53915
  }
53151
53916
  ),
53152
- !isUptimeMode && chartData.lastSetTarget > 0 && /* @__PURE__ */ jsx(
53153
- ReferenceLine,
53154
- {
53155
- y: chartData.lastSetTarget,
53156
- stroke: "#E34329",
53157
- strokeDasharray: "5 5",
53158
- strokeWidth: 2
53159
- }
53160
- ),
53917
+ !isUptimeMode && /* @__PURE__ */ jsx(Customized, { component: (props) => renderDailyOutputCapsules(props, chartData.data) }),
53161
53918
  isUptimeMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
53162
53919
  /* @__PURE__ */ jsx(
53163
53920
  Bar,
@@ -53190,6 +53947,8 @@ var LineMonthlyHistory = ({
53190
53947
  {
53191
53948
  dataKey: "output",
53192
53949
  radius: [4, 4, 0, 0],
53950
+ fill: "transparent",
53951
+ opacity: 0,
53193
53952
  isAnimationActive: true,
53194
53953
  animationBegin: 0,
53195
53954
  animationDuration: 1e3,
@@ -53208,13 +53967,9 @@ var LineMonthlyHistory = ({
53208
53967
  },
53209
53968
  chartKey
53210
53969
  ) }) }),
53211
- !isUptimeMode && chartData.lastSetTarget > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 bg-white py-1 pt-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
53212
- /* @__PURE__ */ jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
53213
- /* @__PURE__ */ jsxs("span", { children: [
53214
- "Target: ",
53215
- Math.round(chartData.lastSetTarget),
53216
- " units/day"
53217
- ] })
53970
+ !isUptimeMode && chartData.targetValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 bg-white py-1 pt-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
53971
+ /* @__PURE__ */ jsx("div", { className: "relative h-5 w-3 rounded-full border border-[#E34329] bg-[#fff5f3] overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 h-1/2 bg-[#E34329]" }) }),
53972
+ /* @__PURE__ */ jsx("span", { children: chartData.targetLegend })
53218
53973
  ] }) })
53219
53974
  ] })
53220
53975
  ] })
@@ -55137,6 +55892,106 @@ var formatCycleSeconds = (value) => {
55137
55892
  if (!Number.isFinite(value)) return "0.0s";
55138
55893
  return `${value.toFixed(1)}s`;
55139
55894
  };
55895
+ var resolveDailyTargetOutput2 = (shiftData) => {
55896
+ if (!shiftData || shiftData.hasData === false) return 0;
55897
+ const target = Number(shiftData?.targetOutput || shiftData?.idealOutput || 0);
55898
+ return Number.isFinite(target) && target > 0 ? target : 0;
55899
+ };
55900
+ var getUniqueRoundedTargets2 = (data) => Array.from(new Set(
55901
+ data.map((entry) => Number(entry.targetOutput || 0)).filter((target) => Number.isFinite(target) && target > 0).map((target) => Math.round(target))
55902
+ )).sort((a, b) => a - b);
55903
+ var formatDailyTargetLegend2 = (targets) => {
55904
+ if (targets.length === 0) return "";
55905
+ if (targets.length === 1) return `Target: ${targets[0].toLocaleString()} units/day`;
55906
+ return `Target: ${targets[0].toLocaleString()} - ${targets[targets.length - 1].toLocaleString()} units/day`;
55907
+ };
55908
+ var WEEKDAY_NAMES2 = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
55909
+ var getShiftOffDays2 = (shiftConfig, selectedShiftId) => {
55910
+ const shift = shiftConfig?.shifts?.find((candidate) => candidate.shiftId === selectedShiftId);
55911
+ const raw = shift?.offDays || shift?.off_days || [];
55912
+ return Array.isArray(raw) ? raw.map((day) => String(day || "").trim().toLowerCase()).filter(Boolean) : [];
55913
+ };
55914
+ var isScheduledOffDay2 = (dateKey, shiftConfig, selectedShiftId) => {
55915
+ const offDays = getShiftOffDays2(shiftConfig, selectedShiftId);
55916
+ if (offDays.length === 0) return false;
55917
+ const date = parseDateKeyToDate(dateKey);
55918
+ return offDays.includes(WEEKDAY_NAMES2[date.getDay()]);
55919
+ };
55920
+ var renderDailyOutputCapsules2 = (props, data) => {
55921
+ const { offset, yAxisMap } = props;
55922
+ if (!offset || !yAxisMap || data.length === 0) return null;
55923
+ const { left, width } = offset;
55924
+ const yAxis = yAxisMap.default || yAxisMap[0];
55925
+ if (!Number.isFinite(left) || !Number.isFinite(width) || width <= 0 || !yAxis?.scale) {
55926
+ return null;
55927
+ }
55928
+ const slotWidth = width / data.length;
55929
+ const capsuleWidth = Math.max(10, Math.min(28, slotWidth * 0.44));
55930
+ const radius = capsuleWidth / 2;
55931
+ const baseY = yAxis.scale(0);
55932
+ const capsules = [];
55933
+ data.forEach((entry, index) => {
55934
+ const target = Number(entry.targetOutput || 0);
55935
+ const output = Number(entry.output || 0);
55936
+ if ((!Number.isFinite(target) || target <= 0) && (!Number.isFinite(output) || output <= 0)) return;
55937
+ const x = left + index * slotWidth + (slotWidth - capsuleWidth) / 2;
55938
+ const targetTop = target > 0 ? yAxis.scale(target) : yAxis.scale(output);
55939
+ const capsuleTop = Math.min(targetTop, baseY - 4);
55940
+ const capsuleHeight = Math.max(baseY - capsuleTop, 4);
55941
+ const fillValue = target > 0 ? Math.min(Math.max(output, 0), target) : Math.max(output, 0);
55942
+ const fillTop = fillValue > 0 ? Math.max(yAxis.scale(fillValue), capsuleTop) : baseY;
55943
+ const fillHeight = Math.max(baseY - fillTop, 0);
55944
+ const fillColor = target > 0 ? output >= target ? "#00AB45" : "#E34329" : entry.color || "#6b7280";
55945
+ const trackFill = output >= target ? "#f0fdf4" : "#fff5f3";
55946
+ const trackStroke = output >= target ? "#00AB45" : "#E34329";
55947
+ const clipId = `workspace-daily-output-capsule-${index}`;
55948
+ capsules.push(
55949
+ /* @__PURE__ */ jsxs("g", { children: [
55950
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx(
55951
+ "rect",
55952
+ {
55953
+ x,
55954
+ y: capsuleTop,
55955
+ width: capsuleWidth,
55956
+ height: capsuleHeight,
55957
+ rx: radius,
55958
+ ry: radius
55959
+ }
55960
+ ) }) }),
55961
+ target > 0 && /* @__PURE__ */ jsx(
55962
+ "rect",
55963
+ {
55964
+ "data-testid": "daily-target-capsule-track",
55965
+ x,
55966
+ y: capsuleTop,
55967
+ width: capsuleWidth,
55968
+ height: capsuleHeight,
55969
+ rx: radius,
55970
+ ry: radius,
55971
+ fill: trackFill,
55972
+ stroke: trackStroke,
55973
+ strokeWidth: 1.5
55974
+ }
55975
+ ),
55976
+ /* @__PURE__ */ jsx(
55977
+ "rect",
55978
+ {
55979
+ "data-testid": "daily-output-capsule-fill",
55980
+ x,
55981
+ y: fillTop,
55982
+ width: capsuleWidth,
55983
+ height: fillHeight,
55984
+ rx: fillHeight >= capsuleHeight ? radius : 0,
55985
+ ry: fillHeight >= capsuleHeight ? radius : 0,
55986
+ fill: fillColor,
55987
+ clipPath: target > 0 ? `url(#${clipId})` : void 0
55988
+ }
55989
+ )
55990
+ ] }, `daily-output-capsule-${index}`)
55991
+ );
55992
+ });
55993
+ return capsules.length > 0 ? /* @__PURE__ */ jsx("g", { children: capsules }) : null;
55994
+ };
55140
55995
  var CustomTooltip3 = ({ active, payload, label, isUptimeMode }) => {
55141
55996
  if (!active || !payload || payload.length === 0) return null;
55142
55997
  if (isUptimeMode) {
@@ -55316,28 +56171,20 @@ var WorkspaceMonthlyHistory = ({
55316
56171
  });
55317
56172
  }
55318
56173
  const yAxisMax2 = maxHours > 0 ? maxHours * 1.1 : 1;
55319
- return { data: dailyData, yAxisMax: yAxisMax2, lastSetTarget: 0 };
56174
+ return { data: dailyData, yAxisMax: yAxisMax2, targetValues: [], targetLegend: "" };
55320
56175
  }
55321
56176
  let maxOutput = 0;
55322
- let lastSetTarget = 0;
55323
- for (let i = rangeDateKeys.length - 1; i >= 0; i--) {
55324
- const dateKey = rangeDateKeys[i];
55325
- const dayData = analysisMonthlyDataByKey.get(dateKey);
55326
- const shiftData = dayData ? getShiftData(dayData, selectedShiftId) : null;
55327
- const idealOutput = shiftData ? shiftData.idealOutput : 0;
55328
- if (idealOutput > 0) {
55329
- lastSetTarget = idealOutput;
55330
- break;
55331
- }
55332
- }
56177
+ let maxTarget = 0;
55333
56178
  for (const dateKey of rangeDateKeys) {
55334
56179
  const dayData = analysisMonthlyDataByKey.get(dateKey);
55335
56180
  const dayNumber = Number(dateKey.slice(-2));
56181
+ const isOffDay = isScheduledOffDay2(dateKey, shiftConfig, selectedShiftId);
55336
56182
  const shiftData = dayData ? getShiftData(dayData, selectedShiftId) : null;
55337
- const output = shiftData && hasRealData(shiftData) ? shiftData.output : 0;
55338
- const idealOutput = shiftData ? shiftData.idealOutput : 0;
56183
+ const output = !isOffDay && shiftData && hasRealData(shiftData) ? shiftData.output : 0;
56184
+ const targetOutput = isOffDay ? 0 : resolveDailyTargetOutput2(shiftData);
55339
56185
  if (output > maxOutput) maxOutput = output;
55340
- const color2 = output >= lastSetTarget ? "#00AB45" : "#E34329";
56186
+ if (targetOutput > maxTarget) maxTarget = targetOutput;
56187
+ const color2 = targetOutput > 0 && output >= targetOutput ? "#00AB45" : "#E34329";
55341
56188
  dailyData.push({
55342
56189
  hour: getOrdinal2(dayNumber),
55343
56190
  // Using ordinal format (1st, 2nd, 3rd, etc.)
@@ -55345,21 +56192,29 @@ var WorkspaceMonthlyHistory = ({
55345
56192
  output,
55346
56193
  originalOutput: output,
55347
56194
  // For label display
55348
- idealOutput,
56195
+ idealOutput: targetOutput,
56196
+ targetOutput,
55349
56197
  efficiency: shiftData && hasRealData(shiftData) ? shiftData.efficiency : 0,
55350
56198
  color: color2,
55351
56199
  idleMinutes: 0
55352
56200
  // Not used but keeps structure consistent
55353
56201
  });
55354
56202
  }
55355
- const calculatedMax = Math.max(maxOutput, lastSetTarget);
56203
+ const calculatedMax = Math.max(maxOutput, maxTarget);
55356
56204
  const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
55357
- return { data: dailyData, maxOutput, lastSetTarget, yAxisMax };
55358
- }, [analysisMonthlyDataByKey, rangeDateKeys, selectedShiftId, isUptimeMode, shiftWorkSeconds]);
56205
+ const targetValues = getUniqueRoundedTargets2(dailyData);
56206
+ return {
56207
+ data: dailyData,
56208
+ maxOutput,
56209
+ targetValues,
56210
+ yAxisMax,
56211
+ targetLegend: formatDailyTargetLegend2(targetValues)
56212
+ };
56213
+ }, [analysisMonthlyDataByKey, rangeDateKeys, selectedShiftId, isUptimeMode, shiftConfig, shiftWorkSeconds]);
55359
56214
  const yAxisTicks = useMemo(() => {
55360
56215
  if (isUptimeMode) return void 0;
55361
56216
  const max = chartData.yAxisMax;
55362
- const target = chartData.lastSetTarget;
56217
+ const targets = chartData.targetValues || [];
55363
56218
  if (!max || max <= 0) return void 0;
55364
56219
  const desiredIntervals = 4;
55365
56220
  const roughStep = max / desiredIntervals;
@@ -55373,7 +56228,7 @@ var WorkspaceMonthlyHistory = ({
55373
56228
  for (let v = 0; v <= max; v += step) {
55374
56229
  ticks.push(Math.round(v));
55375
56230
  }
55376
- if (target > 0) {
56231
+ targets.forEach((target) => {
55377
56232
  const roundedTarget = Math.round(target);
55378
56233
  if (!ticks.includes(roundedTarget)) {
55379
56234
  let nearestIndex = -1;
@@ -55392,9 +56247,18 @@ var WorkspaceMonthlyHistory = ({
55392
56247
  ticks.push(roundedTarget);
55393
56248
  }
55394
56249
  }
55395
- }
56250
+ });
55396
56251
  return ticks.filter((v) => v >= 0 && v <= max * 1.05).sort((a, b) => a - b);
55397
- }, [chartData.yAxisMax, chartData.lastSetTarget, isUptimeMode]);
56252
+ }, [chartData.yAxisMax, chartData.targetValues, isUptimeMode]);
56253
+ const visibleYAxisTicks = useMemo(() => {
56254
+ if (!isMobile || isUptimeMode || !yAxisTicks || yAxisTicks.length <= 3) return yAxisTicks;
56255
+ const importantTicks = /* @__PURE__ */ new Set([
56256
+ 0,
56257
+ Math.round(chartData.yAxisMax),
56258
+ ...chartData.targetValues || []
56259
+ ]);
56260
+ return yAxisTicks.filter((tick) => importantTicks.has(Math.round(tick))).sort((a, b) => a - b).slice(-3);
56261
+ }, [chartData.targetValues, chartData.yAxisMax, isMobile, isUptimeMode, yAxisTicks]);
55398
56262
  const pieChartData = useMemo(() => {
55399
56263
  const aggregateMode = isUptimeMode ? "uptime" : "output";
55400
56264
  const validShifts = analysisMonthlyData.map((d) => getShiftData(d, selectedShiftId)).filter(
@@ -55784,22 +56648,22 @@ var WorkspaceMonthlyHistory = ({
55784
56648
  ] }),
55785
56649
  (!isAssemblyWorkspace || isUptimeMode) && /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-sm border border-gray-100 p-4 flex-1", children: [
55786
56650
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-bold text-gray-700 mb-3 text-left", children: isUptimeMode ? "Daily Utilization" : "Daily Output" }),
55787
- /* @__PURE__ */ jsx("div", { style: { height: "220px" }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
56651
+ /* @__PURE__ */ jsx("div", { style: { height: isMobile ? "150px" : "220px" }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
55788
56652
  BarChart$1,
55789
56653
  {
55790
56654
  data: chartData.data,
55791
- margin: { top: 20, right: 10, bottom: 40, left: 10 },
56655
+ margin: isMobile ? { top: 8, right: 4, bottom: 28, left: 0 } : { top: 20, right: 10, bottom: 40, left: 10 },
55792
56656
  children: [
55793
56657
  /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false, stroke: "#f3f4f6" }),
55794
56658
  /* @__PURE__ */ jsx(
55795
56659
  XAxis,
55796
56660
  {
55797
56661
  dataKey: "hour",
55798
- tick: { fontSize: 10, fill: "#6b7280" },
55799
- interval: isMobile ? "preserveStartEnd" : 0,
56662
+ tick: { fontSize: isMobile ? 9 : 10, fill: "#6b7280" },
56663
+ interval: isMobile ? 3 : 0,
55800
56664
  angle: -45,
55801
56665
  textAnchor: "end",
55802
- height: 60
56666
+ height: isMobile ? 42 : 60
55803
56667
  }
55804
56668
  ),
55805
56669
  isUptimeMode ? /* @__PURE__ */ jsx(
@@ -55814,13 +56678,12 @@ var WorkspaceMonthlyHistory = ({
55814
56678
  YAxis,
55815
56679
  {
55816
56680
  domain: [0, chartData.yAxisMax],
55817
- width: 40,
55818
- ticks: yAxisTicks,
56681
+ width: isMobile ? 34 : 40,
56682
+ ticks: visibleYAxisTicks,
55819
56683
  tick: (props) => {
55820
56684
  const { x, y, payload } = props;
55821
56685
  const value = Math.round(payload.value);
55822
- const targetValue = Math.round(chartData.lastSetTarget);
55823
- const isTarget = value === targetValue && targetValue > 0;
56686
+ const isTarget = (chartData.targetValues || []).includes(value);
55824
56687
  return /* @__PURE__ */ jsx(
55825
56688
  "text",
55826
56689
  {
@@ -55843,15 +56706,7 @@ var WorkspaceMonthlyHistory = ({
55843
56706
  content: (props) => /* @__PURE__ */ jsx(CustomTooltip3, { ...props, isUptimeMode })
55844
56707
  }
55845
56708
  ),
55846
- !isUptimeMode && chartData.lastSetTarget > 0 && /* @__PURE__ */ jsx(
55847
- ReferenceLine,
55848
- {
55849
- y: chartData.lastSetTarget,
55850
- stroke: "#E34329",
55851
- strokeDasharray: "5 5",
55852
- strokeWidth: 2
55853
- }
55854
- ),
56709
+ !isUptimeMode && /* @__PURE__ */ jsx(Customized, { component: (props) => renderDailyOutputCapsules2(props, chartData.data) }),
55855
56710
  isUptimeMode ? /* @__PURE__ */ jsxs(Fragment, { children: [
55856
56711
  /* @__PURE__ */ jsx(
55857
56712
  Bar,
@@ -55884,6 +56739,8 @@ var WorkspaceMonthlyHistory = ({
55884
56739
  {
55885
56740
  dataKey: "output",
55886
56741
  radius: [4, 4, 0, 0],
56742
+ fill: "transparent",
56743
+ opacity: 0,
55887
56744
  isAnimationActive: true,
55888
56745
  animationBegin: 0,
55889
56746
  animationDuration: 1e3,
@@ -55927,13 +56784,9 @@ var WorkspaceMonthlyHistory = ({
55927
56784
  /* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full", style: { backgroundColor: "#e5e7eb" } }),
55928
56785
  /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600", children: "Idle" })
55929
56786
  ] })
55930
- ] }) : chartData.lastSetTarget > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
55931
- /* @__PURE__ */ jsx("div", { className: "w-12 h-0.5 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
55932
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-600", children: [
55933
- "Target: ",
55934
- Math.round(chartData.lastSetTarget),
55935
- " units/day"
55936
- ] })
56787
+ ] }) : chartData.targetValues.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
56788
+ /* @__PURE__ */ jsx("div", { className: "relative h-5 w-3 rounded-full border border-[#E34329] bg-[#fff5f3] overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 h-1/2 bg-[#E34329]" }) }),
56789
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600", children: chartData.targetLegend })
55937
56790
  ] }) })
55938
56791
  ] })
55939
56792
  ] })
@@ -58000,7 +58853,14 @@ var WorkspaceHealthCard = ({
58000
58853
  event.stopPropagation();
58001
58854
  event.preventDefault();
58002
58855
  if (onViewDetails) {
58003
- onViewDetails(workspace);
58856
+ onViewDetails(workspace, "camera", "card_button");
58857
+ }
58858
+ };
58859
+ const handleLightChipClick = (event) => {
58860
+ event.stopPropagation();
58861
+ event.preventDefault();
58862
+ if (onViewDetails) {
58863
+ onViewDetails(workspace, "light", "light_chip");
58004
58864
  }
58005
58865
  };
58006
58866
  const handleKeyDown = (event) => {
@@ -58078,6 +58938,22 @@ var WorkspaceHealthCard = ({
58078
58938
  };
58079
58939
  };
58080
58940
  const downtimeConfig = getDowntimeConfig(workspace.uptimeDetails);
58941
+ const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary.bulbIp);
58942
+ const lightStatus = workspace.lightSummary?.currentStatus || null;
58943
+ const lightChipLabel = (() => {
58944
+ if (!workspace.lightSummary) return "Light --";
58945
+ if (lightStatus === "down") return "Light offline";
58946
+ if (lightStatus === "unknown") return "Light unknown";
58947
+ if (typeof workspace.lightSummary.uptimePercent === "number") {
58948
+ return `Light ${workspace.lightSummary.uptimePercent.toFixed(1)}%`;
58949
+ }
58950
+ if (lightStatus === "up") return "Light operational";
58951
+ return "Light --";
58952
+ })();
58953
+ const lightChipClassName = clsx(
58954
+ "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-semibold transition-all",
58955
+ lightStatus === "down" ? "border-rose-200 bg-rose-50 text-rose-700 hover:bg-rose-100" : lightStatus === "unknown" ? "border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100" : "border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100"
58956
+ );
58081
58957
  return /* @__PURE__ */ jsx(
58082
58958
  Card2,
58083
58959
  {
@@ -58147,6 +59023,20 @@ var WorkspaceHealthCard = ({
58147
59023
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-slate-500", children: "Camera IP:" }),
58148
59024
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: "N/A" })
58149
59025
  ] }),
59026
+ hasLightConfig && /* @__PURE__ */ jsxs(
59027
+ "button",
59028
+ {
59029
+ type: "button",
59030
+ onClick: handleLightChipClick,
59031
+ className: lightChipClassName,
59032
+ "aria-label": `Open light timeline for ${workspace.workspace_display_name || workspace.workspace_id}`,
59033
+ title: workspace.lightSummary?.bulbIp ? `Bulb IP ${workspace.lightSummary.bulbIp}` : "Light timeline",
59034
+ children: [
59035
+ /* @__PURE__ */ jsx(Lightbulb, { className: "h-3.5 w-3.5" }),
59036
+ /* @__PURE__ */ jsx("span", { children: lightChipLabel })
59037
+ ]
59038
+ }
59039
+ ),
58150
59040
  /* @__PURE__ */ jsx("div", { className: "mt-3 pt-3 border-t border-slate-100 dark:border-slate-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
58151
59041
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wide", children: downtimeConfig.label }),
58152
59042
  /* @__PURE__ */ jsx("span", { className: clsx("text-sm font-semibold", downtimeConfig.className), children: downtimeConfig.text })
@@ -58216,7 +59106,7 @@ var CompactWorkspaceHealthCard = ({
58216
59106
  event.stopPropagation();
58217
59107
  event.preventDefault();
58218
59108
  if (onViewDetails) {
58219
- onViewDetails(workspace);
59109
+ onViewDetails(workspace, "camera", "card_button");
58220
59110
  }
58221
59111
  };
58222
59112
  return /* @__PURE__ */ jsxs(
@@ -67563,6 +68453,17 @@ var setSessionSeenValue = (key) => {
67563
68453
  };
67564
68454
  var buildAllGreenCelebrationSeenKey = (identity) => `${ALL_GREEN_CELEBRATION_SEEN_PREFIX}${identity}`;
67565
68455
  var buildAllGreenMilestoneSeenKey = (identity, milestoneSeconds) => `${ALL_GREEN_MILESTONE_SEEN_PREFIX}${identity}:${milestoneSeconds}`;
68456
+ var LINE_SELECTOR_INDICATOR_VERSION = "incident_exclamation_v1";
68457
+ var LineSelectorIncidentIcon = ({ lineId }) => /* @__PURE__ */ jsx(
68458
+ "span",
68459
+ {
68460
+ "data-testid": `line-selector-incident-icon-${lineId}`,
68461
+ "aria-label": "Line needs attention",
68462
+ role: "img",
68463
+ className: "inline-flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full border border-rose-200 bg-white text-[13px] font-semibold leading-none text-rose-600 shadow-[0_1px_2px_rgba(15,23,42,0.06)]",
68464
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "-mt-px", children: "!" })
68465
+ }
68466
+ );
67566
68467
  var LoadingPageCmp = LoadingPage_default;
67567
68468
  var LoadingOverlayCmp = LoadingOverlay_default;
67568
68469
  function HomeView({
@@ -67902,25 +68803,49 @@ function HomeView({
67902
68803
  const currentIsCurrentScopeResolved = isBootstrapMonitorMode ? bootstrapMonitor.isCurrentScopeResolved : legacyIsCurrentScopeResolved;
67903
68804
  const currentMetricsError = isBootstrapMonitorMode ? bootstrapMonitor.error : legacyMetricsError;
67904
68805
  const currentRefetchMetrics = isBootstrapMonitorMode ? bootstrapMonitor.refetch : refetchLegacyMetrics;
67905
- const lineSelectorSignalStatusByLine = useMemo(() => {
67906
- const statusByLine = /* @__PURE__ */ new Map();
68806
+ const lineSelectorIndicatorByLine = useMemo(() => {
68807
+ const indicatorByLine = /* @__PURE__ */ new Map();
67907
68808
  const legend = currentEfficiencyLegend || DEFAULT_EFFICIENCY_LEGEND;
67908
68809
  const addRows = (rows) => {
67909
68810
  (rows || []).forEach((row) => {
67910
68811
  const lineId = typeof row?.line_id === "string" ? row.line_id : "";
67911
- if (!lineId || statusByLine.has(lineId)) {
68812
+ if (!lineId) {
68813
+ return;
68814
+ }
68815
+ if (row?.red_flow_incident?.active === true) {
68816
+ indicatorByLine.set(lineId, "incident");
68817
+ return;
68818
+ }
68819
+ if (indicatorByLine.get(lineId) === "incident") {
67912
68820
  return;
67913
68821
  }
67914
68822
  const status = getKpiSignalStatus(row?.line_signal, legend);
67915
- if (status) {
67916
- statusByLine.set(lineId, status);
68823
+ if (status === "attention") {
68824
+ indicatorByLine.set(lineId, "red");
67917
68825
  }
67918
68826
  });
67919
68827
  };
67920
68828
  addRows(currentSelectorLineMetrics);
67921
68829
  addRows(currentLineMetrics);
67922
- return statusByLine;
68830
+ return indicatorByLine;
67923
68831
  }, [currentEfficiencyLegend, currentLineMetrics, currentSelectorLineMetrics]);
68832
+ const lineSelectorIndicatorStats = useMemo(() => {
68833
+ let incidentLineCount = 0;
68834
+ let redFlowLineCount = 0;
68835
+ visibleLineIds.forEach((lineId) => {
68836
+ const indicator = lineSelectorIndicatorByLine.get(lineId);
68837
+ if (indicator === "incident") {
68838
+ incidentLineCount += 1;
68839
+ } else if (indicator === "red") {
68840
+ redFlowLineCount += 1;
68841
+ }
68842
+ });
68843
+ return {
68844
+ incidentLineCount,
68845
+ redFlowLineCount,
68846
+ hasAnyIncident: incidentLineCount > 0
68847
+ };
68848
+ }, [lineSelectorIndicatorByLine, visibleLineIds]);
67924
68849
  const metricsDisplayNames = useMemo(() => {
67925
68850
  const nextDisplayNames = {};
67926
68851
  currentWorkspaceMetrics.forEach((workspace) => {
@@ -68777,9 +69702,12 @@ function HomeView({
68777
69702
  new_line_ids: normalizedLineIds,
68778
69703
  selected_line_count: normalizedLineIds.length,
68779
69704
  selection_mode: isAllLinesSelection(normalizedLineIds) ? "all" : normalizedLineIds.length === 1 ? "single" : "custom",
69705
+ incident_line_count: lineSelectorIndicatorStats.incidentLineCount,
69706
+ red_flow_line_count: lineSelectorIndicatorStats.redFlowLineCount,
69707
+ selector_indicator_version: LINE_SELECTOR_INDICATOR_VERSION,
68780
69708
  line_name: getLineSelectionLabel(normalizedLineIds)
68781
69709
  });
68782
- }, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
69710
+ }, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, lineSelectorIndicatorStats, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
68783
69711
  useCallback(() => {
68784
69712
  updateSelectedLineIds(visibleLineIds);
68785
69713
  }, [updateSelectedLineIds, visibleLineIds]);
@@ -68837,7 +69765,10 @@ function HomeView({
68837
69765
  current_display_mode_label: getHomeDisplayModeLabel(displayMode),
68838
69766
  selected_line_ids: selectedLineIds,
68839
69767
  selected_line_count: selectedLineIds.length,
68840
- is_all_lines: isAllLinesSelection(selectedLineIds)
69768
+ is_all_lines: isAllLinesSelection(selectedLineIds),
69769
+ incident_line_count: lineSelectorIndicatorStats.incidentLineCount,
69770
+ red_flow_line_count: lineSelectorIndicatorStats.redFlowLineCount,
69771
+ selector_indicator_version: LINE_SELECTOR_INDICATOR_VERSION
68841
69772
  });
68842
69773
  }
68843
69774
  },
@@ -68878,8 +69809,8 @@ function HomeView({
68878
69809
  ] }),
68879
69810
  /* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-0.5 overflow-y-auto pr-1", children: visibleLineIds.map((lineId) => {
68880
69811
  const isChecked = pendingSelectedLineIds.includes(lineId);
68881
- const signalStatus = lineSelectorSignalStatusByLine.get(lineId);
68882
- const signalDotClass = signalStatus === "stable" ? "bg-green-500" : signalStatus === "warning" ? "bg-yellow-400" : signalStatus === "attention" ? "bg-red-500" : "";
69812
+ const selectorIndicator = lineSelectorIndicatorByLine.get(lineId);
69813
+ const lineLabel = mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}`;
68883
69814
  return /* @__PURE__ */ jsxs(
68884
69815
  "label",
68885
69816
  {
@@ -68889,6 +69820,7 @@ function HomeView({
68889
69820
  "input",
68890
69821
  {
68891
69822
  type: "checkbox",
69823
+ "aria-label": lineLabel,
68892
69824
  className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
68893
69825
  checked: isChecked,
68894
69826
  onChange: () => {
@@ -68904,12 +69836,12 @@ function HomeView({
68904
69836
  }
68905
69837
  }
68906
69838
  ),
68907
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}` }),
68908
- /* @__PURE__ */ jsx("span", { className: "flex h-2.5 w-2.5 flex-shrink-0 items-center justify-center", children: signalStatus ? /* @__PURE__ */ jsx(
69839
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: lineLabel }),
69840
+ /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center", children: selectorIndicator === "incident" ? /* @__PURE__ */ jsx(LineSelectorIncidentIcon, { lineId }) : selectorIndicator === "red" && !lineSelectorIndicatorStats.hasAnyIncident ? /* @__PURE__ */ jsx(
68909
69841
  "span",
68910
69842
  {
68911
69843
  "data-testid": `line-selector-signal-dot-${lineId}`,
68912
- className: `h-2 w-2 rounded-full ${signalDotClass}`,
69844
+ className: "h-2 w-2 rounded-full bg-red-500",
68913
69845
  "aria-hidden": "true"
68914
69846
  }
68915
69847
  ) : null })
@@ -68941,7 +69873,8 @@ function HomeView({
68941
69873
  mergedLineNames,
68942
69874
  selectedLineIds,
68943
69875
  pendingSelectedLineIds,
68944
- lineSelectorSignalStatusByLine,
69876
+ lineSelectorIndicatorByLine,
69877
+ lineSelectorIndicatorStats,
68945
69878
  displayMode,
68946
69879
  slideshowActiveLineId,
68947
69880
  visibleLineIds,
@@ -83902,11 +84835,13 @@ var useWorkspaceHealth = (options) => {
83902
84835
  var STATUS_COLORS = {
83903
84836
  up: "bg-emerald-500",
83904
84837
  down: "bg-rose-500",
84838
+ unknown: "bg-amber-400",
83905
84839
  pending: "bg-gray-200"
83906
84840
  };
83907
84841
  var STATUS_TITLES = {
83908
84842
  up: "Uptime",
83909
84843
  down: "Downtime",
84844
+ unknown: "Unknown",
83910
84845
  pending: "Pending"
83911
84846
  };
83912
84847
  var formatTime4 = (date, timezone) => new Intl.DateTimeFormat("en-IN", {
@@ -83949,7 +84884,9 @@ var UptimeTimelineStrip = ({
83949
84884
  timezone,
83950
84885
  className = "",
83951
84886
  uptimePercentage = null,
83952
- downtimeMinutes = 0
84887
+ downtimeMinutes = 0,
84888
+ metricLabel = "uptime",
84889
+ summaryText
83953
84890
  }) => {
83954
84891
  const segments = useMemo(() => {
83955
84892
  if (!points.length || totalMinutes <= 0) return [];
@@ -84013,9 +84950,11 @@ var UptimeTimelineStrip = ({
84013
84950
  return /* @__PURE__ */ jsx("div", { className: "w-full rounded-xl border border-dashed border-gray-200 bg-gray-50/50 p-6 text-center text-sm text-gray-600", children: "No uptime data available for this shift yet." });
84014
84951
  }
84015
84952
  return /* @__PURE__ */ jsxs("div", { className: `relative w-full ${className}`, children: [
84016
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs("span", { className: "text-gray-900 font-semibold", children: [
84953
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center mb-3 text-sm", children: summaryText ? /* @__PURE__ */ jsx("span", { className: "text-gray-900 font-semibold", children: summaryText }) : typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs("span", { className: "text-gray-900 font-semibold", children: [
84017
84954
  uptimePercentage.toFixed(1),
84018
- " % uptime ",
84955
+ " % ",
84956
+ metricLabel,
84957
+ " ",
84019
84958
  downtimeMinutes > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
84020
84959
  "(",
84021
84960
  formatDowntimeLabel(downtimeMinutes),
@@ -84083,6 +85022,27 @@ var formatDowntimeLabel2 = (minutes, includeSuffix = true) => {
84083
85022
  const label = formatDuration4(minutes);
84084
85023
  return includeSuffix ? `${label} down` : label;
84085
85024
  };
85025
+ var formatSecondsDuration = (seconds) => {
85026
+ if (!seconds || seconds <= 0) return "0 min";
85027
+ return formatDuration4(Math.max(1, Math.ceil(seconds / 60)));
85028
+ };
85029
+ var getLightStatusLabel = (status) => {
85030
+ if (status === "up") return "operational";
85031
+ if (status === "down") return "offline";
85032
+ if (status === "unknown") return "unknown";
85033
+ return "status unavailable";
85034
+ };
85035
+ var getLightDurationPrefix = (status) => {
85036
+ if (status === "up") return "Operational";
85037
+ if (status === "down") return "Offline";
85038
+ if (status === "unknown") return "Unknown";
85039
+ return "Current";
85040
+ };
85041
+ var formatLightError = (error) => {
85042
+ if (!error) return null;
85043
+ const cleaned = error.replace(/\s*\(after retry\)\s*$/i, "").trim();
85044
+ return cleaned || null;
85045
+ };
84086
85046
  var formatTimeRange = (start, end, timezone) => {
84087
85047
  const formatter = new Intl.DateTimeFormat("en-IN", {
84088
85048
  hour: "numeric",
@@ -84098,12 +85058,15 @@ var WorkspaceUptimeDetailModal = ({
84098
85058
  onClose,
84099
85059
  shiftConfig: passedShiftConfig,
84100
85060
  date,
84101
- shiftId
85061
+ shiftId,
85062
+ initialMode = "camera"
84102
85063
  }) => {
84103
85064
  const timezone = useAppTimezone() || "UTC";
84104
85065
  const logsContainerRef = useRef(null);
84105
85066
  const [showScrollIndicator, setShowScrollIndicator] = useState(false);
85067
+ const [activeMode, setActiveMode] = useState(initialMode);
84106
85068
  const isHistorical = Boolean(date);
85069
+ const hasLightTimeline = Boolean(workspace?.lightSummary?.hasLightConfig && workspace?.lightSummary?.bulbIp);
84107
85070
  const {
84108
85071
  timeline,
84109
85072
  loading,
@@ -84112,7 +85075,7 @@ var WorkspaceUptimeDetailModal = ({
84112
85075
  } = useWorkspaceUptimeTimeline({
84113
85076
  workspaceId: workspace?.workspace_id,
84114
85077
  companyId: workspace?.company_id,
84115
- enabled: isOpen && Boolean(workspace?.workspace_id && workspace?.company_id),
85078
+ enabled: isOpen && activeMode === "camera" && Boolean(workspace?.workspace_id && workspace?.company_id),
84116
85079
  refreshInterval: isHistorical ? void 0 : 6e4,
84117
85080
  // Disable auto-refresh for historical
84118
85081
  lineId: workspace?.line_id,
@@ -84125,6 +85088,25 @@ var WorkspaceUptimeDetailModal = ({
84125
85088
  shiftId
84126
85089
  // Pass override shift ID for historical queries
84127
85090
  });
85091
+ const {
85092
+ timeline: lightTimeline,
85093
+ loading: lightLoading,
85094
+ error: lightError,
85095
+ refetch: refetchLight
85096
+ } = useWorkspaceLightTimeline({
85097
+ workspaceId: workspace?.workspace_id,
85098
+ enabled: isOpen && activeMode === "light" && hasLightTimeline && Boolean(workspace?.workspace_id),
85099
+ refreshInterval: isHistorical ? void 0 : 6e4,
85100
+ lineId: workspace?.line_id,
85101
+ shiftConfig: passedShiftConfig || void 0,
85102
+ timezone,
85103
+ date,
85104
+ shiftId
85105
+ });
85106
+ useEffect(() => {
85107
+ if (!isOpen) return;
85108
+ setActiveMode(initialMode === "light" && hasLightTimeline ? "light" : "camera");
85109
+ }, [hasLightTimeline, initialMode, isOpen]);
84128
85110
  useEffect(() => {
84129
85111
  if (!isOpen || !workspace) return;
84130
85112
  const handleKeyDown = (event) => {
@@ -84137,13 +85119,21 @@ var WorkspaceUptimeDetailModal = ({
84137
85119
  window.removeEventListener("keydown", handleKeyDown);
84138
85120
  };
84139
85121
  }, [isOpen, onClose, workspace]);
84140
- const shiftStart = timeline ? new Date(timeline.shiftStart) : null;
84141
- 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;
84142
85128
  const downtimeSegments = timeline?.downtimeSegments || [];
84143
85129
  downtimeSegments.length;
84144
85130
  const downtimeMinutes = timeline?.downtimeMinutes ?? 0;
84145
- const hasTimelineData = Boolean(timeline?.hasData);
84146
- const uptimePercentage = hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
85131
+ const hasTimelineData = activeMode === "light" ? Boolean(lightTimeline?.hasData) : Boolean(timeline?.hasData);
85132
+ const uptimePercentage = activeMode === "light" ? lightTimeline?.uptimePercentage ?? null : hasTimelineData ? timeline?.uptimePercentage ?? workspace?.uptimePercentage ?? null : null;
85133
+ const lightStatus = lightTimeline?.currentStatus || workspace?.lightSummary?.currentStatus || null;
85134
+ const lightStatusText = `Light ${getLightStatusLabel(lightStatus)}`;
85135
+ const lightDurationText = lightTimeline?.currentDurationSeconds ? `${getLightDurationPrefix(lightStatus)} for ${formatSecondsDuration(lightTimeline.currentDurationSeconds)}` : null;
85136
+ const lightLastError = formatLightError(lightTimeline?.lastError);
84147
85137
  const allInterruptionsSorted = useMemo(
84148
85138
  () => [...downtimeSegments].sort(
84149
85139
  (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime()
@@ -84165,7 +85155,7 @@ var WorkspaceUptimeDetailModal = ({
84165
85155
  container.addEventListener("scroll", checkScroll);
84166
85156
  return () => container.removeEventListener("scroll", checkScroll);
84167
85157
  }
84168
- }, [downtimeSegments]);
85158
+ }, [downtimeSegments, lightTimeline?.statusSegments, activeMode]);
84169
85159
  const renderSegment = (segment) => {
84170
85160
  const start = new Date(segment.startTime);
84171
85161
  const end = new Date(segment.endTime);
@@ -84186,6 +85176,63 @@ var WorkspaceUptimeDetailModal = ({
84186
85176
  `${segment.startMinuteIndex}-${segment.endMinuteIndex}`
84187
85177
  );
84188
85178
  };
85179
+ const renderLightSegment = (segment) => {
85180
+ const start = new Date(segment.startTime);
85181
+ const end = new Date(segment.endTime);
85182
+ const startLabel = new Intl.DateTimeFormat("en-IN", {
85183
+ hour: "numeric",
85184
+ minute: "2-digit",
85185
+ hour12: true,
85186
+ timeZone: timezone
85187
+ }).format(start);
85188
+ const endLabel = segment.isCurrent ? "Now" : new Intl.DateTimeFormat("en-IN", {
85189
+ hour: "numeric",
85190
+ minute: "2-digit",
85191
+ hour12: true,
85192
+ timeZone: timezone
85193
+ }).format(end);
85194
+ const containerClasses = segment.status === "down" ? "border-rose-200 bg-rose-50" : "border-amber-200 bg-amber-50";
85195
+ return /* @__PURE__ */ jsxs(
85196
+ "div",
85197
+ {
85198
+ className: `rounded-lg border px-5 py-3 ${containerClasses}`,
85199
+ children: [
85200
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-semibold text-gray-900", children: [
85201
+ startLabel,
85202
+ " - ",
85203
+ endLabel
85204
+ ] }),
85205
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600 mt-1", children: [
85206
+ getLightDurationPrefix(segment.status),
85207
+ " for ",
85208
+ formatSecondsDuration(segment.durationSeconds)
85209
+ ] }),
85210
+ formatLightError(segment.lastError) && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs font-medium text-rose-700", children: formatLightError(segment.lastError) })
85211
+ ]
85212
+ },
85213
+ `${segment.status}-${segment.startMinuteIndex}-${segment.endMinuteIndex}`
85214
+ );
85215
+ };
85216
+ const handleModeChange = (mode) => {
85217
+ if (mode === activeMode) return;
85218
+ const previousMode = activeMode;
85219
+ setActiveMode(mode);
85220
+ trackCoreEvent("Health Timeline Mode Changed", {
85221
+ previous_mode: previousMode,
85222
+ selected_mode: mode,
85223
+ source: "segmented_control",
85224
+ workspace_id: workspace?.workspace_id,
85225
+ line_id: workspace?.line_id
85226
+ });
85227
+ };
85228
+ const handleRefresh = () => {
85229
+ trackCoreEvent("Health Timeline Refreshed", {
85230
+ mode: activeMode,
85231
+ workspace_id: workspace?.workspace_id,
85232
+ line_id: workspace?.line_id
85233
+ });
85234
+ activeRefetch();
85235
+ };
84189
85236
  if (!isOpen || !workspace) {
84190
85237
  return null;
84191
85238
  }
@@ -84209,13 +85256,35 @@ var WorkspaceUptimeDetailModal = ({
84209
85256
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0 mr-4", children: [
84210
85257
  /* @__PURE__ */ jsx("h2", { id: "uptime-detail-title", className: "text-2xl font-semibold text-gray-900 truncate mb-3", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 6)}` }),
84211
85258
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 text-sm text-gray-600", children: [
84212
- /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: timeline?.shiftLabel || "Current Shift" }),
85259
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: activeTimeline?.shiftLabel || "Current Shift" }),
84213
85260
  shiftStart && shiftEnd && /* @__PURE__ */ jsxs(Fragment, { children: [
84214
85261
  /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
84215
85262
  /* @__PURE__ */ jsx("span", { className: "text-gray-600", children: formatTimeRange(shiftStart, shiftEnd, timezone) })
84216
85263
  ] })
84217
85264
  ] }),
84218
- /* @__PURE__ */ jsxs("div", { className: "mt-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: [
84219
85288
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
84220
85289
  /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${workspace.status === "healthy" ? "bg-emerald-500 animate-pulse" : workspace.status === "warning" ? "bg-amber-500" : "bg-rose-500"}` }),
84221
85290
  /* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${workspace.status === "healthy" ? "text-emerald-700" : workspace.status === "warning" ? "text-amber-700" : "text-rose-700"}`, children: workspace.status === "healthy" ? "Operational" : workspace.status === "warning" ? "Intermittent" : "Down" })
@@ -84232,17 +85301,37 @@ var WorkspaceUptimeDetailModal = ({
84232
85301
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-600 dark:text-slate-300 select-all", children: workspace.cameraIp })
84233
85302
  ] })
84234
85303
  ] })
84235
- ] })
85304
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
85305
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
85306
+ /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${lightStatus === "up" ? "bg-emerald-500 animate-pulse" : lightStatus === "unknown" ? "bg-amber-500" : "bg-rose-500"}` }),
85307
+ /* @__PURE__ */ jsx("span", { className: `text-xs font-medium ${lightStatus === "up" ? "text-emerald-700" : lightStatus === "unknown" ? "text-amber-700" : "text-rose-700"}`, children: lightStatusText })
85308
+ ] }),
85309
+ lightTimeline?.bulbIp && /* @__PURE__ */ jsxs(Fragment, { children: [
85310
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
85311
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 bg-slate-50 px-2 py-0.5 rounded border border-slate-100", title: "Bulb IP", children: [
85312
+ /* @__PURE__ */ jsx(Lightbulb, { className: "h-3 w-3 text-slate-400" }),
85313
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-600 select-all", children: lightTimeline.bulbIp })
85314
+ ] })
85315
+ ] }),
85316
+ lightDurationText && /* @__PURE__ */ jsxs(Fragment, { children: [
85317
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
85318
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: lightDurationText })
85319
+ ] }),
85320
+ lightLastError && /* @__PURE__ */ jsxs(Fragment, { children: [
85321
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2022" }),
85322
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-rose-700", children: lightLastError })
85323
+ ] })
85324
+ ] }) })
84236
85325
  ] }),
84237
85326
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
84238
85327
  /* @__PURE__ */ jsxs(
84239
85328
  "button",
84240
85329
  {
84241
- onClick: refetch,
84242
- disabled: loading,
85330
+ onClick: handleRefresh,
85331
+ disabled: activeLoading,
84243
85332
  className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 transition-all duration-200 shadow-sm",
84244
85333
  children: [
84245
- /* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${loading ? "animate-spin" : ""}` }),
85334
+ /* @__PURE__ */ jsx(RefreshCw, { className: `h-4 w-4 ${activeLoading ? "animate-spin" : ""}` }),
84246
85335
  /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Refresh" })
84247
85336
  ]
84248
85337
  }
@@ -84258,31 +85347,63 @@ var WorkspaceUptimeDetailModal = ({
84258
85347
  )
84259
85348
  ] })
84260
85349
  ] }),
84261
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-8 py-6 space-y-6", children: 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: [
84262
85351
  /* @__PURE__ */ jsx("p", { className: "font-semibold mb-1", children: "Unable to load uptime details" }),
84263
- /* @__PURE__ */ jsx("p", { className: "text-xs text-rose-600/90", children: error.message })
85352
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-rose-600/90", children: activeError.message })
84264
85353
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
84265
85354
  /* @__PURE__ */ jsxs("div", { className: "relative pb-4 border-b border-gray-200", children: [
84266
85355
  /* @__PURE__ */ jsx(
84267
85356
  UptimeTimelineStrip_default,
84268
85357
  {
84269
- points: timeline?.points || [],
84270
- totalMinutes: timeline?.totalMinutes || 0,
84271
- shiftStart: timeline?.shiftStart || (/* @__PURE__ */ new Date()).toISOString(),
84272
- 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(),
84273
85362
  timezone,
84274
85363
  uptimePercentage,
84275
- downtimeMinutes: hasTimelineData ? downtimeMinutes : 0
85364
+ downtimeMinutes: activeMode === "light" ? 0 : hasTimelineData ? downtimeMinutes : 0,
85365
+ metricLabel: activeMode === "light" ? "light uptime" : "uptime",
85366
+ summaryText: activeMode === "light" && typeof uptimePercentage === "number" ? /* @__PURE__ */ jsxs(Fragment, { children: [
85367
+ uptimePercentage.toFixed(1),
85368
+ " % light uptime",
85369
+ (lightTimeline?.downSeconds || 0) > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
85370
+ " (",
85371
+ formatSecondsDuration(lightTimeline?.downSeconds || 0),
85372
+ " offline)"
85373
+ ] }),
85374
+ (lightTimeline?.unknownSeconds || 0) > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-600 font-normal", children: [
85375
+ " (",
85376
+ formatSecondsDuration(lightTimeline?.unknownSeconds || 0),
85377
+ " unknown)"
85378
+ ] })
85379
+ ] }) : void 0
84276
85380
  }
84277
85381
  ),
84278
- 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: [
84279
85383
  /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4 animate-spin text-gray-500" }),
84280
85384
  /* @__PURE__ */ jsx("span", { children: "Updating timeline\u2026" })
84281
85385
  ] })
84282
85386
  ] }),
84283
85387
  /* @__PURE__ */ jsxs("div", { className: "pt-4", children: [
84284
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3", children: "Downtime Logs" }),
84285
- !hasTimelineData ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-gray-100 bg-gray-50/50 px-5 py-4 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "No 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: [
84286
85407
  /* @__PURE__ */ jsx(
84287
85408
  "div",
84288
85409
  {
@@ -84329,6 +85450,7 @@ var WorkspaceHealthView = ({
84329
85450
  const timezone = useAppTimezone();
84330
85451
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(effectiveLineIdForShiftConfig);
84331
85452
  const [selectedWorkspace, setSelectedWorkspace] = useState(null);
85453
+ const [selectedTimelineMode, setSelectedTimelineMode] = useState("camera");
84332
85454
  const [selectedDate, setSelectedDate] = useState(void 0);
84333
85455
  const [selectedShiftId, setSelectedShiftId] = useState(void 0);
84334
85456
  const [showDatePicker, setShowDatePicker] = useState(false);
@@ -84388,11 +85510,33 @@ var WorkspaceHealthView = ({
84388
85510
  },
84389
85511
  [router, onNavigate]
84390
85512
  );
84391
- const handleViewDetails = useCallback((workspace) => {
85513
+ const handleViewDetails = useCallback((workspace, mode = "camera", source = "card_button") => {
85514
+ const hasLightConfig = Boolean(workspace.lightSummary?.hasLightConfig && workspace.lightSummary?.bulbIp);
85515
+ if (source === "light_chip") {
85516
+ trackCoreEvent("Health Light Chip Clicked", {
85517
+ workspace_id: workspace.workspace_id,
85518
+ line_id: workspace.line_id,
85519
+ light_status: workspace.lightSummary?.currentStatus || null,
85520
+ uptime_percent: workspace.lightSummary?.uptimePercent ?? null,
85521
+ bulb_ip_present: Boolean(workspace.lightSummary?.bulbIp)
85522
+ });
85523
+ }
85524
+ trackCoreEvent("Health Timeline Opened", {
85525
+ source,
85526
+ initial_mode: mode,
85527
+ workspace_id: workspace.workspace_id,
85528
+ line_id: workspace.line_id,
85529
+ has_light_config: hasLightConfig,
85530
+ light_status: workspace.lightSummary?.currentStatus || null,
85531
+ selected_date: selectedDate || operationalDate,
85532
+ selected_shift_id: selectedShiftId ?? currentShiftDetails.shiftId
85533
+ });
85534
+ setSelectedTimelineMode(mode);
84392
85535
  setSelectedWorkspace(workspace);
84393
- }, []);
85536
+ }, [currentShiftDetails.shiftId, operationalDate, selectedDate, selectedShiftId]);
84394
85537
  const handleCloseDetails = useCallback(() => {
84395
85538
  setSelectedWorkspace(null);
85539
+ setSelectedTimelineMode("camera");
84396
85540
  }, []);
84397
85541
  const getStatusIcon = (status) => {
84398
85542
  switch (status) {
@@ -84650,7 +85794,8 @@ var WorkspaceHealthView = ({
84650
85794
  onClose: handleCloseDetails,
84651
85795
  shiftConfig: modalShiftConfig,
84652
85796
  date: selectedDate,
84653
- shiftId: selectedShiftId
85797
+ shiftId: selectedShiftId,
85798
+ initialMode: selectedTimelineMode
84654
85799
  }
84655
85800
  )
84656
85801
  ] });
@@ -92903,4 +94048,4 @@ var RecentFlowSnapshotGrid = ({
92903
94048
  );
92904
94049
  };
92905
94050
 
92906
- export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPI_SIGNAL_LABELS, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LineOvertakeNotificationManager, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProductionPlanApiError, ProductionPlanView_default as ProductionPlanView, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RecentFlowSnapshotGrid, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SENTRY_HANDLED_EVENT_SESSION_LIMIT, SENTRY_HANDLED_EVENT_WINDOW_MS, SENTRY_QUOTA_STORAGE_KEY, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, addSentryBreadcrumb, aggregateKPIsFromLineMetricsRows, aggregateLineSignals, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildKpiLineHierarchy, buildLegacyLineOvertakeEventKey, buildLineLeaderboardRows, buildLineOvertakeEventKey, buildLineSkuBreakdown, buildShiftGroupsKey, canPermissionEditProductionPlan, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleEditProductionPlan, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, detectLineOvertakeEvents, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getKpiSignalLabel, getKpiSignalStatus, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeDateKeyRangeUnbounded, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, productionPlanService, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSentryQuotaForTests, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useCompanyWorkspaceHourAiSummaryEnabled, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceHourSummary, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
94051
+ export { ACTION_FAMILIES, ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, ClipsCostView_default as ClipsCostView, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EFFICIENCY_ON_TRACK_THRESHOLD, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, HourlyUptimeChart, ISTTimer_default as ISTTimer, IdleTimeVlmConfigProvider, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPI_SIGNAL_LABELS, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LineOvertakeNotificationManager, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlantHeadView_default as PlantHeadView, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProductionPlanApiError, ProductionPlanView_default as ProductionPlanView, ProfileView_default as ProfileView, ROOT_DASHBOARD_EVENT_NAMES, RecentFlowSnapshotGrid, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SENTRY_HANDLED_EVENT_SESSION_LIMIT, SENTRY_HANDLED_EVENT_WINDOW_MS, SENTRY_QUOTA_STORAGE_KEY, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UptimeDonutChart, UptimeLineChart, UptimeMetricCards, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceCycleTimeMetricCards, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, addSentryBreadcrumb, aggregateKPIsFromLineMetricsRows, aggregateLineSignals, alertsService, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildKpiLineHierarchy, buildLegacyLineOvertakeEventKey, buildLineLeaderboardRows, buildLineOvertakeEventKey, buildLineSkuBreakdown, buildShiftGroupsKey, canPermissionEditProductionPlan, canRoleAccessDashboardPath, canRoleAccessTeamManagement, canRoleAssignFactories, canRoleAssignLines, canRoleChangeRole, canRoleEditProductionPlan, canRoleInviteRole, canRoleManageCompany, canRoleManageTargets, canRoleManageUsers, canRoleRemoveUser, canRoleViewClipsCost, canRoleViewUsageStats, captureHandledFrontendException, captureSentryException, captureSentryMessage, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, combineLineMetricsRows, countRealSkus, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, detectLineOvertakeEvents, fetchIdleTimeReasons, fetchLineDummySkuId, fetchLineSkuCatalog, filterDataByDateKeyRange, filterRealSkuBreakdown, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration2 as formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getActionDisplayName, getActiveShift, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAssignableRoles, getAssignmentColumnLabel, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getCurrentWeekFullRange, getCurrentWeekToDateRange, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDateKeyFromValue, getDayDateKey, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getKpiSignalLabel, getKpiSignalStatus, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getMonthlyTrendComparisonLabel, getNextUpdateInterval, getOperationalDate, getRoleAssignmentKind, getRoleDescription, getRoleLabel, getRoleMetadata, getRoleNavPaths, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getVisibleRolesForCurrentUser, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isEfficiencyOnTrack, isFactoryScopedRole, isFullMonthRange, isIgnorableFrontendError, isLegacyConfiguration, isLoopbackHostname, isPrefetchError, isRealSku, isRecentFlowVideoGridMetricMode, isSafari, isSupervisorRole, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWipGatedVideoGridMetricMode, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, lineLeaderboardService, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeActionFamily, normalizeDateKeyRange, normalizeDateKeyRangeUnbounded, normalizeRoleLevel, normalizeVideoGridMetricMode, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, pickPreferredLineMetricsRow, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, productionPlanService, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSentryQuotaForTests, resetSubscriptionManager, resolveDefaultSkuId, resolveLiveSkuId, s3VideoPreloader, selectPreferredLineMetricsRow, setSentryUserContext, setSentryWorkspaceContext, shouldEnableLocalDevTestLogin, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyClipsCost, useCompanyFastSlowClipFiltersEnabled, useCompanyHasVlmEnabledLine, useCompanyUsersUsage, useCompanyWorkspaceHourAiSummaryEnabled, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useIdleTimeVlmConfig, useKpiTrends, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMonthlyTrend, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceHourSummary, useWorkspaceLightTimeline, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };