@optifye/dashboard-core 6.4.2 → 6.5.1

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
@@ -3,10 +3,9 @@ import React19__default, { createContext, useRef, useCallback, useState, useMemo
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useRouter } from 'next/router';
5
5
  import { toZonedTime, formatInTimeZone } from 'date-fns-tz';
6
- import { subDays, format, parseISO, isValid, isFuture, isToday } from 'date-fns';
6
+ import { subDays, format, parseISO, isValid, formatDistanceToNow, isFuture, isToday } from 'date-fns';
7
7
  import mixpanel from 'mixpanel-browser';
8
8
  import { EventEmitter } from 'events';
9
- import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
10
9
  import { REALTIME_SUBSCRIBE_STATES, createClient } from '@supabase/supabase-js';
11
10
  import Hls2 from 'hls.js';
12
11
  import useSWR from 'swr';
@@ -14,9 +13,9 @@ import { noop, warning, invariant, progress, secondsToMilliseconds, milliseconds
14
13
  import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
15
14
  import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ReferenceLine, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
16
15
  import { Slot } from '@radix-ui/react-slot';
17
- import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, Minus, ArrowDown, ArrowUp, Settings2, CheckCircle2, Search, CheckCircle, AlertTriangle, Info, Share2, Trophy, Target, Download, User, XCircle, ChevronLeft, ChevronRight, AlertCircle, Sun, Moon, MessageSquare, Trash2, RefreshCw, Menu, Send, Copy, Edit2, UserCheck, LogOut, Package, Settings, LifeBuoy, EyeOff, Eye, Zap, UserCircle } from 'lucide-react';
16
+ import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, Minus, ArrowDown, ArrowUp, Settings2, CheckCircle2, Search, Loader2, AlertCircle, Edit2, CheckCircle, AlertTriangle, Info, Share2, Trophy, Target, Download, User, XCircle, ChevronLeft, ChevronRight, Sun, Moon, MessageSquare, Trash2, RefreshCw, Menu, Send, Copy, UserCheck, LogOut, Package, TrendingUp, TrendingDown, Activity, Settings, LifeBuoy, EyeOff, Eye, Zap, UserCircle } from 'lucide-react';
18
17
  import { DayPicker, useNavigation as useNavigation$1 } from 'react-day-picker';
19
- import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, InformationCircleIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
18
+ import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, HeartIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, InformationCircleIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
20
19
  import { CheckIcon } from '@heroicons/react/24/solid';
21
20
  import html2canvas from 'html2canvas';
22
21
  import jsPDF, { jsPDF as jsPDF$1 } from 'jspdf';
@@ -24,6 +23,7 @@ import * as SelectPrimitive from '@radix-ui/react-select';
24
23
  import videojs from 'video.js';
25
24
  import 'video.js/dist/video-js.css';
26
25
  import { toast } from 'sonner';
26
+ import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
27
27
  import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
28
28
  import { Readable } from 'stream';
29
29
 
@@ -1674,6 +1674,25 @@ var workspaceService = {
1674
1674
  this._workspaceDisplayNamesCache.clear();
1675
1675
  this._cacheTimestamp = 0;
1676
1676
  },
1677
+ /**
1678
+ * Updates the display name for a workspace
1679
+ * @param workspaceId - The workspace UUID
1680
+ * @param displayName - The new display name
1681
+ * @returns Promise<void>
1682
+ */
1683
+ async updateWorkspaceDisplayName(workspaceId, displayName) {
1684
+ const supabase = _getSupabaseInstance();
1685
+ if (!supabase) throw new Error("Supabase client not initialized");
1686
+ const config = _getDashboardConfigInstance();
1687
+ const dbConfig = config?.databaseConfig;
1688
+ const workspacesTable = getTable3(dbConfig, "workspaces");
1689
+ const { error } = await supabase.from(workspacesTable).update({ display_name: displayName.trim() }).eq("id", workspaceId);
1690
+ if (error) {
1691
+ console.error(`Error updating workspace display name for ${workspaceId}:`, error);
1692
+ throw error;
1693
+ }
1694
+ this.clearWorkspaceDisplayNamesCache();
1695
+ },
1677
1696
  async updateWorkspaceAction(updates) {
1678
1697
  const supabase = _getSupabaseInstance();
1679
1698
  if (!supabase) throw new Error("Supabase client not initialized");
@@ -1817,6 +1836,209 @@ var workspaceService = {
1817
1836
  }
1818
1837
  }
1819
1838
  };
1839
+ var WorkspaceHealthService = class _WorkspaceHealthService {
1840
+ constructor() {
1841
+ this.cache = /* @__PURE__ */ new Map();
1842
+ this.cacheExpiryMs = 30 * 1e3;
1843
+ }
1844
+ // 30 seconds cache
1845
+ static getInstance() {
1846
+ if (!_WorkspaceHealthService.instance) {
1847
+ _WorkspaceHealthService.instance = new _WorkspaceHealthService();
1848
+ }
1849
+ return _WorkspaceHealthService.instance;
1850
+ }
1851
+ getFromCache(key) {
1852
+ const cached = this.cache.get(key);
1853
+ if (cached && Date.now() - cached.timestamp < this.cacheExpiryMs) {
1854
+ return cached.data;
1855
+ }
1856
+ this.cache.delete(key);
1857
+ return null;
1858
+ }
1859
+ setCache(key, data) {
1860
+ this.cache.set(key, { data, timestamp: Date.now() });
1861
+ }
1862
+ async getWorkspaceHealthStatus(options = {}) {
1863
+ const supabase = _getSupabaseInstance();
1864
+ if (!supabase) throw new Error("Supabase client not initialized");
1865
+ let query = supabase.from("workspace_health_status").select("*").order("workspace_display_name", { ascending: true });
1866
+ if (options.lineId) {
1867
+ query = query.eq("line_id", options.lineId);
1868
+ }
1869
+ if (options.companyId) {
1870
+ query = query.eq("company_id", options.companyId);
1871
+ }
1872
+ const { data, error } = await query;
1873
+ if (error) {
1874
+ console.error("Error fetching workspace health status:", error);
1875
+ throw error;
1876
+ }
1877
+ const processedData = (data || []).map((item) => this.processHealthStatus(item));
1878
+ let filteredData = processedData;
1879
+ try {
1880
+ const { data: enabledWorkspaces, error: workspaceError } = await supabase.from("workspaces").select("workspace_id, display_name").eq("enable", true);
1881
+ if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length > 0) {
1882
+ const enabledWorkspaceNames = /* @__PURE__ */ new Set();
1883
+ enabledWorkspaces.forEach((w) => {
1884
+ if (w.workspace_id) enabledWorkspaceNames.add(w.workspace_id);
1885
+ if (w.display_name) enabledWorkspaceNames.add(w.display_name);
1886
+ });
1887
+ filteredData = filteredData.filter((item) => {
1888
+ const displayName = item.workspace_display_name || "";
1889
+ return enabledWorkspaceNames.has(displayName);
1890
+ });
1891
+ } else if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length === 0) {
1892
+ return [];
1893
+ } else if (workspaceError) {
1894
+ console.error("Error fetching enabled workspaces:", workspaceError);
1895
+ }
1896
+ } catch (e) {
1897
+ console.error("Error filtering workspaces:", e);
1898
+ }
1899
+ if (options.status) {
1900
+ filteredData = filteredData.filter((item) => item.status === options.status);
1901
+ }
1902
+ if (options.searchTerm) {
1903
+ const searchLower = options.searchTerm.toLowerCase();
1904
+ filteredData = filteredData.filter(
1905
+ (item) => item.workspace_display_name?.toLowerCase().includes(searchLower) || item.line_name?.toLowerCase().includes(searchLower) || item.company_name?.toLowerCase().includes(searchLower)
1906
+ );
1907
+ }
1908
+ if (options.sortBy) {
1909
+ filteredData.sort((a, b) => {
1910
+ let compareValue = 0;
1911
+ switch (options.sortBy) {
1912
+ case "name":
1913
+ compareValue = (a.workspace_display_name || "").localeCompare(b.workspace_display_name || "");
1914
+ break;
1915
+ case "status":
1916
+ compareValue = this.getStatusPriority(a.status) - this.getStatusPriority(b.status);
1917
+ break;
1918
+ case "lastUpdate":
1919
+ compareValue = new Date(b.last_heartbeat).getTime() - new Date(a.last_heartbeat).getTime();
1920
+ break;
1921
+ }
1922
+ return options.sortOrder === "desc" ? -compareValue : compareValue;
1923
+ });
1924
+ }
1925
+ return filteredData;
1926
+ }
1927
+ async getWorkspaceHealthById(workspaceId) {
1928
+ const cacheKey = `health-${workspaceId}`;
1929
+ const cached = this.getFromCache(cacheKey);
1930
+ if (cached) return cached;
1931
+ const supabase = _getSupabaseInstance();
1932
+ if (!supabase) throw new Error("Supabase client not initialized");
1933
+ const { data, error } = await supabase.from("workspace_health_status").select("*").eq("workspace_id", workspaceId).single();
1934
+ if (error) {
1935
+ if (error.code === "PGRST116") {
1936
+ return null;
1937
+ }
1938
+ console.error("Error fetching workspace health:", error);
1939
+ throw error;
1940
+ }
1941
+ const processedData = data ? this.processHealthStatus(data) : null;
1942
+ if (processedData) {
1943
+ this.setCache(cacheKey, processedData);
1944
+ }
1945
+ return processedData;
1946
+ }
1947
+ async getHealthSummary(lineId, companyId) {
1948
+ this.clearCache();
1949
+ const workspaces = await this.getWorkspaceHealthStatus({ lineId, companyId });
1950
+ const totalWorkspaces = workspaces.length;
1951
+ const healthyWorkspaces = workspaces.filter((w) => w.status === "healthy").length;
1952
+ const unhealthyWorkspaces = workspaces.filter((w) => w.status === "unhealthy").length;
1953
+ const warningWorkspaces = workspaces.filter((w) => w.status === "warning").length;
1954
+ const uptimePercentage = totalWorkspaces > 0 ? healthyWorkspaces / totalWorkspaces * 100 : 0;
1955
+ return {
1956
+ totalWorkspaces,
1957
+ healthyWorkspaces,
1958
+ unhealthyWorkspaces,
1959
+ warningWorkspaces,
1960
+ uptimePercentage,
1961
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1962
+ };
1963
+ }
1964
+ async getHealthMetrics(workspaceId, startDate, endDate) {
1965
+ const supabase = _getSupabaseInstance();
1966
+ if (!supabase) throw new Error("Supabase client not initialized");
1967
+ return {
1968
+ avgResponseTime: 250,
1969
+ totalDowntime: 0,
1970
+ incidentCount: 0,
1971
+ mttr: 0
1972
+ };
1973
+ }
1974
+ processHealthStatus(data) {
1975
+ const now2 = /* @__PURE__ */ new Date();
1976
+ const lastHeartbeat = new Date(data.last_heartbeat);
1977
+ const minutesSinceUpdate = Math.floor((now2.getTime() - lastHeartbeat.getTime()) / (1e3 * 60));
1978
+ let status = "unknown";
1979
+ let isStale = false;
1980
+ if (data.is_healthy) {
1981
+ if (minutesSinceUpdate < 3) {
1982
+ status = "healthy";
1983
+ } else if (minutesSinceUpdate < 5) {
1984
+ status = "warning";
1985
+ isStale = true;
1986
+ } else {
1987
+ status = "unhealthy";
1988
+ isStale = true;
1989
+ }
1990
+ } else {
1991
+ status = "unhealthy";
1992
+ if (minutesSinceUpdate > 5) {
1993
+ isStale = true;
1994
+ }
1995
+ }
1996
+ const timeSinceLastUpdate = formatDistanceToNow(lastHeartbeat, { addSuffix: true });
1997
+ return {
1998
+ ...data,
1999
+ status,
2000
+ timeSinceLastUpdate,
2001
+ isStale
2002
+ };
2003
+ }
2004
+ getStatusPriority(status) {
2005
+ const priorities = {
2006
+ unhealthy: 0,
2007
+ warning: 1,
2008
+ unknown: 2,
2009
+ healthy: 3
2010
+ };
2011
+ return priorities[status];
2012
+ }
2013
+ subscribeToHealthUpdates(callback, filters) {
2014
+ const supabase = _getSupabaseInstance();
2015
+ if (!supabase) throw new Error("Supabase client not initialized");
2016
+ let subscription = supabase.channel("workspace-health-updates").on(
2017
+ "postgres_changes",
2018
+ {
2019
+ event: "*",
2020
+ schema: "public",
2021
+ table: "workspace_health_status"
2022
+ },
2023
+ (payload) => {
2024
+ if (payload.new) {
2025
+ const newData = payload.new;
2026
+ if (filters?.lineId && newData.line_id !== filters.lineId) return;
2027
+ if (filters?.companyId && newData.company_id !== filters.companyId) return;
2028
+ this.clearCache();
2029
+ callback(newData);
2030
+ }
2031
+ }
2032
+ ).subscribe();
2033
+ return () => {
2034
+ subscription.unsubscribe();
2035
+ };
2036
+ }
2037
+ clearCache() {
2038
+ this.cache.clear();
2039
+ }
2040
+ };
2041
+ var workspaceHealthService = WorkspaceHealthService.getInstance();
1820
2042
 
1821
2043
  // src/lib/services/skuService.ts
1822
2044
  var getTable4 = (dbConfig, tableName) => {
@@ -3034,35 +3256,6 @@ var useAudioService = () => {
3034
3256
  };
3035
3257
 
3036
3258
  // src/lib/utils/dateShiftUtils.ts
3037
- function getOperationalDate2(timezone = "Asia/Kolkata") {
3038
- const now2 = /* @__PURE__ */ new Date();
3039
- const localTime = new Date(now2.toLocaleString("en-US", { timeZone: timezone }));
3040
- const hour = localTime.getHours();
3041
- if (hour < 6) {
3042
- localTime.setDate(localTime.getDate() - 1);
3043
- }
3044
- return localTime.toISOString().split("T")[0];
3045
- }
3046
- function getCurrentShift2(timezone = "Asia/Kolkata") {
3047
- const now2 = /* @__PURE__ */ new Date();
3048
- const localTime = new Date(now2.toLocaleString("en-US", { timeZone: timezone }));
3049
- const hour = localTime.getHours();
3050
- if (hour >= 6 && hour < 18) {
3051
- return {
3052
- shiftId: 0,
3053
- shiftName: "Day Shift",
3054
- startTime: "06:00",
3055
- endTime: "18:00"
3056
- };
3057
- } else {
3058
- return {
3059
- shiftId: 1,
3060
- shiftName: "Night Shift",
3061
- startTime: "18:00",
3062
- endTime: "06:00"
3063
- };
3064
- }
3065
- }
3066
3259
  function isValidDateFormat(date) {
3067
3260
  return /^\d{4}-\d{2}-\d{2}$/.test(date);
3068
3261
  }
@@ -3211,6 +3404,14 @@ function parseS3Uri(s3Uri, sopCategories) {
3211
3404
  return null;
3212
3405
  }
3213
3406
  }
3407
+ function shuffleArray(array) {
3408
+ const shuffled = [...array];
3409
+ for (let i = shuffled.length - 1; i > 0; i--) {
3410
+ const j = Math.floor(Math.random() * (i + 1));
3411
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
3412
+ }
3413
+ return shuffled;
3414
+ }
3214
3415
 
3215
3416
  // src/lib/cache/clipsCache.ts
3216
3417
  var LRUCache = class _LRUCache {
@@ -3723,300 +3924,321 @@ if (typeof window !== "undefined") {
3723
3924
  });
3724
3925
  });
3725
3926
  }
3726
-
3727
- // src/lib/api/s3-clips.ts
3728
- var RequestDeduplicationCache = class {
3729
- constructor() {
3730
- this.pendingRequests = /* @__PURE__ */ new Map();
3731
- this.maxCacheSize = 1e3;
3732
- this.cleanupInterval = 3e5;
3733
- // 5 minutes
3734
- this.lastCleanup = Date.now();
3927
+ var getSupabaseClient = () => {
3928
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
3929
+ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
3930
+ if (!url || !key) {
3931
+ throw new Error("Supabase configuration missing");
3932
+ }
3933
+ return createClient(url, key);
3934
+ };
3935
+ var getAuthToken = async () => {
3936
+ try {
3937
+ const supabase = getSupabaseClient();
3938
+ const { data: { session } } = await supabase.auth.getSession();
3939
+ return session?.access_token || null;
3940
+ } catch (error) {
3941
+ console.error("[S3ClipsAPIClient] Error getting auth token:", error);
3942
+ return null;
3943
+ }
3944
+ };
3945
+ var S3ClipsAPIClient = class {
3946
+ constructor(sopCategories) {
3947
+ this.baseUrl = "/api/clips";
3948
+ this.requestCache = /* @__PURE__ */ new Map();
3949
+ this.sopCategories = sopCategories;
3950
+ console.log("[S3ClipsAPIClient] \u2705 Initialized - Using secure API routes (no direct S3 access)");
3735
3951
  }
3736
3952
  /**
3737
- * Get or create a deduplicated request
3953
+ * Fetch with authentication and error handling
3738
3954
  */
3739
- async deduplicate(key, factory, logPrefix = "Request") {
3740
- this.periodicCleanup();
3741
- const existingRequest = this.pendingRequests.get(key);
3742
- if (existingRequest) {
3743
- console.log(`[${logPrefix}] Deduplicating request for key: ${key}`);
3744
- return existingRequest;
3955
+ async fetchWithAuth(endpoint, body) {
3956
+ const token = await getAuthToken();
3957
+ if (!token) {
3958
+ throw new Error("Authentication required");
3959
+ }
3960
+ const response = await fetch(endpoint, {
3961
+ method: "POST",
3962
+ headers: {
3963
+ "Authorization": `Bearer ${token}`,
3964
+ "Content-Type": "application/json"
3965
+ },
3966
+ body: JSON.stringify(body)
3967
+ });
3968
+ if (!response.ok) {
3969
+ const error = await response.json().catch(() => ({ error: "Request failed" }));
3970
+ throw new Error(error.error || `API error: ${response.status}`);
3971
+ }
3972
+ return response.json();
3973
+ }
3974
+ /**
3975
+ * Deduplicate requests to prevent multiple API calls
3976
+ */
3977
+ async deduplicate(key, factory) {
3978
+ if (this.requestCache.has(key)) {
3979
+ console.log(`[S3ClipsAPIClient] Deduplicating request: ${key}`);
3980
+ return this.requestCache.get(key);
3745
3981
  }
3746
- console.log(`[${logPrefix}] Creating new request for key: ${key}`);
3747
3982
  const promise = factory().finally(() => {
3748
- this.pendingRequests.delete(key);
3749
- console.log(`[${logPrefix}] Completed and cleaned up request: ${key}`);
3983
+ this.requestCache.delete(key);
3750
3984
  });
3751
- this.pendingRequests.set(key, promise);
3985
+ this.requestCache.set(key, promise);
3752
3986
  return promise;
3753
3987
  }
3754
3988
  /**
3755
- * Clear all pending requests (useful for cleanup)
3989
+ * List S3 clips
3756
3990
  */
3757
- clear() {
3758
- console.log(`[RequestCache] Clearing ${this.pendingRequests.size} pending requests`);
3759
- this.pendingRequests.clear();
3991
+ async listS3Clips(params) {
3992
+ const cacheKey = `list:${JSON.stringify(params)}`;
3993
+ return this.deduplicate(cacheKey, async () => {
3994
+ const response = await this.fetchWithAuth(this.baseUrl, {
3995
+ action: "list",
3996
+ workspaceId: params.workspaceId,
3997
+ date: params.date,
3998
+ shift: params.shiftId,
3999
+ maxKeys: params.maxKeys
4000
+ });
4001
+ return response.clips.map((clip) => clip.originalUri);
4002
+ });
3760
4003
  }
3761
4004
  /**
3762
- * Get current cache stats
4005
+ * Get clip counts
3763
4006
  */
3764
- getStats() {
3765
- return {
3766
- pendingCount: this.pendingRequests.size,
3767
- maxSize: this.maxCacheSize
4007
+ async getClipCounts(workspaceId, date, shiftId) {
4008
+ const cacheKey = `counts:${workspaceId}:${date}:${shiftId}`;
4009
+ return this.deduplicate(cacheKey, async () => {
4010
+ const response = await this.fetchWithAuth(this.baseUrl, {
4011
+ action: "count",
4012
+ workspaceId,
4013
+ date,
4014
+ shift: shiftId.toString()
4015
+ });
4016
+ return response.counts;
4017
+ });
4018
+ }
4019
+ /**
4020
+ * Get clip counts with index (for compatibility)
4021
+ */
4022
+ async getClipCountsWithIndex(workspaceId, date, shiftId) {
4023
+ const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
4024
+ const videoIndex = {
4025
+ byCategory: /* @__PURE__ */ new Map(),
4026
+ allVideos: [],
4027
+ counts,
4028
+ workspaceId,
4029
+ date,
4030
+ shiftId: shiftId.toString(),
4031
+ lastUpdated: /* @__PURE__ */ new Date()
3768
4032
  };
4033
+ return { counts, videoIndex };
3769
4034
  }
3770
4035
  /**
3771
- * Periodic cleanup to prevent memory leaks
4036
+ * Get metadata for a video
3772
4037
  */
3773
- periodicCleanup() {
3774
- const now2 = Date.now();
3775
- if (now2 - this.lastCleanup > this.cleanupInterval) {
3776
- if (this.pendingRequests.size > this.maxCacheSize) {
3777
- console.warn(`[RequestCache] Cache size exceeded ${this.maxCacheSize}, clearing oldest entries`);
3778
- const entries = Array.from(this.pendingRequests.entries());
3779
- this.pendingRequests.clear();
3780
- entries.slice(-Math.floor(this.maxCacheSize / 2)).forEach(([key, promise]) => {
3781
- this.pendingRequests.set(key, promise);
3782
- });
4038
+ async getMetadata(playlistUri) {
4039
+ const cacheKey = `metadata:${playlistUri}`;
4040
+ return this.deduplicate(cacheKey, async () => {
4041
+ const response = await this.fetchWithAuth(this.baseUrl, {
4042
+ action: "metadata",
4043
+ playlistUri
4044
+ });
4045
+ return response.metadata;
4046
+ });
4047
+ }
4048
+ /**
4049
+ * Get metadata cycle time
4050
+ */
4051
+ async getMetadataCycleTime(playlistUri) {
4052
+ const metadata = await this.getMetadata(playlistUri);
4053
+ return metadata?.cycle_time_seconds || null;
4054
+ }
4055
+ /**
4056
+ * Get first clip for category
4057
+ */
4058
+ async getFirstClipForCategory(workspaceId, date, shiftId, category) {
4059
+ const cacheKey = `first:${workspaceId}:${date}:${shiftId}:${category}`;
4060
+ return this.deduplicate(cacheKey, async () => {
4061
+ const response = await this.fetchWithAuth(this.baseUrl, {
4062
+ action: "first",
4063
+ workspaceId,
4064
+ date,
4065
+ shift: shiftId.toString(),
4066
+ category,
4067
+ sopCategories: this.sopCategories
4068
+ });
4069
+ return response.video;
4070
+ });
4071
+ }
4072
+ /**
4073
+ * Get clip by index
4074
+ */
4075
+ async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
4076
+ const cacheKey = `by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}`;
4077
+ return this.deduplicate(cacheKey, async () => {
4078
+ const response = await this.fetchWithAuth(this.baseUrl, {
4079
+ action: "by-index",
4080
+ workspaceId,
4081
+ date,
4082
+ shift: shiftId.toString(),
4083
+ category,
4084
+ index,
4085
+ sopCategories: this.sopCategories
4086
+ });
4087
+ const video = response.video;
4088
+ if (video && includeMetadata && video.originalUri) {
4089
+ try {
4090
+ const metadata = await this.getMetadata(video.originalUri);
4091
+ if (metadata) {
4092
+ video.cycle_time_seconds = metadata.cycle_time_seconds;
4093
+ video.creation_timestamp = metadata.creation_timestamp;
4094
+ }
4095
+ } catch (error) {
4096
+ console.warn("[S3ClipsAPIClient] Failed to fetch metadata:", error);
4097
+ }
3783
4098
  }
3784
- this.lastCleanup = now2;
3785
- }
4099
+ return video;
4100
+ });
4101
+ }
4102
+ /**
4103
+ * Get videos page with pagination
4104
+ */
4105
+ async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
4106
+ const cacheKey = `page:${workspaceId}:${date}:${shiftId}:${category}:${pageSize}:${startAfter || "first"}`;
4107
+ return this.deduplicate(cacheKey, async () => {
4108
+ const response = await this.fetchWithAuth(this.baseUrl, {
4109
+ action: "page",
4110
+ workspaceId,
4111
+ date,
4112
+ shift: shiftId.toString(),
4113
+ category,
4114
+ pageSize,
4115
+ startAfter,
4116
+ sopCategories: this.sopCategories
4117
+ });
4118
+ return {
4119
+ videos: response.videos,
4120
+ nextToken: response.nextToken,
4121
+ hasMore: response.hasMore
4122
+ };
4123
+ });
4124
+ }
4125
+ /**
4126
+ * Convert S3 URI to CloudFront URL
4127
+ * In the API client, URLs are already signed from the server
4128
+ */
4129
+ s3UriToCloudfront(s3Uri) {
4130
+ return s3Uri;
4131
+ }
4132
+ /**
4133
+ * Clean up resources
4134
+ */
4135
+ dispose() {
4136
+ this.requestCache.clear();
4137
+ }
4138
+ /**
4139
+ * Get service statistics
4140
+ */
4141
+ getStats() {
4142
+ return {
4143
+ requestCache: {
4144
+ pendingCount: this.requestCache.size,
4145
+ maxSize: 1e3
4146
+ }
4147
+ };
3786
4148
  }
3787
4149
  };
4150
+
4151
+ // src/lib/api/s3-clips-secure.ts
3788
4152
  var S3ClipsService = class {
3789
4153
  constructor(config) {
3790
- // Request deduplication cache
3791
- this.requestCache = new RequestDeduplicationCache();
3792
- // Flag to prevent metadata fetching during index building
4154
+ // Flags for compatibility
3793
4155
  this.isIndexBuilding = false;
3794
- // Flag to prevent metadata fetching during entire prefetch operation
3795
4156
  this.isPrefetching = false;
3796
- // Global safeguard: limit concurrent metadata fetches to prevent flooding
3797
4157
  this.currentMetadataFetches = 0;
3798
4158
  this.MAX_CONCURRENT_METADATA = 3;
3799
4159
  this.config = config;
3800
4160
  if (!config.s3Config) {
3801
4161
  throw new Error("S3 configuration is required");
3802
4162
  }
4163
+ const sopCategories = config.s3Config.sopCategories?.default;
4164
+ this.apiClient = new S3ClipsAPIClient(sopCategories);
3803
4165
  const processing = config.s3Config.processing || {};
3804
4166
  this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
3805
4167
  this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
3806
4168
  this.concurrencyLimit = processing.concurrencyLimit || 10;
3807
4169
  this.maxInitialFetch = processing.maxInitialFetch || 60;
3808
- const region = this.validateAndSanitizeRegion(config.s3Config.region);
3809
- console.log(`S3ClipsService: Using AWS region: ${region}`);
3810
- this.s3Client = new S3Client({
3811
- region,
3812
- credentials: config.s3Config.credentials ? {
3813
- accessKeyId: config.s3Config.credentials.accessKeyId,
3814
- secretAccessKey: config.s3Config.credentials.secretAccessKey
3815
- } : void 0
3816
- });
3817
- }
3818
- /**
3819
- * Validates and sanitizes the AWS region
3820
- */
3821
- validateAndSanitizeRegion(region) {
3822
- const defaultRegion = "us-east-1";
3823
- if (!region || typeof region !== "string") {
3824
- console.warn(`S3ClipsService: Invalid region provided (${region}), using default: ${defaultRegion}`);
3825
- return defaultRegion;
3826
- }
3827
- const sanitizedRegion = region.trim().toLowerCase();
3828
- if (!sanitizedRegion) {
3829
- console.warn(`S3ClipsService: Empty region provided, using default: ${defaultRegion}`);
3830
- return defaultRegion;
3831
- }
3832
- const regionPattern = /^[a-z]{2,3}-[a-z]+-\d+$/;
3833
- if (!regionPattern.test(sanitizedRegion)) {
3834
- console.warn(`S3ClipsService: Invalid region format (${sanitizedRegion}), using default: ${defaultRegion}`);
3835
- return defaultRegion;
3836
- }
3837
- return sanitizedRegion;
4170
+ console.log("[S3ClipsService] \u2705 Initialized with secure API client - AWS credentials are now server-side only!");
3838
4171
  }
3839
4172
  /**
3840
- * Lists S3 clips for a workspace, date, and shift with request deduplication
4173
+ * Lists S3 clips using API
3841
4174
  */
3842
4175
  async listS3Clips(params) {
3843
- const { workspaceId, date, shiftId, maxKeys } = params;
4176
+ const { workspaceId, date, shiftId } = params;
3844
4177
  if (!isValidShiftId(shiftId)) {
3845
- console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}. Must be 0 (day) or 1 (night)`);
4178
+ console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
3846
4179
  return [];
3847
4180
  }
3848
- const prefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
3849
- console.log(`[S3ClipsService] Listing clips for workspace: ${workspaceId}, date: ${date}, shift: ${shiftId}`);
3850
- const deduplicationKey = `list-s3-clips:${prefix}:${maxKeys || "all"}`;
3851
- return this.requestCache.deduplicate(
3852
- deduplicationKey,
3853
- () => this.executeListS3Clips(params),
3854
- "ListS3Clips"
3855
- );
3856
- }
3857
- /**
3858
- * Internal implementation of S3 listing (called through deduplication)
3859
- */
3860
- async executeListS3Clips(params) {
3861
- const { workspaceId, date, shiftId, maxKeys } = params;
3862
- const prefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
3863
- console.log(`[S3ClipsService] Executing S3 list for prefix: ${prefix}`);
4181
+ console.log(`[S3ClipsService] Listing clips via API for workspace: ${workspaceId}`);
3864
4182
  try {
3865
- let playlists = [];
3866
- let continuationToken = void 0;
3867
- do {
3868
- const command = new ListObjectsV2Command({
3869
- Bucket: this.config.s3Config.bucketName,
3870
- Prefix: prefix,
3871
- ContinuationToken: continuationToken,
3872
- MaxKeys: maxKeys ?? 1e3
3873
- });
3874
- const response = await this.s3Client.send(command);
3875
- console.log(`Got S3 response for ${prefix}:`, {
3876
- keyCount: response.KeyCount,
3877
- contentsLength: response.Contents?.length || 0,
3878
- hasContinuation: !!response.NextContinuationToken
3879
- });
3880
- if (response.Contents) {
3881
- if (response.Contents.length > 0) {
3882
- console.log("Sample Keys:", response.Contents.slice(0, 3).map((obj) => obj.Key));
3883
- }
3884
- for (const obj of response.Contents) {
3885
- if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
3886
- if (obj.Key.includes("missed_qchecks")) {
3887
- console.log(`Skipping missed_qchecks path: ${obj.Key}`);
3888
- continue;
3889
- }
3890
- playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
3891
- }
3892
- }
3893
- }
3894
- continuationToken = response.NextContinuationToken;
3895
- if (maxKeys && playlists.length >= maxKeys) {
3896
- break;
3897
- }
3898
- } while (continuationToken && (!maxKeys || playlists.length < maxKeys));
3899
- console.log(`Found ${playlists.length} HLS playlists in ${prefix}`);
3900
- if (playlists.length > 0) {
3901
- console.log("First playlist URI:", playlists[0]);
3902
- }
3903
- return playlists;
4183
+ return await this.apiClient.listS3Clips(params);
3904
4184
  } catch (error) {
3905
- console.error(`Error listing S3 objects with prefix '${prefix}':`, error);
4185
+ console.error("[S3ClipsService] Error listing clips:", error);
3906
4186
  return [];
3907
4187
  }
3908
4188
  }
3909
4189
  /**
3910
- * Fetches and extracts cycle time from metadata.json with deduplication
4190
+ * Get metadata cycle time
3911
4191
  */
3912
4192
  async getMetadataCycleTime(playlistUri) {
3913
- const deduplicationKey = `metadata-cycle-time:${playlistUri}`;
3914
- return this.requestCache.deduplicate(
3915
- deduplicationKey,
3916
- () => this.executeGetMetadataCycleTime(playlistUri),
3917
- "MetadataCycleTime"
3918
- );
3919
- }
3920
- /**
3921
- * Internal implementation of metadata cycle time fetching
3922
- */
3923
- async executeGetMetadataCycleTime(playlistUri) {
3924
4193
  try {
3925
- const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
3926
- const url = new URL(metadataUri);
3927
- const bucket = url.hostname;
3928
- const key = url.pathname.substring(1);
3929
- console.log(`[S3ClipsService] Fetching metadata cycle time for: ${key}`);
3930
- const command = new GetObjectCommand({
3931
- Bucket: bucket,
3932
- Key: key
3933
- });
3934
- const response = await this.s3Client.send(command);
3935
- if (!response.Body) {
3936
- console.warn(`Empty response body for metadata file: ${key}`);
3937
- return null;
3938
- }
3939
- const metadataContent = await response.Body.transformToString();
3940
- const metadata = JSON.parse(metadataContent);
3941
- const cycleTimeFrames = metadata?.original_task_metadata?.cycle_time;
3942
- if (typeof cycleTimeFrames === "number") {
3943
- return cycleTimeFrames;
3944
- }
3945
- return null;
4194
+ return await this.apiClient.getMetadataCycleTime(playlistUri);
3946
4195
  } catch (error) {
3947
- console.error(`Error fetching or parsing metadata:`, error);
4196
+ console.error("[S3ClipsService] Error fetching metadata cycle time:", error);
3948
4197
  return null;
3949
4198
  }
3950
4199
  }
3951
4200
  /**
3952
- * Control prefetch mode to prevent metadata fetching during background operations
4201
+ * Control prefetch mode
3953
4202
  */
3954
4203
  setPrefetchMode(enabled) {
3955
4204
  this.isPrefetching = enabled;
3956
- console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"} - metadata fetching ${enabled ? "blocked" : "allowed"}`);
4205
+ console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}`);
3957
4206
  }
3958
4207
  /**
3959
- * Fetches full metadata including timestamps with deduplication
4208
+ * Get full metadata
3960
4209
  */
3961
4210
  async getFullMetadata(playlistUri) {
3962
4211
  if (this.isIndexBuilding || this.isPrefetching) {
3963
- console.warn(`[S3ClipsService] Skipping metadata fetch - building: ${this.isIndexBuilding}, prefetching: ${this.isPrefetching}`);
4212
+ console.warn("[S3ClipsService] Skipping metadata - operation in progress");
3964
4213
  return null;
3965
4214
  }
3966
4215
  if (this.currentMetadataFetches >= this.MAX_CONCURRENT_METADATA) {
3967
- console.warn(`[S3ClipsService] Skipping metadata - max concurrent fetches (${this.MAX_CONCURRENT_METADATA}) reached`);
4216
+ console.warn("[S3ClipsService] Skipping metadata - max concurrent fetches reached");
3968
4217
  return null;
3969
4218
  }
3970
4219
  this.currentMetadataFetches++;
3971
4220
  try {
3972
- const deduplicationKey = `full-metadata:${playlistUri}`;
3973
- return await this.requestCache.deduplicate(
3974
- deduplicationKey,
3975
- () => this.executeGetFullMetadata(playlistUri),
3976
- "FullMetadata"
3977
- );
3978
- } finally {
3979
- this.currentMetadataFetches--;
3980
- }
3981
- }
3982
- /**
3983
- * Internal implementation of full metadata fetching
3984
- */
3985
- async executeGetFullMetadata(playlistUri) {
3986
- try {
3987
- const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
3988
- const url = new URL(metadataUri);
3989
- const bucket = url.hostname;
3990
- const key = url.pathname.substring(1);
3991
- console.log(`[S3ClipsService] Fetching full metadata for: ${key}`);
3992
- const command = new GetObjectCommand({
3993
- Bucket: bucket,
3994
- Key: key
3995
- });
3996
- const response = await this.s3Client.send(command);
3997
- if (!response.Body) {
3998
- console.warn(`Empty response body for metadata file: ${key}`);
3999
- return null;
4000
- }
4001
- const metadataContent = await response.Body.transformToString();
4002
- const metadata = JSON.parse(metadataContent);
4003
- return metadata;
4221
+ return await this.apiClient.getMetadata(playlistUri);
4004
4222
  } catch (error) {
4005
- console.error(`Error fetching or parsing metadata:`, error);
4223
+ console.error("[S3ClipsService] Error fetching metadata:", error);
4006
4224
  return null;
4225
+ } finally {
4226
+ this.currentMetadataFetches--;
4007
4227
  }
4008
4228
  }
4009
4229
  /**
4010
- * Converts S3 URI to CloudFront URL
4230
+ * Convert S3 URI to CloudFront URL
4231
+ * URLs from API are already signed
4011
4232
  */
4012
4233
  s3UriToCloudfront(s3Uri) {
4013
- const url = new URL(s3Uri);
4014
- const key = url.pathname.startsWith("/") ? url.pathname.substring(1) : url.pathname;
4015
- const cloudfrontUrl = `${this.config.s3Config.cloudFrontDomain}/${key}`;
4016
- return cloudfrontUrl;
4234
+ if (s3Uri.startsWith("http")) {
4235
+ return s3Uri;
4236
+ }
4237
+ console.warn("[S3ClipsService] Unexpected S3 URI in secure mode:", s3Uri);
4238
+ return s3Uri;
4017
4239
  }
4018
4240
  /**
4019
- * Gets SOP categories for a specific workspace
4241
+ * Get SOP categories for workspace
4020
4242
  */
4021
4243
  getSOPCategories(workspaceId) {
4022
4244
  const sopConfig = this.config.s3Config?.sopCategories;
@@ -4028,299 +4250,85 @@ var S3ClipsService = class {
4028
4250
  }
4029
4251
  async getClipCounts(workspaceId, date, shiftId, buildIndex) {
4030
4252
  if (!isValidShiftId(shiftId)) {
4031
- console.error(`[S3ClipsService] getClipCounts - Invalid shift ID: ${shiftId}. Must be 0 (day) or 1 (night)`);
4032
- return buildIndex ? { counts: {}, videoIndex: { byCategory: /* @__PURE__ */ new Map(), allVideos: [], counts: {}, workspaceId, date, shiftId: "0", lastUpdated: /* @__PURE__ */ new Date() } } : {};
4033
- }
4034
- const deduplicationKey = `clip-counts:${workspaceId}:${date}:${shiftId}:${buildIndex ? "with-index" : "counts-only"}`;
4035
- return this.requestCache.deduplicate(
4036
- deduplicationKey,
4037
- () => this.executeGetClipCounts(workspaceId, date, shiftId, buildIndex),
4038
- "ClipCounts"
4039
- );
4040
- }
4041
- /**
4042
- * Internal implementation of clip counts fetching
4043
- */
4044
- async executeGetClipCounts(workspaceId, date, shiftId, buildIndex) {
4045
- const effectiveBuildIndex = false;
4253
+ console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
4254
+ return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
4255
+ }
4046
4256
  try {
4047
- const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
4048
- const counts = { total: 0 };
4049
- const categoryFolders = [
4050
- "idle_time",
4051
- "low_value",
4052
- "sop_deviation",
4053
- "missing_quality_check",
4054
- "best_cycle_time",
4055
- "worst_cycle_time",
4056
- "long_cycle_time",
4057
- "cycle_completion",
4058
- "bottleneck"
4059
- ];
4060
- const shiftName = shiftId === 0 || shiftId === "0" ? "Day" : "Night";
4061
- console.log(`[S3ClipsService] Fast counting clips for ${workspaceId} on ${date}, shift ${shiftId} (${shiftName} Shift)`);
4062
- const startTime = performance.now();
4063
- const videoIndex = effectiveBuildIndex ? {
4064
- byCategory: /* @__PURE__ */ new Map(),
4065
- allVideos: [],
4066
- counts: {},
4067
- workspaceId,
4068
- date,
4069
- shiftId: shiftId.toString(),
4070
- lastUpdated: /* @__PURE__ */ new Date(),
4071
- _debugId: `vid_${Date.now()}_${Math.random().toString(36).substring(7)}`
4072
- } : null;
4073
- const countPromises = categoryFolders.map(async (category) => {
4074
- const categoryPrefix = `${basePrefix}${category}/videos/`;
4075
- const categoryVideos = [];
4076
- try {
4077
- if (effectiveBuildIndex) ; else {
4078
- const command = new ListObjectsV2Command({
4079
- Bucket: this.config.s3Config.bucketName,
4080
- Prefix: categoryPrefix,
4081
- Delimiter: "/",
4082
- MaxKeys: 1e3
4083
- });
4084
- let folderCount = 0;
4085
- let continuationToken;
4086
- do {
4087
- if (continuationToken) {
4088
- command.input.ContinuationToken = continuationToken;
4089
- }
4090
- const response = await this.s3Client.send(command);
4091
- if (response.CommonPrefixes) {
4092
- folderCount += response.CommonPrefixes.length;
4093
- }
4094
- continuationToken = response.NextContinuationToken;
4095
- } while (continuationToken);
4096
- return { category, count: folderCount, videos: [] };
4097
- }
4098
- } catch (error) {
4099
- console.error(`Error ${buildIndex ? "building index for" : "counting folders for"} category ${category}:`, error);
4100
- return { category, count: 0, videos: [] };
4101
- }
4102
- });
4103
- const results = await Promise.all(countPromises);
4104
- for (const { category, count, videos } of results) {
4105
- counts[category] = count;
4106
- counts.total += count;
4107
- if (effectiveBuildIndex && videoIndex && videos) ;
4108
- }
4109
- if (effectiveBuildIndex && videoIndex) ;
4110
- const elapsed = performance.now() - startTime;
4111
- console.log(`[S3ClipsService] Clip counts completed in ${elapsed.toFixed(2)}ms - Total: ${counts.total}`);
4112
- if (effectiveBuildIndex && videoIndex) ;
4113
- return counts;
4114
- } finally {
4257
+ if (buildIndex) {
4258
+ const result = await this.apiClient.getClipCountsWithIndex(workspaceId, date, shiftId);
4259
+ const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
4260
+ await smartVideoCache.setClipCounts(cacheKey, result);
4261
+ return result;
4262
+ } else {
4263
+ const counts = await this.apiClient.getClipCounts(workspaceId, date, shiftId);
4264
+ return counts;
4265
+ }
4266
+ } catch (error) {
4267
+ console.error("[S3ClipsService] Error fetching clip counts:", error);
4268
+ return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
4115
4269
  }
4116
4270
  }
4117
4271
  async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex) {
4118
4272
  const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
4119
4273
  const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
4120
4274
  if (cachedResult) {
4121
- console.log(`[S3ClipsService] Using cached clip counts for ${workspaceId}`);
4122
- if (buildIndex) {
4123
- return cachedResult;
4124
- } else {
4125
- return cachedResult.counts;
4126
- }
4275
+ console.log("[S3ClipsService] Using cached clip counts");
4276
+ return buildIndex ? cachedResult : cachedResult.counts;
4127
4277
  }
4128
- console.log(`[S3ClipsService] Cache miss - fetching clip counts for ${workspaceId}`);
4278
+ console.log("[S3ClipsService] Cache miss - fetching from API");
4129
4279
  return buildIndex ? this.getClipCounts(workspaceId, date, shiftId, true) : this.getClipCounts(workspaceId, date, shiftId);
4130
4280
  }
4131
4281
  /**
4132
- * Get first clip for a specific category with deduplication
4282
+ * Get first clip for category
4133
4283
  */
4134
4284
  async getFirstClipForCategory(workspaceId, date, shiftId, category) {
4135
4285
  if (!isValidShiftId(shiftId)) {
4136
- console.error(`[S3ClipsService] getFirstClipForCategory - Invalid shift ID: ${shiftId}. Must be 0 (day) or 1 (night)`);
4286
+ console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
4137
4287
  return null;
4138
4288
  }
4139
- const deduplicationKey = `first-clip:${workspaceId}:${date}:${shiftId}:${category}`;
4140
- return this.requestCache.deduplicate(
4141
- deduplicationKey,
4142
- () => this.executeGetFirstClipForCategory(workspaceId, date, shiftId, category),
4143
- "FirstClip"
4144
- );
4145
- }
4146
- /**
4147
- * Internal implementation of first clip fetching
4148
- */
4149
- async executeGetFirstClipForCategory(workspaceId, date, shiftId, category) {
4150
- const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
4151
4289
  try {
4152
- const command = new ListObjectsV2Command({
4153
- Bucket: this.config.s3Config.bucketName,
4154
- Prefix: categoryPrefix,
4155
- MaxKeys: 10
4156
- // Small limit since we only need one
4157
- });
4158
- const response = await this.s3Client.send(command);
4159
- if (response.Contents) {
4160
- const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
4161
- if (playlistObj && playlistObj.Key) {
4162
- const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
4163
- const sopCategories = this.getSOPCategories(workspaceId);
4164
- const parsedInfo = parseS3Uri(s3Uri, sopCategories);
4165
- if (parsedInfo) {
4166
- const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
4167
- return {
4168
- id: `${workspaceId}-${date}-${shiftId}-${category}-first`,
4169
- src: cloudfrontUrl,
4170
- ...parsedInfo,
4171
- originalUri: s3Uri
4172
- };
4173
- }
4174
- }
4175
- }
4290
+ return await this.apiClient.getFirstClipForCategory(workspaceId, date, shiftId, category);
4176
4291
  } catch (error) {
4177
- console.error(`Error getting first clip for category ${category}:`, error);
4292
+ console.error("[S3ClipsService] Error fetching first clip:", error);
4293
+ return null;
4178
4294
  }
4179
- return null;
4180
4295
  }
4181
4296
  /**
4182
- * Get a specific video from the pre-built video index - O(1) lookup performance
4297
+ * Get video from index (for compatibility)
4183
4298
  */
4184
4299
  async getVideoFromIndex(videoIndex, category, index, includeCycleTime = true, includeMetadata = true) {
4300
+ const { workspaceId, date, shiftId } = videoIndex;
4301
+ return this.getClipByIndex(
4302
+ workspaceId,
4303
+ date,
4304
+ shiftId,
4305
+ category,
4306
+ index,
4307
+ includeCycleTime,
4308
+ includeMetadata
4309
+ );
4310
+ }
4311
+ /**
4312
+ * Get clip by index
4313
+ */
4314
+ async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
4185
4315
  try {
4186
- const categoryVideos = videoIndex.byCategory.get(category);
4187
- if (!categoryVideos || index < 0 || index >= categoryVideos.length) {
4188
- console.warn(`Invalid index ${index} for category '${category}'. Available: ${categoryVideos?.length || 0}`);
4189
- console.warn(`Video index ID: ${videoIndex._debugId || "NO_ID"}`);
4190
- console.warn(`Video index categories available: [${Array.from(videoIndex.byCategory.keys()).join(", ")}]`);
4191
- console.warn(`Video index total videos: ${videoIndex.allVideos.length}`);
4192
- return null;
4193
- }
4194
- const videoEntry = categoryVideos[index];
4195
- return this.processFullVideo(
4196
- videoEntry.uri,
4316
+ return await this.apiClient.getClipByIndex(
4317
+ workspaceId,
4318
+ date,
4319
+ shiftId,
4320
+ category,
4197
4321
  index,
4198
- videoEntry.workspaceId,
4199
- videoEntry.date,
4200
- videoEntry.shiftId,
4201
4322
  includeCycleTime,
4202
4323
  includeMetadata
4203
4324
  );
4204
4325
  } catch (error) {
4205
- console.error(`Error getting video from index at ${index} for category ${category}:`, error);
4326
+ console.error("[S3ClipsService] Error fetching clip by index:", error);
4206
4327
  return null;
4207
4328
  }
4208
4329
  }
4209
4330
  /**
4210
- * Get a specific clip by index for a category with deduplication
4211
- * @deprecated Use getVideoFromIndex with a pre-built VideoIndex for better performance
4212
- */
4213
- async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
4214
- const deduplicationKey = `clip-by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}:${includeCycleTime}:${includeMetadata}`;
4215
- return this.requestCache.deduplicate(
4216
- deduplicationKey,
4217
- () => this.executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime, includeMetadata),
4218
- "ClipByIndex"
4219
- );
4220
- }
4221
- /**
4222
- * Internal implementation of clip by index fetching
4223
- */
4224
- async executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
4225
- const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
4226
- try {
4227
- const command = new ListObjectsV2Command({
4228
- Bucket: this.config.s3Config.bucketName,
4229
- Prefix: categoryPrefix,
4230
- MaxKeys: 1e3
4231
- });
4232
- let playlists = [];
4233
- let continuationToken = void 0;
4234
- do {
4235
- if (continuationToken) {
4236
- command.input.ContinuationToken = continuationToken;
4237
- }
4238
- const response = await this.s3Client.send(command);
4239
- if (response.Contents) {
4240
- for (const obj of response.Contents) {
4241
- if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
4242
- playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
4243
- }
4244
- }
4245
- }
4246
- continuationToken = response.NextContinuationToken;
4247
- } while (continuationToken && playlists.length < index + 10);
4248
- playlists.sort((a, b) => {
4249
- const parsedA = parseS3Uri(a);
4250
- const parsedB = parseS3Uri(b);
4251
- if (!parsedA || !parsedB) return 0;
4252
- return parsedB.timestamp.localeCompare(parsedA.timestamp);
4253
- });
4254
- if (index < playlists.length) {
4255
- const uri = playlists[index];
4256
- return this.processFullVideo(
4257
- uri,
4258
- index,
4259
- workspaceId,
4260
- date,
4261
- shiftId.toString(),
4262
- includeCycleTime,
4263
- includeMetadata
4264
- );
4265
- }
4266
- } catch (error) {
4267
- console.error(`[getClipByIndex] Error getting clip at index ${index} for category ${category}:`, error);
4268
- }
4269
- return null;
4270
- }
4271
- /**
4272
- * Get one sample video from each category for preloading
4273
- */
4274
- async getSampleVideos(workspaceId, date, shiftId) {
4275
- const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
4276
- const samples = {};
4277
- const sopCategories = this.getSOPCategories(workspaceId);
4278
- const categoriesToSample = sopCategories ? sopCategories.map((cat) => cat.id) : ["low_value", "best_cycle_time", "worst_cycle_time", "long_cycle_time", "cycle_completion"];
4279
- console.log(`[S3ClipsService] Getting sample videos for categories:`, categoriesToSample);
4280
- const samplePromises = categoriesToSample.map(async (category) => {
4281
- const categoryPrefix = `${basePrefix}${category}/`;
4282
- try {
4283
- const command = new ListObjectsV2Command({
4284
- Bucket: this.config.s3Config.bucketName,
4285
- Prefix: categoryPrefix,
4286
- MaxKeys: 20
4287
- // Small limit since we only need one
4288
- });
4289
- const response = await this.s3Client.send(command);
4290
- if (response.Contents) {
4291
- const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
4292
- if (playlistObj && playlistObj.Key) {
4293
- const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
4294
- const parsedInfo = parseS3Uri(s3Uri, sopCategories);
4295
- if (parsedInfo) {
4296
- return {
4297
- category,
4298
- sample: {
4299
- id: `${workspaceId}-${date}-${shiftId}-${category}-sample`,
4300
- src: this.s3UriToCloudfront(s3Uri),
4301
- // Pre-convert to CloudFront URL
4302
- ...parsedInfo,
4303
- originalUri: s3Uri
4304
- }
4305
- };
4306
- }
4307
- }
4308
- }
4309
- } catch (error) {
4310
- console.error(`Error getting sample for category ${category}:`, error);
4311
- }
4312
- return { category, sample: null };
4313
- });
4314
- const sampleResults = await Promise.all(samplePromises);
4315
- for (const { category, sample } of sampleResults) {
4316
- if (sample) {
4317
- samples[category] = sample;
4318
- }
4319
- }
4320
- return samples;
4321
- }
4322
- /**
4323
- * Processes a single video completely
4331
+ * Process full video (for compatibility)
4324
4332
  */
4325
4333
  async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime, includeMetadata = false) {
4326
4334
  const sopCategories = this.getSOPCategories(workspaceId);
@@ -4329,39 +4337,43 @@ var S3ClipsService = class {
4329
4337
  console.warn(`Skipping URI due to parsing failure: ${uri}`);
4330
4338
  return null;
4331
4339
  }
4332
- let cycleTimeSeconds = null;
4333
- let creationTimestamp = void 0;
4340
+ const video = {
4341
+ id: `${workspaceId}-${date}-${shiftId}-${index}`,
4342
+ src: uri,
4343
+ // Already signed from API
4344
+ ...parsedInfo,
4345
+ originalUri: uri
4346
+ };
4334
4347
  if (includeMetadata) {
4335
4348
  const metadata = await this.getFullMetadata(uri);
4336
4349
  if (metadata) {
4337
- if (metadata.original_task_metadata?.cycle_time) {
4338
- cycleTimeSeconds = metadata.original_task_metadata.cycle_time;
4339
- }
4340
- creationTimestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp || metadata[""];
4350
+ video.cycle_time_seconds = metadata.original_task_metadata?.cycle_time;
4351
+ video.creation_timestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp;
4341
4352
  }
4342
- } else if (includeCycleTime && (parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time") || parsedInfo.type === "best_cycle_time" || parsedInfo.type === "worst_cycle_time" || parsedInfo.type === "cycle_completion")) {
4343
- cycleTimeSeconds = null;
4344
4353
  }
4345
- const cloudfrontPlaylistUrl = this.s3UriToCloudfront(uri);
4346
- const { type: videoType, timestamp: videoTimestamp } = parsedInfo;
4347
- return {
4348
- id: `${workspaceId}-${date}-${shiftId}-${videoType}-${videoTimestamp.replace(/:/g, "")}-${index}`,
4349
- src: cloudfrontPlaylistUrl,
4350
- // Direct CloudFront playlist URL
4351
- ...parsedInfo,
4352
- cycle_time_seconds: cycleTimeSeconds || void 0,
4353
- creation_timestamp: creationTimestamp
4354
- };
4354
+ return video;
4355
4355
  }
4356
4356
  /**
4357
- * Simplified method to fetch clips based on parameters
4357
+ * Fetch clips (main method for compatibility)
4358
4358
  */
4359
4359
  async fetchClips(params) {
4360
- const { workspaceId, date: inputDate, shift, category, limit, offset = 0, mode, includeCycleTime, includeMetadata, timestampStart, timestampEnd } = params;
4360
+ const {
4361
+ workspaceId,
4362
+ date: inputDate,
4363
+ shift,
4364
+ category,
4365
+ limit,
4366
+ offset = 0,
4367
+ mode
4368
+ } = params;
4361
4369
  if (!workspaceId) {
4362
4370
  throw new Error("Valid Workspace ID is required");
4363
4371
  }
4364
- const date = inputDate || getOperationalDate2(this.config.dateTimeConfig?.defaultTimezone);
4372
+ const date = inputDate || getOperationalDate(
4373
+ this.config.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
4374
+ /* @__PURE__ */ new Date(),
4375
+ this.config.shiftConfig?.dayShift?.startTime || "06:00"
4376
+ );
4365
4377
  if (!isValidDateFormat(date)) {
4366
4378
  throw new Error("Invalid date format. Use YYYY-MM-DD.");
4367
4379
  }
@@ -4372,160 +4384,72 @@ var S3ClipsService = class {
4372
4384
  }
4373
4385
  shiftId = parseInt(shift, 10);
4374
4386
  } else {
4375
- const { shiftId: currentShiftId } = getCurrentShift2(this.config.dateTimeConfig?.defaultTimezone);
4387
+ const { shiftId: currentShiftId } = getCurrentShift(
4388
+ this.config.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
4389
+ this.config.shiftConfig
4390
+ );
4376
4391
  shiftId = currentShiftId;
4377
4392
  }
4378
- console.log(`S3ClipsService: Fetching clips for workspace ${workspaceId} on date ${date}, shift ${shiftId}`);
4393
+ console.log(`[S3ClipsService] Fetching clips for workspace ${workspaceId}`);
4379
4394
  if (mode === "summary") {
4380
4395
  const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
4381
4396
  return { counts, samples: {} };
4382
4397
  }
4383
4398
  const effectiveLimit = limit || this.defaultLimitPerCategory;
4384
- const s3Uris = await this.listS3Clips({
4399
+ const result = await this.getVideosPage(
4385
4400
  workspaceId,
4386
4401
  date,
4387
4402
  shiftId,
4388
- maxKeys: category ? effectiveLimit * 2 : void 0
4389
- // Fetch extra for filtering
4390
- });
4391
- if (s3Uris.length === 0) {
4392
- console.log(`S3ClipsService: No HLS playlists found`);
4393
- return [];
4394
- }
4395
- let filteredUris = s3Uris;
4396
- if (category) {
4397
- filteredUris = s3Uris.filter((uri) => {
4398
- const parsedInfo = parseS3Uri(uri);
4399
- if (!parsedInfo) return false;
4400
- if (category === "long_cycle_time") {
4401
- return parsedInfo.type === "long_cycle_time" || parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time");
4402
- }
4403
- return parsedInfo.type === category;
4404
- });
4405
- filteredUris = filteredUris.slice(offset, offset + effectiveLimit);
4406
- }
4407
- const videoPromises = filteredUris.map(async (uri, index) => {
4408
- return this.processFullVideo(
4409
- uri,
4410
- index,
4403
+ category || "all",
4404
+ effectiveLimit
4405
+ );
4406
+ return result.videos;
4407
+ }
4408
+ /**
4409
+ * Get videos page using pagination API
4410
+ */
4411
+ async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
4412
+ try {
4413
+ return await this.apiClient.getVideosPage(
4411
4414
  workspaceId,
4412
4415
  date,
4413
- shiftId.toString(),
4414
- includeCycleTime || false,
4415
- includeMetadata || false
4416
- // Never fetch metadata for timestamp filtering to prevent flooding
4416
+ shiftId,
4417
+ category,
4418
+ pageSize,
4419
+ startAfter
4417
4420
  );
4418
- });
4419
- const videoResults = await Promise.all(videoPromises);
4420
- let videos = videoResults.filter((v) => v !== null);
4421
- if (timestampStart || timestampEnd) {
4422
- videos = videos.filter((video) => {
4423
- if (!video.creation_timestamp) return false;
4424
- const videoTimestamp = new Date(video.creation_timestamp).getTime();
4425
- if (timestampStart && timestampEnd) {
4426
- const start = new Date(timestampStart).getTime();
4427
- const end = new Date(timestampEnd).getTime();
4428
- return videoTimestamp >= start && videoTimestamp <= end;
4429
- } else if (timestampStart) {
4430
- const start = new Date(timestampStart).getTime();
4431
- return videoTimestamp >= start;
4432
- } else if (timestampEnd) {
4433
- const end = new Date(timestampEnd).getTime();
4434
- return videoTimestamp <= end;
4435
- }
4436
- return true;
4437
- });
4438
- }
4439
- videos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
4440
- const cacheKey = `videos:${workspaceId}:${date}:${shiftId}:${category || "all"}`;
4441
- if (videos.length > 0) {
4442
- smartVideoCache.setVideos(cacheKey, videos);
4421
+ } catch (error) {
4422
+ console.error("[S3ClipsService] Error fetching videos page:", error);
4423
+ return { videos: [], hasMore: false };
4443
4424
  }
4444
- return videos;
4445
4425
  }
4446
4426
  /**
4447
- * Get a page of videos for a specific category using efficient pagination
4448
- * This method replaces the need for full video indexing
4449
- * @param workspaceId - Workspace ID
4450
- * @param date - Date in YYYY-MM-DD format
4451
- * @param shiftId - Shift ID (0 for day, 1 for night)
4452
- * @param category - Category to fetch videos from
4453
- * @param pageSize - Number of videos to fetch per page (default 5)
4454
- * @param startAfter - Optional key to start after for pagination
4455
- * @returns Page of videos with continuation token
4427
+ * Create empty video index for compatibility
4456
4428
  */
4457
- async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
4458
- if (!isValidShiftId(shiftId)) {
4459
- console.error(`[S3ClipsService] getVideosPage - Invalid shift ID: ${shiftId}`);
4460
- return { videos: [], hasMore: false };
4461
- }
4462
- const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
4463
- const deduplicationKey = `videos-page:${categoryPrefix}:${pageSize}:${startAfter || "first"}`;
4464
- return this.requestCache.deduplicate(
4465
- deduplicationKey,
4466
- async () => {
4467
- try {
4468
- console.log(`[S3ClipsService] Fetching page of ${pageSize} videos for category '${category}'`);
4469
- const command = new ListObjectsV2Command({
4470
- Bucket: this.config.s3Config.bucketName,
4471
- Prefix: categoryPrefix,
4472
- MaxKeys: pageSize * 10,
4473
- // Fetch extra to account for non-playlist files
4474
- StartAfter: startAfter
4475
- });
4476
- const response = await this.s3Client.send(command);
4477
- const videos = [];
4478
- if (response.Contents) {
4479
- const sopCategories = this.getSOPCategories(workspaceId);
4480
- for (const obj of response.Contents) {
4481
- if (videos.length >= pageSize) break;
4482
- if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
4483
- if (obj.Key.includes("missed_qchecks")) continue;
4484
- const s3Uri = `s3://${this.config.s3Config.bucketName}/${obj.Key}`;
4485
- const parsedInfo = parseS3Uri(s3Uri, sopCategories);
4486
- if (parsedInfo) {
4487
- const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
4488
- videos.push({
4489
- id: `${workspaceId}-${date}-${shiftId}-${category}-${videos.length}`,
4490
- src: cloudfrontUrl,
4491
- ...parsedInfo,
4492
- originalUri: s3Uri
4493
- });
4494
- }
4495
- }
4496
- }
4497
- const lastKey = response.Contents[response.Contents.length - 1]?.Key;
4498
- const hasMore = !!response.IsTruncated || response.Contents.length === pageSize * 10;
4499
- console.log(`[S3ClipsService] Fetched ${videos.length} videos, hasMore: ${hasMore}`);
4500
- return {
4501
- videos,
4502
- nextToken: hasMore ? lastKey : void 0,
4503
- hasMore
4504
- };
4505
- }
4506
- return { videos: [], hasMore: false };
4507
- } catch (error) {
4508
- console.error(`[S3ClipsService] Error fetching videos page:`, error);
4509
- return { videos: [], hasMore: false };
4510
- }
4511
- },
4512
- "VideosPage"
4513
- );
4429
+ createEmptyVideoIndex(workspaceId, date, shiftId) {
4430
+ return {
4431
+ byCategory: /* @__PURE__ */ new Map(),
4432
+ allVideos: [],
4433
+ counts: {},
4434
+ workspaceId,
4435
+ date,
4436
+ shiftId,
4437
+ lastUpdated: /* @__PURE__ */ new Date(),
4438
+ _debugId: `empty_${Date.now()}`
4439
+ };
4514
4440
  }
4515
4441
  /**
4516
- * Cleanup method for proper resource management
4442
+ * Cleanup
4517
4443
  */
4518
4444
  dispose() {
4519
- console.log("[S3ClipsService] Disposing service and clearing request cache");
4520
- this.requestCache.clear();
4445
+ console.log("[S3ClipsService] Disposing service");
4446
+ this.apiClient.dispose();
4521
4447
  }
4522
4448
  /**
4523
- * Get service statistics for monitoring
4449
+ * Get statistics
4524
4450
  */
4525
4451
  getStats() {
4526
- return {
4527
- requestCache: this.requestCache.getStats()
4528
- };
4452
+ return this.apiClient.getStats();
4529
4453
  }
4530
4454
  };
4531
4455
 
@@ -8103,7 +8027,7 @@ var useActiveBreaks = (lineIds) => {
8103
8027
  }
8104
8028
  return { elapsedMinutes, remainingMinutes };
8105
8029
  };
8106
- const getCurrentShift3 = (currentMinutes, dayStart, nightStart) => {
8030
+ const getCurrentShift2 = (currentMinutes, dayStart, nightStart) => {
8107
8031
  const dayStartMinutes = parseTimeToMinutes2(dayStart);
8108
8032
  const nightStartMinutes = parseTimeToMinutes2(nightStart);
8109
8033
  if (nightStartMinutes < dayStartMinutes) {
@@ -8135,7 +8059,7 @@ var useActiveBreaks = (lineIds) => {
8135
8059
  const dayShift = dayShifts?.find((s) => s.line_id === lineId);
8136
8060
  const nightShift = nightShifts?.find((s) => s.line_id === lineId);
8137
8061
  if (!dayShift || !nightShift) continue;
8138
- const currentShift = getCurrentShift3(
8062
+ const currentShift = getCurrentShift2(
8139
8063
  currentMinutes,
8140
8064
  dayShift.start_time || "06:00",
8141
8065
  nightShift.start_time || "18:00"
@@ -11632,9 +11556,12 @@ var usePrefetchClipCounts = ({
11632
11556
  shiftStr = "0";
11633
11557
  console.log(`[usePrefetchClipCounts] No shift provided for historical date ${date}, defaulting to day shift (0)`);
11634
11558
  } else {
11635
- const currentShift = getCurrentShift2();
11559
+ const currentShift = getCurrentShift(
11560
+ dashboardConfig.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
11561
+ dashboardConfig.shiftConfig
11562
+ );
11636
11563
  shiftStr = currentShift.shiftId.toString();
11637
- console.log(`[usePrefetchClipCounts] Using current operational shift: ${shiftStr} (${currentShift.shiftName})`);
11564
+ console.log(`[usePrefetchClipCounts] Using current operational shift: ${shiftStr}`);
11638
11565
  }
11639
11566
  return {
11640
11567
  workspaceId: workspaceId || "",
@@ -12114,6 +12041,140 @@ function useWorkspaceNavigation() {
12114
12041
  getWorkspaceNavigationParams: getWorkspaceNavigationParams2
12115
12042
  };
12116
12043
  }
12044
+ function useWorkspaceHealth(options = {}) {
12045
+ const [workspaces, setWorkspaces] = useState([]);
12046
+ const [summary, setSummary] = useState(null);
12047
+ const [loading, setLoading] = useState(true);
12048
+ const [error, setError] = useState(null);
12049
+ const unsubscribeRef = useRef(null);
12050
+ const refreshIntervalRef = useRef(null);
12051
+ const {
12052
+ enableRealtime = true,
12053
+ refreshInterval = 3e4,
12054
+ // 30 seconds default
12055
+ ...filterOptions
12056
+ } = options;
12057
+ const fetchData = useCallback(async () => {
12058
+ try {
12059
+ setError(null);
12060
+ workspaceHealthService.clearCache();
12061
+ const [workspacesData, summaryData] = await Promise.all([
12062
+ workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
12063
+ workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
12064
+ ]);
12065
+ setWorkspaces(workspacesData);
12066
+ setSummary(summaryData);
12067
+ } catch (err) {
12068
+ console.error("Error fetching workspace health:", err);
12069
+ setError(err);
12070
+ } finally {
12071
+ setLoading(false);
12072
+ }
12073
+ }, [filterOptions.lineId, filterOptions.companyId, filterOptions.status, filterOptions.searchTerm, filterOptions.sortBy, filterOptions.sortOrder]);
12074
+ const handleRealtimeUpdate = useCallback(async (data) => {
12075
+ try {
12076
+ const [workspacesData, summaryData] = await Promise.all([
12077
+ workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
12078
+ workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
12079
+ ]);
12080
+ setWorkspaces(workspacesData);
12081
+ setSummary(summaryData);
12082
+ } catch (err) {
12083
+ console.error("Error updating real-time health data:", err);
12084
+ }
12085
+ }, [filterOptions]);
12086
+ useEffect(() => {
12087
+ fetchData();
12088
+ if (refreshInterval > 0) {
12089
+ refreshIntervalRef.current = setInterval(fetchData, 1e4);
12090
+ }
12091
+ if (enableRealtime) {
12092
+ unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
12093
+ handleRealtimeUpdate,
12094
+ { lineId: filterOptions.lineId, companyId: filterOptions.companyId }
12095
+ );
12096
+ }
12097
+ return () => {
12098
+ if (refreshIntervalRef.current) {
12099
+ clearInterval(refreshIntervalRef.current);
12100
+ }
12101
+ if (unsubscribeRef.current) {
12102
+ unsubscribeRef.current();
12103
+ }
12104
+ };
12105
+ }, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, filterOptions.lineId, filterOptions.companyId]);
12106
+ return {
12107
+ workspaces,
12108
+ summary,
12109
+ loading,
12110
+ error,
12111
+ refetch: fetchData
12112
+ };
12113
+ }
12114
+ function useWorkspaceHealthById(workspaceId, options = {}) {
12115
+ const [workspace, setWorkspace] = useState(null);
12116
+ const [metrics2, setMetrics] = useState(null);
12117
+ const [loading, setLoading] = useState(true);
12118
+ const [error, setError] = useState(null);
12119
+ const unsubscribeRef = useRef(null);
12120
+ const refreshIntervalRef = useRef(null);
12121
+ const { enableRealtime = true, refreshInterval = 3e4 } = options;
12122
+ const fetchData = useCallback(async () => {
12123
+ if (!workspaceId) {
12124
+ setWorkspace(null);
12125
+ setMetrics(null);
12126
+ setLoading(false);
12127
+ return;
12128
+ }
12129
+ try {
12130
+ setLoading(true);
12131
+ setError(null);
12132
+ const [workspaceData, metricsData] = await Promise.all([
12133
+ workspaceHealthService.getWorkspaceHealthById(workspaceId),
12134
+ workspaceHealthService.getHealthMetrics(workspaceId)
12135
+ ]);
12136
+ setWorkspace(workspaceData);
12137
+ setMetrics(metricsData);
12138
+ } catch (err) {
12139
+ console.error("Error fetching workspace health by ID:", err);
12140
+ setError(err);
12141
+ } finally {
12142
+ setLoading(false);
12143
+ }
12144
+ }, [workspaceId]);
12145
+ const handleRealtimeUpdate = useCallback((data) => {
12146
+ if (data.workspace_id === workspaceId) {
12147
+ const updatedWorkspace = workspaceHealthService["processHealthStatus"](data);
12148
+ setWorkspace(updatedWorkspace);
12149
+ }
12150
+ }, [workspaceId]);
12151
+ useEffect(() => {
12152
+ fetchData();
12153
+ if (refreshInterval > 0) {
12154
+ refreshIntervalRef.current = setInterval(fetchData, 1e4);
12155
+ }
12156
+ if (enableRealtime && workspaceId) {
12157
+ unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
12158
+ handleRealtimeUpdate
12159
+ );
12160
+ }
12161
+ return () => {
12162
+ if (refreshIntervalRef.current) {
12163
+ clearInterval(refreshIntervalRef.current);
12164
+ }
12165
+ if (unsubscribeRef.current) {
12166
+ unsubscribeRef.current();
12167
+ }
12168
+ };
12169
+ }, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, workspaceId]);
12170
+ return {
12171
+ workspace,
12172
+ metrics: metrics2,
12173
+ loading,
12174
+ error,
12175
+ refetch: fetchData
12176
+ };
12177
+ }
12117
12178
  function useDateFormatter() {
12118
12179
  const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
12119
12180
  const formatDate = useCallback(
@@ -23522,6 +23583,165 @@ var TicketHistory = ({ companyId }) => {
23522
23583
  ] });
23523
23584
  };
23524
23585
  var TicketHistory_default = TicketHistory;
23586
+ var HealthStatusIndicator = ({
23587
+ status,
23588
+ lastUpdated,
23589
+ showLabel = false,
23590
+ showTime = true,
23591
+ size = "md",
23592
+ className = "",
23593
+ inline = true,
23594
+ pulse = true
23595
+ }) => {
23596
+ const getStatusConfig = () => {
23597
+ switch (status) {
23598
+ case "healthy":
23599
+ return {
23600
+ color: "text-green-500",
23601
+ bgColor: "bg-green-500",
23602
+ borderColor: "border-green-500",
23603
+ label: "Healthy",
23604
+ icon: CheckCircle,
23605
+ shouldPulse: pulse
23606
+ };
23607
+ case "unhealthy":
23608
+ return {
23609
+ color: "text-red-500",
23610
+ bgColor: "bg-red-500",
23611
+ borderColor: "border-red-500",
23612
+ label: "Unhealthy",
23613
+ icon: XCircle,
23614
+ shouldPulse: false
23615
+ };
23616
+ case "warning":
23617
+ return {
23618
+ color: "text-yellow-500",
23619
+ bgColor: "bg-yellow-500",
23620
+ borderColor: "border-yellow-500",
23621
+ label: "Warning",
23622
+ icon: AlertTriangle,
23623
+ shouldPulse: true
23624
+ };
23625
+ case "unknown":
23626
+ default:
23627
+ return {
23628
+ color: "text-gray-400",
23629
+ bgColor: "bg-gray-400",
23630
+ borderColor: "border-gray-400",
23631
+ label: "Unknown",
23632
+ icon: Activity,
23633
+ shouldPulse: false
23634
+ };
23635
+ }
23636
+ };
23637
+ const config = getStatusConfig();
23638
+ config.icon;
23639
+ const sizeClasses = {
23640
+ sm: {
23641
+ dot: "h-2 w-2",
23642
+ icon: "h-3 w-3",
23643
+ text: "text-xs",
23644
+ spacing: "gap-1"
23645
+ },
23646
+ md: {
23647
+ dot: "h-3 w-3",
23648
+ icon: "h-4 w-4",
23649
+ text: "text-sm",
23650
+ spacing: "gap-1.5"
23651
+ },
23652
+ lg: {
23653
+ dot: "h-4 w-4",
23654
+ icon: "h-5 w-5",
23655
+ text: "text-base",
23656
+ spacing: "gap-2"
23657
+ }
23658
+ };
23659
+ const currentSize = sizeClasses[size];
23660
+ const containerClasses = clsx(
23661
+ "flex items-center",
23662
+ currentSize.spacing,
23663
+ inline ? "inline-flex" : "flex",
23664
+ className
23665
+ );
23666
+ const dotClasses = clsx(
23667
+ "rounded-full",
23668
+ currentSize.dot,
23669
+ config.bgColor,
23670
+ config.shouldPulse && "animate-pulse"
23671
+ );
23672
+ return /* @__PURE__ */ jsxs("div", { className: containerClasses, children: [
23673
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
23674
+ /* @__PURE__ */ jsx("div", { className: dotClasses }),
23675
+ config.shouldPulse && status === "healthy" && /* @__PURE__ */ jsx(
23676
+ "div",
23677
+ {
23678
+ className: clsx(
23679
+ "absolute rounded-full opacity-25",
23680
+ currentSize.dot,
23681
+ config.bgColor,
23682
+ "animate-ping"
23683
+ )
23684
+ }
23685
+ )
23686
+ ] }),
23687
+ showLabel && /* @__PURE__ */ jsx("span", { className: clsx(currentSize.text, "font-medium", config.color), children: config.label }),
23688
+ showTime && lastUpdated && /* @__PURE__ */ jsx("span", { className: clsx(currentSize.text, "text-gray-500 dark:text-gray-400"), children: lastUpdated })
23689
+ ] });
23690
+ };
23691
+ var DetailedHealthStatus = ({
23692
+ workspaceName,
23693
+ lineName,
23694
+ consecutiveMisses,
23695
+ showDetails = true,
23696
+ ...indicatorProps
23697
+ }) => {
23698
+ const getStatusConfig = () => {
23699
+ switch (indicatorProps.status) {
23700
+ case "healthy":
23701
+ return {
23702
+ bgClass: "bg-green-50 dark:bg-green-900/20",
23703
+ borderClass: "border-green-200 dark:border-green-800"
23704
+ };
23705
+ case "unhealthy":
23706
+ return {
23707
+ bgClass: "bg-red-50 dark:bg-red-900/20",
23708
+ borderClass: "border-red-200 dark:border-red-800"
23709
+ };
23710
+ case "warning":
23711
+ return {
23712
+ bgClass: "bg-yellow-50 dark:bg-yellow-900/20",
23713
+ borderClass: "border-yellow-200 dark:border-yellow-800"
23714
+ };
23715
+ default:
23716
+ return {
23717
+ bgClass: "bg-gray-50 dark:bg-gray-900/20",
23718
+ borderClass: "border-gray-200 dark:border-gray-800"
23719
+ };
23720
+ }
23721
+ };
23722
+ const config = getStatusConfig();
23723
+ return /* @__PURE__ */ jsx(
23724
+ "div",
23725
+ {
23726
+ className: clsx(
23727
+ "rounded-lg border p-3",
23728
+ config.bgClass,
23729
+ config.borderClass
23730
+ ),
23731
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
23732
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
23733
+ showDetails && workspaceName && /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: workspaceName }),
23734
+ showDetails && lineName && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: lineName }),
23735
+ /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(HealthStatusIndicator, { ...indicatorProps, showLabel: true }) })
23736
+ ] }),
23737
+ showDetails && consecutiveMisses !== void 0 && consecutiveMisses > 0 && /* @__PURE__ */ jsxs("div", { className: "ml-3 text-right", children: [
23738
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: "Missed" }),
23739
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-bold text-gray-900 dark:text-gray-100", children: consecutiveMisses })
23740
+ ] })
23741
+ ] })
23742
+ }
23743
+ );
23744
+ };
23525
23745
  var LinePdfExportButton = ({
23526
23746
  targetElement,
23527
23747
  fileName = "line-export",
@@ -24644,6 +24864,8 @@ var WorkspaceCard = ({
24644
24864
  cycleTime,
24645
24865
  operators,
24646
24866
  status = "normal",
24867
+ healthStatus,
24868
+ healthLastUpdated,
24647
24869
  onCardClick,
24648
24870
  headerActions,
24649
24871
  footerContent,
@@ -24736,6 +24958,19 @@ var WorkspaceCard = ({
24736
24958
  ] })
24737
24959
  ] })
24738
24960
  ] }),
24961
+ healthStatus && /* @__PURE__ */ jsx("div", { className: "pt-2 mt-auto border-t dark:border-gray-700", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
24962
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "System Health" }),
24963
+ /* @__PURE__ */ jsx(
24964
+ HealthStatusIndicator,
24965
+ {
24966
+ status: healthStatus,
24967
+ lastUpdated: healthLastUpdated,
24968
+ showTime: false,
24969
+ size: "sm",
24970
+ pulse: healthStatus === "healthy"
24971
+ }
24972
+ )
24973
+ ] }) }),
24739
24974
  chartData && chartData.data && chartData.data.length > 0 && /* @__PURE__ */ jsxs(
24740
24975
  "div",
24741
24976
  {
@@ -26394,6 +26629,194 @@ var BackButtonMinimal = ({
26394
26629
  }
26395
26630
  );
26396
26631
  };
26632
+ var InlineEditableText = ({
26633
+ value,
26634
+ onSave,
26635
+ placeholder = "Click to edit",
26636
+ className = "",
26637
+ editIconClassName = "",
26638
+ inputClassName = "",
26639
+ debounceDelay = 750,
26640
+ // 750ms for quick auto-save
26641
+ disabled = false
26642
+ }) => {
26643
+ const [isEditing, setIsEditing] = useState(false);
26644
+ const [editValue, setEditValue] = useState(value);
26645
+ const [saveStatus, setSaveStatus] = useState("idle");
26646
+ const [lastSavedValue, setLastSavedValue] = useState(value);
26647
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
26648
+ const inputRef = useRef(null);
26649
+ const containerRef = useRef(null);
26650
+ const debounceTimeout = useRef(void 0);
26651
+ const saveStatusTimeout = useRef(void 0);
26652
+ useEffect(() => {
26653
+ if (!isEditing) {
26654
+ setEditValue(value);
26655
+ setLastSavedValue(value);
26656
+ }
26657
+ }, [value, isEditing]);
26658
+ useEffect(() => {
26659
+ if (isEditing && inputRef.current) {
26660
+ requestAnimationFrame(() => {
26661
+ inputRef.current?.focus();
26662
+ inputRef.current?.select();
26663
+ });
26664
+ }
26665
+ }, [isEditing]);
26666
+ useEffect(() => {
26667
+ return () => {
26668
+ if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
26669
+ if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
26670
+ };
26671
+ }, []);
26672
+ const performSave = useCallback(async (valueToSave, shouldClose = false) => {
26673
+ const trimmedValue = valueToSave.trim();
26674
+ if (trimmedValue === lastSavedValue.trim()) {
26675
+ setHasUnsavedChanges(false);
26676
+ if (shouldClose) {
26677
+ setIsEditing(false);
26678
+ setSaveStatus("idle");
26679
+ }
26680
+ return;
26681
+ }
26682
+ setSaveStatus("saving");
26683
+ setHasUnsavedChanges(false);
26684
+ try {
26685
+ await onSave(trimmedValue);
26686
+ setLastSavedValue(trimmedValue);
26687
+ setSaveStatus("saved");
26688
+ if (!shouldClose && inputRef.current) {
26689
+ requestAnimationFrame(() => {
26690
+ inputRef.current?.focus();
26691
+ });
26692
+ }
26693
+ if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
26694
+ saveStatusTimeout.current = setTimeout(() => {
26695
+ setSaveStatus("idle");
26696
+ }, 2e3);
26697
+ if (shouldClose) {
26698
+ setIsEditing(false);
26699
+ }
26700
+ } catch (error) {
26701
+ console.error("Failed to save:", error);
26702
+ setSaveStatus("error");
26703
+ setHasUnsavedChanges(true);
26704
+ if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
26705
+ saveStatusTimeout.current = setTimeout(() => {
26706
+ setSaveStatus("idle");
26707
+ }, 3e3);
26708
+ if (!shouldClose && inputRef.current) {
26709
+ requestAnimationFrame(() => {
26710
+ inputRef.current?.focus();
26711
+ });
26712
+ }
26713
+ }
26714
+ }, [lastSavedValue, onSave]);
26715
+ useEffect(() => {
26716
+ const handleClickOutside = (event) => {
26717
+ if (isEditing && containerRef.current && !containerRef.current.contains(event.target)) {
26718
+ if (debounceTimeout.current) {
26719
+ clearTimeout(debounceTimeout.current);
26720
+ }
26721
+ performSave(editValue, true);
26722
+ }
26723
+ };
26724
+ if (isEditing) {
26725
+ document.addEventListener("mousedown", handleClickOutside);
26726
+ return () => document.removeEventListener("mousedown", handleClickOutside);
26727
+ }
26728
+ }, [isEditing, editValue, performSave]);
26729
+ const handleInputChange = (e) => {
26730
+ const newValue = e.target.value;
26731
+ setEditValue(newValue);
26732
+ if (newValue.trim() !== lastSavedValue.trim()) {
26733
+ setHasUnsavedChanges(true);
26734
+ setSaveStatus("idle");
26735
+ } else {
26736
+ setHasUnsavedChanges(false);
26737
+ }
26738
+ if (debounceTimeout.current) {
26739
+ clearTimeout(debounceTimeout.current);
26740
+ }
26741
+ if (newValue.trim() !== lastSavedValue.trim()) {
26742
+ debounceTimeout.current = setTimeout(() => {
26743
+ performSave(newValue, false);
26744
+ }, debounceDelay);
26745
+ }
26746
+ };
26747
+ const handleKeyDown = (e) => {
26748
+ if (e.key === "Enter") {
26749
+ e.preventDefault();
26750
+ if (debounceTimeout.current) {
26751
+ clearTimeout(debounceTimeout.current);
26752
+ }
26753
+ performSave(editValue, true);
26754
+ } else if (e.key === "Escape") {
26755
+ e.preventDefault();
26756
+ if (debounceTimeout.current) {
26757
+ clearTimeout(debounceTimeout.current);
26758
+ }
26759
+ setEditValue(lastSavedValue);
26760
+ setHasUnsavedChanges(false);
26761
+ setSaveStatus("idle");
26762
+ setIsEditing(false);
26763
+ }
26764
+ };
26765
+ const handleClick = () => {
26766
+ if (!disabled && !isEditing) {
26767
+ setIsEditing(true);
26768
+ setSaveStatus("idle");
26769
+ }
26770
+ };
26771
+ if (isEditing) {
26772
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className: "inline-flex items-center gap-1.5", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
26773
+ /* @__PURE__ */ jsx(
26774
+ "input",
26775
+ {
26776
+ ref: inputRef,
26777
+ type: "text",
26778
+ value: editValue,
26779
+ onChange: handleInputChange,
26780
+ onKeyDown: handleKeyDown,
26781
+ className: `px-2 py-1 pr-7 text-sm border rounded-md transition-colors duration-200
26782
+ ${saveStatus === "error" ? "border-red-400 focus:ring-red-500 focus:border-red-500" : hasUnsavedChanges ? "border-yellow-400 focus:ring-yellow-500 focus:border-yellow-500" : saveStatus === "saved" ? "border-green-400 focus:ring-green-500 focus:border-green-500" : "border-blue-400 focus:ring-blue-500 focus:border-blue-500"}
26783
+ focus:outline-none focus:ring-2 bg-white
26784
+ ${inputClassName}`,
26785
+ placeholder,
26786
+ autoComplete: "off"
26787
+ }
26788
+ ),
26789
+ /* @__PURE__ */ jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2", children: [
26790
+ saveStatus === "saving" && /* @__PURE__ */ jsx(Loader2, { className: "w-3.5 h-3.5 text-blue-500 animate-spin" }),
26791
+ saveStatus === "saved" && /* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5 text-green-500" }),
26792
+ saveStatus === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "w-3.5 h-3.5 text-red-500" }),
26793
+ saveStatus === "idle" && hasUnsavedChanges && /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-yellow-400 rounded-full" })
26794
+ ] })
26795
+ ] }) });
26796
+ }
26797
+ return /* @__PURE__ */ jsxs(
26798
+ "div",
26799
+ {
26800
+ onClick: handleClick,
26801
+ className: `inline-flex items-center gap-1.5 cursor-pointer px-2 py-1 rounded-md
26802
+ transition-all duration-200 hover:bg-gray-50 group
26803
+ ${disabled ? "cursor-not-allowed opacity-50" : ""}
26804
+ ${className}`,
26805
+ children: [
26806
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-900", children: editValue || placeholder }),
26807
+ /* @__PURE__ */ jsx(
26808
+ Edit2,
26809
+ {
26810
+ className: `w-3.5 h-3.5 text-gray-400 transition-opacity duration-200
26811
+ opacity-0 group-hover:opacity-100
26812
+ ${disabled ? "hidden" : ""}
26813
+ ${editIconClassName}`
26814
+ }
26815
+ )
26816
+ ]
26817
+ }
26818
+ );
26819
+ };
26397
26820
  var BottlenecksContent = ({
26398
26821
  workspaceId,
26399
26822
  workspaceName,
@@ -26475,11 +26898,14 @@ var BottlenecksContent = ({
26475
26898
  console.log(`[BottlenecksContent] No shift provided for historical date ${date}, defaulting to day shift (0)`);
26476
26899
  return "0";
26477
26900
  } else {
26478
- const currentShift = getCurrentShift2();
26479
- console.log(`[BottlenecksContent] Using current operational shift: ${currentShift.shiftId} (${currentShift.shiftName})`);
26901
+ const currentShift = getCurrentShift(
26902
+ dashboardConfig.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
26903
+ dashboardConfig.shiftConfig
26904
+ );
26905
+ console.log(`[BottlenecksContent] Using current operational shift: ${currentShift.shiftId}`);
26480
26906
  return currentShift.shiftId.toString();
26481
26907
  }
26482
- }, [shift, date]);
26908
+ }, [shift, date, dashboardConfig]);
26483
26909
  const {
26484
26910
  data: prefetchData,
26485
26911
  isFullyIndexed,
@@ -28139,6 +28565,388 @@ var KPISection = memo(({
28139
28565
  return true;
28140
28566
  });
28141
28567
  KPISection.displayName = "KPISection";
28568
+ var WorkspaceHealthCard = ({
28569
+ workspace,
28570
+ onClick,
28571
+ showDetails = true,
28572
+ className = ""
28573
+ }) => {
28574
+ const getStatusConfig = () => {
28575
+ switch (workspace.status) {
28576
+ case "healthy":
28577
+ return {
28578
+ gradient: "from-emerald-50 to-green-50 dark:from-emerald-950/30 dark:to-green-950/30",
28579
+ border: "border-emerald-200 dark:border-emerald-800",
28580
+ icon: CheckCircle2,
28581
+ iconColor: "text-emerald-600 dark:text-emerald-400",
28582
+ badge: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300",
28583
+ statusText: "Online",
28584
+ pulse: true
28585
+ };
28586
+ case "unhealthy":
28587
+ return {
28588
+ gradient: "from-rose-50 to-red-50 dark:from-rose-950/30 dark:to-red-950/30",
28589
+ border: "border-rose-200 dark:border-rose-800",
28590
+ icon: XCircle,
28591
+ iconColor: "text-rose-600 dark:text-rose-400",
28592
+ badge: "bg-rose-100 text-rose-700 dark:bg-rose-900/50 dark:text-rose-300",
28593
+ statusText: "Offline",
28594
+ pulse: false
28595
+ };
28596
+ case "warning":
28597
+ return {
28598
+ gradient: "from-amber-50 to-yellow-50 dark:from-amber-950/30 dark:to-yellow-950/30",
28599
+ border: "border-amber-200 dark:border-amber-800",
28600
+ icon: AlertTriangle,
28601
+ iconColor: "text-amber-600 dark:text-amber-400",
28602
+ badge: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
28603
+ statusText: "Degraded",
28604
+ pulse: true
28605
+ };
28606
+ default:
28607
+ return {
28608
+ gradient: "from-gray-50 to-slate-50 dark:from-gray-950/30 dark:to-slate-950/30",
28609
+ border: "border-gray-200 dark:border-gray-800",
28610
+ icon: Activity,
28611
+ iconColor: "text-gray-500 dark:text-gray-400",
28612
+ badge: "bg-gray-100 text-gray-700 dark:bg-gray-900/50 dark:text-gray-300",
28613
+ statusText: "Unknown",
28614
+ pulse: false
28615
+ };
28616
+ }
28617
+ };
28618
+ const config = getStatusConfig();
28619
+ const StatusIcon = config.icon;
28620
+ const handleClick = () => {
28621
+ if (onClick) {
28622
+ onClick(workspace);
28623
+ }
28624
+ };
28625
+ const handleKeyDown = (event) => {
28626
+ if (onClick && (event.key === "Enter" || event.key === " ")) {
28627
+ event.preventDefault();
28628
+ onClick(workspace);
28629
+ }
28630
+ };
28631
+ const formatTimeAgo = (timeString) => {
28632
+ return timeString.replace("about ", "").replace(" ago", "");
28633
+ };
28634
+ return /* @__PURE__ */ jsx(
28635
+ Card2,
28636
+ {
28637
+ className: clsx(
28638
+ "relative overflow-hidden transition-all duration-300",
28639
+ "bg-gradient-to-br",
28640
+ config.gradient,
28641
+ "border",
28642
+ config.border,
28643
+ "shadow-sm hover:shadow-md",
28644
+ onClick && "cursor-pointer hover:scale-[1.01]",
28645
+ workspace.isStale && "opacity-90",
28646
+ className
28647
+ ),
28648
+ onClick: handleClick,
28649
+ onKeyDown: handleKeyDown,
28650
+ tabIndex: onClick ? 0 : void 0,
28651
+ role: onClick ? "button" : void 0,
28652
+ "aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
28653
+ children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
28654
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-3", children: [
28655
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
28656
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 8)}` }),
28657
+ showDetails && workspace.line_name && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: workspace.line_name })
28658
+ ] }),
28659
+ /* @__PURE__ */ jsxs("div", { className: clsx(
28660
+ "flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium",
28661
+ config.badge
28662
+ ), children: [
28663
+ /* @__PURE__ */ jsx(StatusIcon, { className: "h-3.5 w-3.5" }),
28664
+ /* @__PURE__ */ jsx("span", { children: config.statusText })
28665
+ ] })
28666
+ ] }),
28667
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
28668
+ /* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5 text-gray-400" }),
28669
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap", children: [
28670
+ "Last seen: ",
28671
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
28672
+ ] })
28673
+ ] }) }) })
28674
+ ] })
28675
+ }
28676
+ );
28677
+ };
28678
+ var CompactWorkspaceHealthCard = ({
28679
+ workspace,
28680
+ onClick,
28681
+ className = ""
28682
+ }) => {
28683
+ const getStatusConfig = () => {
28684
+ switch (workspace.status) {
28685
+ case "healthy":
28686
+ return {
28687
+ dot: "bg-emerald-500",
28688
+ icon: CheckCircle2,
28689
+ iconColor: "text-emerald-600 dark:text-emerald-400",
28690
+ bg: "hover:bg-emerald-50 dark:hover:bg-emerald-950/20"
28691
+ };
28692
+ case "unhealthy":
28693
+ return {
28694
+ dot: "bg-rose-500",
28695
+ icon: XCircle,
28696
+ iconColor: "text-rose-600 dark:text-rose-400",
28697
+ bg: "hover:bg-rose-50 dark:hover:bg-rose-950/20"
28698
+ };
28699
+ case "warning":
28700
+ return {
28701
+ dot: "bg-amber-500",
28702
+ icon: AlertTriangle,
28703
+ iconColor: "text-amber-600 dark:text-amber-400",
28704
+ bg: "hover:bg-amber-50 dark:hover:bg-amber-950/20"
28705
+ };
28706
+ default:
28707
+ return {
28708
+ dot: "bg-gray-400",
28709
+ icon: Activity,
28710
+ iconColor: "text-gray-500 dark:text-gray-400",
28711
+ bg: "hover:bg-gray-50 dark:hover:bg-gray-950/20"
28712
+ };
28713
+ }
28714
+ };
28715
+ const config = getStatusConfig();
28716
+ const StatusIcon = config.icon;
28717
+ const handleClick = () => {
28718
+ if (onClick) {
28719
+ onClick(workspace);
28720
+ }
28721
+ };
28722
+ return /* @__PURE__ */ jsxs(
28723
+ "div",
28724
+ {
28725
+ className: clsx(
28726
+ "flex items-center justify-between px-4 py-3 rounded-lg border",
28727
+ "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700",
28728
+ "transition-all duration-200",
28729
+ onClick && `cursor-pointer ${config.bg}`,
28730
+ className
28731
+ ),
28732
+ onClick: handleClick,
28733
+ role: onClick ? "button" : void 0,
28734
+ tabIndex: onClick ? 0 : void 0,
28735
+ children: [
28736
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
28737
+ /* @__PURE__ */ jsx(StatusIcon, { className: clsx("h-5 w-5", config.iconColor) }),
28738
+ /* @__PURE__ */ jsxs("div", { children: [
28739
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: workspace.workspace_display_name || `WS-${workspace.workspace_id.slice(0, 6)}` }),
28740
+ workspace.line_name && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.line_name })
28741
+ ] })
28742
+ ] }),
28743
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
28744
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
28745
+ /* @__PURE__ */ jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
28746
+ ] })
28747
+ ]
28748
+ }
28749
+ );
28750
+ };
28751
+ var HealthStatusGrid = ({
28752
+ workspaces,
28753
+ onWorkspaceClick,
28754
+ showFilters = true,
28755
+ groupBy: initialGroupBy = "none",
28756
+ className = ""
28757
+ }) => {
28758
+ const [searchTerm, setSearchTerm] = useState("");
28759
+ const [statusFilter, setStatusFilter] = useState("all");
28760
+ const [groupBy, setGroupBy] = useState(initialGroupBy);
28761
+ const [expandedGroups, setExpandedGroups] = useState(/* @__PURE__ */ new Set());
28762
+ const lastGroupByRef = useRef(initialGroupBy);
28763
+ const hasInitializedGroupsRef = useRef(false);
28764
+ const filteredWorkspaces = useMemo(() => {
28765
+ let filtered = [...workspaces];
28766
+ if (searchTerm) {
28767
+ const search = searchTerm.toLowerCase();
28768
+ filtered = filtered.filter(
28769
+ (w) => w.workspace_display_name?.toLowerCase().includes(search) || w.line_name?.toLowerCase().includes(search) || w.company_name?.toLowerCase().includes(search)
28770
+ );
28771
+ }
28772
+ if (statusFilter !== "all") {
28773
+ filtered = filtered.filter((w) => w.status === statusFilter);
28774
+ }
28775
+ return filtered;
28776
+ }, [workspaces, searchTerm, statusFilter]);
28777
+ const groupedWorkspaces = useMemo(() => {
28778
+ if (groupBy === "none") {
28779
+ return { "All Workspaces": filteredWorkspaces };
28780
+ }
28781
+ const groups = {};
28782
+ filteredWorkspaces.forEach((workspace) => {
28783
+ let key = "Unknown";
28784
+ switch (groupBy) {
28785
+ case "line":
28786
+ key = workspace.line_name || "Unknown Line";
28787
+ break;
28788
+ case "status":
28789
+ key = workspace.status;
28790
+ break;
28791
+ }
28792
+ if (!groups[key]) {
28793
+ groups[key] = [];
28794
+ }
28795
+ groups[key].push(workspace);
28796
+ });
28797
+ const sortedGroups = {};
28798
+ Object.keys(groups).sort().forEach((key) => {
28799
+ sortedGroups[key] = groups[key];
28800
+ });
28801
+ return sortedGroups;
28802
+ }, [filteredWorkspaces, groupBy]);
28803
+ useEffect(() => {
28804
+ if (groupBy !== lastGroupByRef.current) {
28805
+ lastGroupByRef.current = groupBy;
28806
+ hasInitializedGroupsRef.current = false;
28807
+ if (groupBy === "none") {
28808
+ setExpandedGroups(/* @__PURE__ */ new Set());
28809
+ }
28810
+ }
28811
+ }, [groupBy]);
28812
+ useEffect(() => {
28813
+ if (groupBy !== "none" && !hasInitializedGroupsRef.current && Object.keys(groupedWorkspaces).length > 0) {
28814
+ hasInitializedGroupsRef.current = true;
28815
+ setExpandedGroups(new Set(Object.keys(groupedWorkspaces)));
28816
+ }
28817
+ }, [groupBy, groupedWorkspaces]);
28818
+ const toggleGroup = (groupName) => {
28819
+ const newExpanded = new Set(expandedGroups);
28820
+ if (newExpanded.has(groupName)) {
28821
+ newExpanded.delete(groupName);
28822
+ } else {
28823
+ newExpanded.add(groupName);
28824
+ }
28825
+ setExpandedGroups(newExpanded);
28826
+ };
28827
+ const getStatusCounts = () => {
28828
+ const counts = {
28829
+ healthy: 0,
28830
+ unhealthy: 0,
28831
+ warning: 0,
28832
+ unknown: 0
28833
+ };
28834
+ workspaces.forEach((w) => {
28835
+ counts[w.status]++;
28836
+ });
28837
+ return counts;
28838
+ };
28839
+ const statusCounts = getStatusCounts();
28840
+ return /* @__PURE__ */ jsxs("div", { className: clsx("space-y-4", className), children: [
28841
+ showFilters && /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4", children: [
28842
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 flex-wrap", children: [
28843
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-[200px]", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
28844
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }),
28845
+ /* @__PURE__ */ jsx(
28846
+ "input",
28847
+ {
28848
+ type: "text",
28849
+ placeholder: "Search workspaces...",
28850
+ value: searchTerm,
28851
+ onChange: (e) => setSearchTerm(e.target.value),
28852
+ className: "w-full pl-10 pr-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
28853
+ }
28854
+ )
28855
+ ] }) }),
28856
+ /* @__PURE__ */ jsxs(Select, { value: statusFilter, onValueChange: (value) => setStatusFilter(value), children: [
28857
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-[180px] bg-white border-gray-200", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "All statuses" }) }),
28858
+ /* @__PURE__ */ jsxs(SelectContent, { className: "bg-white border border-gray-200 shadow-lg", children: [
28859
+ /* @__PURE__ */ jsxs(SelectItem, { value: "all", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: [
28860
+ "All (",
28861
+ workspaces.length,
28862
+ ")"
28863
+ ] }),
28864
+ /* @__PURE__ */ jsx(SelectItem, { value: "healthy", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
28865
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500" }),
28866
+ /* @__PURE__ */ jsxs("span", { children: [
28867
+ "Healthy (",
28868
+ statusCounts.healthy,
28869
+ ")"
28870
+ ] })
28871
+ ] }) }),
28872
+ /* @__PURE__ */ jsx(SelectItem, { value: "unhealthy", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
28873
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-red-500" }),
28874
+ /* @__PURE__ */ jsxs("span", { children: [
28875
+ "Unhealthy (",
28876
+ statusCounts.unhealthy,
28877
+ ")"
28878
+ ] })
28879
+ ] }) }),
28880
+ /* @__PURE__ */ jsx(SelectItem, { value: "warning", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
28881
+ /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-500" }),
28882
+ /* @__PURE__ */ jsxs("span", { children: [
28883
+ "Warning (",
28884
+ statusCounts.warning,
28885
+ ")"
28886
+ ] })
28887
+ ] }) })
28888
+ ] })
28889
+ ] }),
28890
+ /* @__PURE__ */ jsxs(Select, { value: groupBy, onValueChange: (value) => setGroupBy(value), children: [
28891
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-[160px] bg-white border-gray-200", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Group by" }) }),
28892
+ /* @__PURE__ */ jsxs(SelectContent, { className: "bg-white border border-gray-200 shadow-lg", children: [
28893
+ /* @__PURE__ */ jsx(SelectItem, { value: "none", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: "No grouping" }),
28894
+ /* @__PURE__ */ jsx(SelectItem, { value: "line", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: "Group by Line" }),
28895
+ /* @__PURE__ */ jsx(SelectItem, { value: "status", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: "Group by Status" })
28896
+ ] })
28897
+ ] })
28898
+ ] }),
28899
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 text-sm text-gray-500 dark:text-gray-400", children: [
28900
+ "Showing ",
28901
+ filteredWorkspaces.length,
28902
+ " of ",
28903
+ workspaces.length,
28904
+ " workspaces"
28905
+ ] })
28906
+ ] }),
28907
+ /* @__PURE__ */ jsx("div", { className: "space-y-6", children: Object.entries(groupedWorkspaces).map(([groupName, groupWorkspaces]) => {
28908
+ const isExpanded = groupBy === "none" || expandedGroups.has(groupName);
28909
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
28910
+ groupBy !== "none" && /* @__PURE__ */ jsxs(
28911
+ "div",
28912
+ {
28913
+ className: "flex items-center justify-between cursor-pointer group",
28914
+ onClick: () => toggleGroup(groupName),
28915
+ children: [
28916
+ /* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
28917
+ groupName,
28918
+ /* @__PURE__ */ jsxs("span", { className: "text-sm font-normal text-gray-500 dark:text-gray-400", children: [
28919
+ "(",
28920
+ groupWorkspaces.length,
28921
+ ")"
28922
+ ] })
28923
+ ] }),
28924
+ /* @__PURE__ */ jsx(
28925
+ ChevronDown,
28926
+ {
28927
+ className: clsx(
28928
+ "h-5 w-5 text-gray-400 transition-transform",
28929
+ isExpanded && "rotate-180"
28930
+ )
28931
+ }
28932
+ )
28933
+ ]
28934
+ }
28935
+ ),
28936
+ isExpanded && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4", children: groupWorkspaces.map((workspace) => /* @__PURE__ */ jsx(
28937
+ WorkspaceHealthCard,
28938
+ {
28939
+ workspace,
28940
+ onClick: onWorkspaceClick,
28941
+ showDetails: true
28942
+ },
28943
+ workspace.workspace_id
28944
+ )) })
28945
+ ] }, groupName);
28946
+ }) }),
28947
+ filteredWorkspaces.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-gray-500 dark:text-gray-400", children: searchTerm || statusFilter !== "all" ? "No workspaces found matching your filters." : "No workspaces available." }) })
28948
+ ] });
28949
+ };
28142
28950
  var ISTTimer2 = ISTTimer_default;
28143
28951
  var DashboardHeader = memo(({ lineTitle, className = "", headerControls }) => {
28144
28952
  const getShiftName = () => {
@@ -28636,6 +29444,17 @@ var SideNavBar = memo(({
28636
29444
  });
28637
29445
  onMobileMenuClose?.();
28638
29446
  }, [navigate, onMobileMenuClose]);
29447
+ const handleHealthClick = useCallback(() => {
29448
+ navigate("/health", {
29449
+ trackingEvent: {
29450
+ name: "Health Status Page Clicked",
29451
+ properties: {
29452
+ source: "side_nav"
29453
+ }
29454
+ }
29455
+ });
29456
+ onMobileMenuClose?.();
29457
+ }, [navigate, onMobileMenuClose]);
28639
29458
  const handleLogoClick = useCallback(() => {
28640
29459
  navigate("/");
28641
29460
  onMobileMenuClose?.();
@@ -28649,6 +29468,7 @@ var SideNavBar = memo(({
28649
29468
  const profileButtonClasses = useMemo(() => getButtonClasses("/profile"), [getButtonClasses, pathname]);
28650
29469
  const helpButtonClasses = useMemo(() => getButtonClasses("/help"), [getButtonClasses, pathname]);
28651
29470
  const skusButtonClasses = useMemo(() => getButtonClasses("/skus"), [getButtonClasses, pathname]);
29471
+ const healthButtonClasses = useMemo(() => getButtonClasses("/health"), [getButtonClasses, pathname]);
28652
29472
  const NavigationContent = () => /* @__PURE__ */ jsxs(Fragment, { children: [
28653
29473
  /* @__PURE__ */ jsx("div", { className: "w-full py-6 px-4 flex-shrink-0", children: /* @__PURE__ */ jsx(
28654
29474
  "button",
@@ -28793,6 +29613,21 @@ var SideNavBar = memo(({
28793
29613
  /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Help" })
28794
29614
  ]
28795
29615
  }
29616
+ ),
29617
+ /* @__PURE__ */ jsxs(
29618
+ "button",
29619
+ {
29620
+ onClick: handleHealthClick,
29621
+ className: healthButtonClasses,
29622
+ "aria-label": "System Health",
29623
+ tabIndex: 0,
29624
+ role: "tab",
29625
+ "aria-selected": pathname === "/health" || pathname.startsWith("/health/"),
29626
+ children: [
29627
+ /* @__PURE__ */ jsx(HeartIcon, { className: "w-5 h-5 mb-1" }),
29628
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Health" })
29629
+ ]
29630
+ }
28796
29631
  )
28797
29632
  ] })
28798
29633
  ] }),
@@ -35418,6 +36253,7 @@ var TargetsViewUI = ({
35418
36253
  onSaveLine,
35419
36254
  onToggleBulkConfigure,
35420
36255
  onBulkConfigure,
36256
+ onUpdateWorkspaceDisplayName,
35421
36257
  // SKU props
35422
36258
  skuEnabled = false,
35423
36259
  skus = [],
@@ -35583,7 +36419,18 @@ var TargetsViewUI = ({
35583
36419
  {
35584
36420
  className: "px-6 py-4 hover:bg-gray-50 transition-all duration-200",
35585
36421
  children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-6 items-center", children: [
35586
- /* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: formattedName }) }),
36422
+ /* @__PURE__ */ jsx("div", { className: "col-span-2", children: onUpdateWorkspaceDisplayName ? /* @__PURE__ */ jsx(
36423
+ InlineEditableText,
36424
+ {
36425
+ value: formattedName,
36426
+ onSave: async (newName) => {
36427
+ await onUpdateWorkspaceDisplayName(workspace.id, newName);
36428
+ },
36429
+ placeholder: "Workspace name",
36430
+ className: "font-medium text-gray-900",
36431
+ inputClassName: "min-w-[120px]"
36432
+ }
36433
+ ) : /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: formattedName }) }),
35587
36434
  /* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxs(
35588
36435
  "select",
35589
36436
  {
@@ -36324,6 +37171,17 @@ var TargetsView = ({
36324
37171
  router.push("/");
36325
37172
  }
36326
37173
  };
37174
+ const handleUpdateWorkspaceDisplayName = useCallback(async (workspaceId, displayName) => {
37175
+ try {
37176
+ await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
37177
+ await forceRefreshWorkspaceDisplayNames();
37178
+ toast.success("Workspace name updated successfully");
37179
+ } catch (error) {
37180
+ console.error("Error updating workspace display name:", error);
37181
+ toast.error("Failed to update workspace name");
37182
+ throw error;
37183
+ }
37184
+ }, []);
36327
37185
  return /* @__PURE__ */ jsx(
36328
37186
  TargetsViewUI_default,
36329
37187
  {
@@ -36346,6 +37204,7 @@ var TargetsView = ({
36346
37204
  onSaveLine: handleSaveLine,
36347
37205
  onToggleBulkConfigure: handleToggleBulkConfigure,
36348
37206
  onBulkConfigure: handleBulkConfigure,
37207
+ onUpdateWorkspaceDisplayName: handleUpdateWorkspaceDisplayName,
36349
37208
  skuEnabled,
36350
37209
  skus,
36351
37210
  onUpdateSelectedSKU: updateSelectedSKU,
@@ -36444,6 +37303,14 @@ var WorkspaceDetailView = ({
36444
37303
  const [usingFallbackData, setUsingFallbackData] = useState(false);
36445
37304
  const [showIdleTime, setShowIdleTime] = useState(false);
36446
37305
  const dashboardConfig = useDashboardConfig();
37306
+ const {
37307
+ workspace: workspaceHealth,
37308
+ loading: healthLoading,
37309
+ error: healthError
37310
+ } = useWorkspaceHealthById(workspaceId, {
37311
+ enableRealtime: true,
37312
+ refreshInterval: 3e4
37313
+ });
36447
37314
  const {
36448
37315
  status: prefetchStatus,
36449
37316
  data: prefetchData,
@@ -36796,10 +37663,23 @@ var WorkspaceDetailView = ({
36796
37663
  "aria-label": "Navigate back to previous page"
36797
37664
  }
36798
37665
  ) }),
36799
- /* @__PURE__ */ jsxs("div", { className: "absolute left-1/2 transform -translate-x-1/2 flex items-center gap-3", children: [
37666
+ /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
36800
37667
  /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }),
36801
- /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
36802
- ] }),
37668
+ workspaceHealth && /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
37669
+ /* @__PURE__ */ jsx("span", { className: clsx(
37670
+ "animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
37671
+ workspaceHealth.status === "healthy" ? "bg-green-400" : "bg-red-400"
37672
+ ) }),
37673
+ /* @__PURE__ */ jsx("span", { className: clsx(
37674
+ "relative inline-flex rounded-full h-2.5 w-2.5",
37675
+ workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
37676
+ ) })
37677
+ ] })
37678
+ ] }) }),
37679
+ workspaceHealth && activeTab !== "monthly_history" && /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-0 flex items-center h-8", children: /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
37680
+ "Last update: ",
37681
+ workspaceHealth.timeSinceLastUpdate
37682
+ ] }) }),
36803
37683
  /* @__PURE__ */ jsx("div", { className: "w-full h-8" })
36804
37684
  ] }),
36805
37685
  activeTab !== "monthly_history" && /* @__PURE__ */ jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4", children: [
@@ -37363,6 +38243,253 @@ var SKUManagementView = () => {
37363
38243
  )
37364
38244
  ] });
37365
38245
  };
38246
+ var WorkspaceHealthView = ({
38247
+ lineId,
38248
+ companyId,
38249
+ onNavigate,
38250
+ className = ""
38251
+ }) => {
38252
+ const router = useRouter();
38253
+ const [groupBy, setGroupBy] = useState("line");
38254
+ const operationalDate = getOperationalDate();
38255
+ const currentHour = (/* @__PURE__ */ new Date()).getHours();
38256
+ const isNightShift = currentHour >= 18 || currentHour < 6;
38257
+ const shiftType = isNightShift ? "Night" : "Day";
38258
+ const formatDate = (date) => {
38259
+ const d = new Date(date);
38260
+ return d.toLocaleDateString("en-IN", {
38261
+ month: "long",
38262
+ day: "numeric",
38263
+ year: "numeric",
38264
+ timeZone: "Asia/Kolkata"
38265
+ });
38266
+ };
38267
+ const getShiftIcon = (shift) => {
38268
+ return shift === "Night" ? /* @__PURE__ */ jsx(Moon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Sun, { className: "h-4 w-4" });
38269
+ };
38270
+ const {
38271
+ workspaces,
38272
+ summary,
38273
+ loading,
38274
+ error,
38275
+ refetch
38276
+ } = useWorkspaceHealth({
38277
+ lineId,
38278
+ companyId,
38279
+ enableRealtime: true,
38280
+ refreshInterval: 1e4
38281
+ // Refresh every 10 seconds for more responsive updates
38282
+ });
38283
+ const handleWorkspaceClick = useCallback(
38284
+ (workspace) => {
38285
+ const url = `/workspace/${workspace.workspace_id}`;
38286
+ if (onNavigate) {
38287
+ onNavigate(url);
38288
+ } else {
38289
+ router.push(url);
38290
+ }
38291
+ },
38292
+ [router, onNavigate]
38293
+ );
38294
+ const handleExport = useCallback(() => {
38295
+ const csv = [
38296
+ ["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
38297
+ ...workspaces.map((w) => [
38298
+ w.workspace_display_name || "",
38299
+ w.line_name || "",
38300
+ w.company_name || "",
38301
+ w.status,
38302
+ w.last_heartbeat,
38303
+ w.consecutive_misses?.toString() || "0"
38304
+ ])
38305
+ ].map((row) => row.join(",")).join("\n");
38306
+ const blob = new Blob([csv], { type: "text/csv" });
38307
+ const url = window.URL.createObjectURL(blob);
38308
+ const a = document.createElement("a");
38309
+ a.href = url;
38310
+ a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
38311
+ document.body.appendChild(a);
38312
+ a.click();
38313
+ document.body.removeChild(a);
38314
+ window.URL.revokeObjectURL(url);
38315
+ }, [workspaces]);
38316
+ const getStatusIcon = (status) => {
38317
+ switch (status) {
38318
+ case "healthy":
38319
+ return /* @__PURE__ */ jsx(CheckCircle, { className: "h-5 w-5 text-green-500" });
38320
+ case "unhealthy":
38321
+ return /* @__PURE__ */ jsx(XCircle, { className: "h-5 w-5 text-red-500" });
38322
+ case "warning":
38323
+ return /* @__PURE__ */ jsx(AlertTriangle, { className: "h-5 w-5 text-yellow-500" });
38324
+ default:
38325
+ return /* @__PURE__ */ jsx(Activity, { className: "h-5 w-5 text-gray-400" });
38326
+ }
38327
+ };
38328
+ const getUptimeColor = (percentage) => {
38329
+ if (percentage >= 99) return "text-green-600 dark:text-green-400";
38330
+ if (percentage >= 95) return "text-yellow-600 dark:text-yellow-400";
38331
+ return "text-red-600 dark:text-red-400";
38332
+ };
38333
+ if (loading && !summary) {
38334
+ return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsx(LoadingState, {}) });
38335
+ }
38336
+ if (error) {
38337
+ return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto", children: /* @__PURE__ */ jsx(Card2, { className: "border-red-200 dark:border-red-800", children: /* @__PURE__ */ jsxs(CardContent2, { className: "p-8 text-center", children: [
38338
+ /* @__PURE__ */ jsx(XCircle, { className: "h-12 w-12 text-red-500 mx-auto mb-4" }),
38339
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2", children: "Error Loading Health Status" }),
38340
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 dark:text-gray-400 mb-4", children: error.message || "Unable to load workspace health status" }),
38341
+ /* @__PURE__ */ jsx(
38342
+ "button",
38343
+ {
38344
+ onClick: () => refetch(),
38345
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors",
38346
+ children: "Try Again"
38347
+ }
38348
+ )
38349
+ ] }) }) }) });
38350
+ }
38351
+ return /* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
38352
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-2 sm:px-2.5 lg:px-3 py-1.5 sm:py-2 lg:py-3 flex flex-col shadow-sm bg-white", children: [
38353
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
38354
+ /* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
38355
+ BackButtonMinimal,
38356
+ {
38357
+ onClick: () => router.push("/"),
38358
+ text: "Back",
38359
+ size: "default",
38360
+ "aria-label": "Navigate back to dashboard"
38361
+ }
38362
+ ) }),
38363
+ /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
38364
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "System Health" }),
38365
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
38366
+ /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
38367
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
38368
+ ] })
38369
+ ] }) }),
38370
+ /* @__PURE__ */ jsxs("div", { className: "absolute right-0 flex gap-2", children: [
38371
+ /* @__PURE__ */ jsx(
38372
+ "button",
38373
+ {
38374
+ onClick: () => {
38375
+ refetch();
38376
+ },
38377
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
38378
+ "aria-label": "Refresh",
38379
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
38380
+ }
38381
+ ),
38382
+ /* @__PURE__ */ jsx(
38383
+ "button",
38384
+ {
38385
+ onClick: handleExport,
38386
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
38387
+ "aria-label": "Export CSV",
38388
+ children: /* @__PURE__ */ jsx(Download, { className: "h-5 w-5" })
38389
+ }
38390
+ )
38391
+ ] }),
38392
+ /* @__PURE__ */ jsx("div", { className: "w-full h-8" })
38393
+ ] }),
38394
+ /* @__PURE__ */ jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4", children: [
38395
+ /* @__PURE__ */ jsx("div", { className: "text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsx(LiveTimer, {}) }),
38396
+ /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
38397
+ /* @__PURE__ */ jsx("span", { className: "text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
38398
+ /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
38399
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38400
+ /* @__PURE__ */ jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
38401
+ /* @__PURE__ */ jsxs("span", { className: "text-base font-medium text-blue-600", children: [
38402
+ shiftType,
38403
+ " Shift"
38404
+ ] })
38405
+ ] })
38406
+ ] }) })
38407
+ ] }),
38408
+ /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
38409
+ summary && /* @__PURE__ */ jsxs(
38410
+ motion.div,
38411
+ {
38412
+ initial: { opacity: 0, y: 20 },
38413
+ animate: { opacity: 1, y: 0 },
38414
+ transition: { duration: 0.3, delay: 0.1 },
38415
+ className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
38416
+ children: [
38417
+ /* @__PURE__ */ jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
38418
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "Overall System Status" }) }),
38419
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
38420
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
38421
+ /* @__PURE__ */ jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
38422
+ summary.uptimePercentage.toFixed(1),
38423
+ "%"
38424
+ ] }),
38425
+ summary.uptimePercentage >= 99 ? /* @__PURE__ */ jsx(TrendingUp, { className: "h-5 w-5 text-green-500" }) : /* @__PURE__ */ jsx(TrendingDown, { className: "h-5 w-5 text-red-500" })
38426
+ ] }),
38427
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: [
38428
+ summary.healthyWorkspaces,
38429
+ " of ",
38430
+ summary.totalWorkspaces,
38431
+ " workspaces healthy"
38432
+ ] })
38433
+ ] })
38434
+ ] }),
38435
+ /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
38436
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
38437
+ getStatusIcon("healthy"),
38438
+ "Healthy"
38439
+ ] }) }),
38440
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
38441
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.healthyWorkspaces }),
38442
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
38443
+ ] })
38444
+ ] }),
38445
+ /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
38446
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
38447
+ getStatusIcon("warning"),
38448
+ "Warning"
38449
+ ] }) }),
38450
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
38451
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.warningWorkspaces }),
38452
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
38453
+ ] })
38454
+ ] }),
38455
+ /* @__PURE__ */ jsxs(Card2, { className: "bg-white", children: [
38456
+ /* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
38457
+ getStatusIcon("unhealthy"),
38458
+ "Unhealthy"
38459
+ ] }) }),
38460
+ /* @__PURE__ */ jsxs(CardContent2, { children: [
38461
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.unhealthyWorkspaces }),
38462
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
38463
+ ] })
38464
+ ] })
38465
+ ]
38466
+ }
38467
+ ),
38468
+ /* @__PURE__ */ jsx(
38469
+ motion.div,
38470
+ {
38471
+ initial: { opacity: 0, y: 20 },
38472
+ animate: { opacity: 1, y: 0 },
38473
+ transition: { duration: 0.3, delay: 0.2 },
38474
+ children: /* @__PURE__ */ jsx(
38475
+ HealthStatusGrid,
38476
+ {
38477
+ workspaces,
38478
+ onWorkspaceClick: handleWorkspaceClick,
38479
+ showFilters: true,
38480
+ groupBy
38481
+ }
38482
+ )
38483
+ }
38484
+ )
38485
+ ] })
38486
+ ] });
38487
+ };
38488
+ var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
38489
+ redirectTo: "/login",
38490
+ requireAuth: true
38491
+ });
38492
+ var AuthenticatedWorkspaceHealthView = WorkspaceHealthView;
37366
38493
  var S3Service = class {
37367
38494
  constructor(config) {
37368
38495
  this.s3Client = null;
@@ -37818,4 +38945,4 @@ var streamProxyConfig = {
37818
38945
  }
37819
38946
  };
37820
38947
 
37821
- export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CongratulationsOverlay, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, 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, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPrefetchManager, videoPreloader, whatsappService, withAuth, withRegistry, workspaceService };
38948
+ export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedWorkspaceHealthView, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CompactWorkspaceHealthCard, CongratulationsOverlay, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, 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, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealth, useWorkspaceHealthById, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPrefetchManager, videoPreloader, whatsappService, withAuth, withRegistry, workspaceHealthService, workspaceService };