@optifye/dashboard-core 6.5.11 → 6.6.0
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.css +376 -30
- package/dist/index.d.mts +564 -30
- package/dist/index.d.ts +564 -30
- package/dist/index.js +3278 -485
- package/dist/index.mjs +3258 -489
- package/global.css +58 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ var jsPDF = require('jspdf');
|
|
|
23
23
|
var SelectPrimitive = require('@radix-ui/react-select');
|
|
24
24
|
var videojs = require('video.js');
|
|
25
25
|
require('video.js/dist/video-js.css');
|
|
26
|
+
var reactDom = require('react-dom');
|
|
26
27
|
var sonner = require('sonner');
|
|
27
28
|
var clientS3 = require('@aws-sdk/client-s3');
|
|
28
29
|
var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
|
|
@@ -4837,8 +4838,347 @@ var S3ClipsService = class {
|
|
|
4837
4838
|
return this.apiClient.getStats();
|
|
4838
4839
|
}
|
|
4839
4840
|
};
|
|
4840
|
-
|
|
4841
|
-
|
|
4841
|
+
var getSupabaseClient2 = () => {
|
|
4842
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
4843
|
+
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
4844
|
+
if (!url || !key) {
|
|
4845
|
+
throw new Error("Supabase configuration missing");
|
|
4846
|
+
}
|
|
4847
|
+
return supabaseJs.createClient(url, key);
|
|
4848
|
+
};
|
|
4849
|
+
var getAuthToken2 = async () => {
|
|
4850
|
+
try {
|
|
4851
|
+
const supabase = getSupabaseClient2();
|
|
4852
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
4853
|
+
console.log("[S3ClipsSupabase] Auth session exists:", !!session, "has token:", !!session?.access_token);
|
|
4854
|
+
return session?.access_token || null;
|
|
4855
|
+
} catch (error) {
|
|
4856
|
+
console.error("[S3ClipsSupabase] Error getting auth token:", error);
|
|
4857
|
+
return null;
|
|
4858
|
+
}
|
|
4859
|
+
};
|
|
4860
|
+
var S3ClipsSupabaseService = class {
|
|
4861
|
+
constructor(config) {
|
|
4862
|
+
this.requestCache = /* @__PURE__ */ new Map();
|
|
4863
|
+
// Flags for compatibility
|
|
4864
|
+
this.isIndexBuilding = false;
|
|
4865
|
+
this.isPrefetching = false;
|
|
4866
|
+
this.currentMetadataFetches = 0;
|
|
4867
|
+
this.MAX_CONCURRENT_METADATA = 3;
|
|
4868
|
+
this.config = config;
|
|
4869
|
+
if (!config.s3Config) {
|
|
4870
|
+
throw new Error("S3 configuration is required");
|
|
4871
|
+
}
|
|
4872
|
+
const processing = config.s3Config.processing || {};
|
|
4873
|
+
this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
|
|
4874
|
+
this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
|
|
4875
|
+
this.concurrencyLimit = processing.concurrencyLimit || 10;
|
|
4876
|
+
this.maxInitialFetch = processing.maxInitialFetch || 60;
|
|
4877
|
+
console.log("[S3ClipsSupabase] \u2705 Initialized with Supabase backend - Direct database queries!");
|
|
4878
|
+
}
|
|
4879
|
+
/**
|
|
4880
|
+
* Fetch with authentication and error handling
|
|
4881
|
+
*/
|
|
4882
|
+
async fetchWithAuth(endpoint, body) {
|
|
4883
|
+
const token = await getAuthToken2();
|
|
4884
|
+
if (!token) {
|
|
4885
|
+
throw new Error("Authentication required");
|
|
4886
|
+
}
|
|
4887
|
+
const apiEndpoint = "/api/clips/supabase";
|
|
4888
|
+
const requestBody = {
|
|
4889
|
+
...body,
|
|
4890
|
+
action: endpoint.replace("/api/clips/supabase/", "")
|
|
4891
|
+
};
|
|
4892
|
+
console.log(`[S3ClipsSupabase] Making request to ${apiEndpoint} with action: ${requestBody.action}, body:`, requestBody);
|
|
4893
|
+
const response = await fetch(apiEndpoint, {
|
|
4894
|
+
method: "POST",
|
|
4895
|
+
headers: {
|
|
4896
|
+
"Authorization": `Bearer ${token}`,
|
|
4897
|
+
"Content-Type": "application/json"
|
|
4898
|
+
},
|
|
4899
|
+
body: JSON.stringify(requestBody)
|
|
4900
|
+
});
|
|
4901
|
+
console.log(`[S3ClipsSupabase] Response status: ${response.status}`);
|
|
4902
|
+
if (!response.ok) {
|
|
4903
|
+
const error = await response.json().catch(() => ({ error: "Request failed" }));
|
|
4904
|
+
console.error(`[S3ClipsSupabase] API error:`, error);
|
|
4905
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
4906
|
+
}
|
|
4907
|
+
const data = await response.json();
|
|
4908
|
+
if (requestBody.action === "by-index" || requestBody.action === "batch") {
|
|
4909
|
+
console.log(`[S3ClipsSupabase] API Response for ${requestBody.action}:`, {
|
|
4910
|
+
action: requestBody.action,
|
|
4911
|
+
hasData: !!data,
|
|
4912
|
+
dataKeys: Object.keys(data || {}),
|
|
4913
|
+
video: requestBody.action === "by-index" ? data?.video : void 0,
|
|
4914
|
+
videosCount: requestBody.action === "batch" ? data?.videos?.length : void 0
|
|
4915
|
+
});
|
|
4916
|
+
} else if (requestBody.action === "clip-types") {
|
|
4917
|
+
console.log(`[S3ClipsSupabase] API Response for clip-types:`, {
|
|
4918
|
+
action: requestBody.action,
|
|
4919
|
+
hasData: !!data,
|
|
4920
|
+
dataKeys: Object.keys(data || {}),
|
|
4921
|
+
clipTypesCount: data?.clipTypes?.length,
|
|
4922
|
+
clipTypes: data?.clipTypes
|
|
4923
|
+
});
|
|
4924
|
+
} else if (requestBody.action === "count") {
|
|
4925
|
+
console.log(`[S3ClipsSupabase] API Response for count:`, {
|
|
4926
|
+
action: requestBody.action,
|
|
4927
|
+
hasData: !!data,
|
|
4928
|
+
counts: data?.counts
|
|
4929
|
+
});
|
|
4930
|
+
}
|
|
4931
|
+
return data;
|
|
4932
|
+
}
|
|
4933
|
+
/**
|
|
4934
|
+
* Deduplicate requests to prevent multiple API calls
|
|
4935
|
+
*/
|
|
4936
|
+
async deduplicate(key, factory) {
|
|
4937
|
+
if (this.requestCache.has(key)) {
|
|
4938
|
+
console.log(`[S3ClipsSupabase] Deduplicating request: ${key}`);
|
|
4939
|
+
return this.requestCache.get(key);
|
|
4940
|
+
}
|
|
4941
|
+
const promise = factory().finally(() => {
|
|
4942
|
+
this.requestCache.delete(key);
|
|
4943
|
+
});
|
|
4944
|
+
this.requestCache.set(key, promise);
|
|
4945
|
+
return promise;
|
|
4946
|
+
}
|
|
4947
|
+
/**
|
|
4948
|
+
* Lists clips using Supabase API
|
|
4949
|
+
*/
|
|
4950
|
+
async listS3Clips(params) {
|
|
4951
|
+
const { workspaceId, date, shiftId } = params;
|
|
4952
|
+
if (!isValidShiftId(shiftId)) {
|
|
4953
|
+
console.error(`[S3ClipsSupabase] Invalid shift ID: ${shiftId}`);
|
|
4954
|
+
return [];
|
|
4955
|
+
}
|
|
4956
|
+
console.log(`[S3ClipsSupabase] Listing clips via Supabase for workspace: ${workspaceId}`);
|
|
4957
|
+
try {
|
|
4958
|
+
const response = await this.fetchWithAuth("list", {
|
|
4959
|
+
workspaceId,
|
|
4960
|
+
date,
|
|
4961
|
+
shift: shiftId,
|
|
4962
|
+
sopCategories: this.config.s3Config?.sopCategories?.default
|
|
4963
|
+
});
|
|
4964
|
+
return response.clips.map((clip) => clip.originalUri || `clips:${clip.id}`);
|
|
4965
|
+
} catch (error) {
|
|
4966
|
+
console.error("[S3ClipsSupabase] Error listing clips:", error);
|
|
4967
|
+
return [];
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
/**
|
|
4971
|
+
* Get metadata cycle time
|
|
4972
|
+
*/
|
|
4973
|
+
async getMetadataCycleTime(clipId) {
|
|
4974
|
+
const id3 = clipId.startsWith("clips:") ? clipId.substring(6) : clipId;
|
|
4975
|
+
try {
|
|
4976
|
+
console.log(`[S3ClipsSupabase] Metadata cycle time requested for clip: ${id3}`);
|
|
4977
|
+
return null;
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
console.error("[S3ClipsSupabase] Error fetching metadata cycle time:", error);
|
|
4980
|
+
return null;
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
/**
|
|
4984
|
+
* Control prefetch mode
|
|
4985
|
+
*/
|
|
4986
|
+
setPrefetchMode(enabled) {
|
|
4987
|
+
this.isPrefetching = enabled;
|
|
4988
|
+
console.log(`[S3ClipsSupabase] Prefetch mode ${enabled ? "enabled" : "disabled"}`);
|
|
4989
|
+
}
|
|
4990
|
+
/**
|
|
4991
|
+
* Get full metadata
|
|
4992
|
+
*/
|
|
4993
|
+
async getFullMetadata(clipId) {
|
|
4994
|
+
if (this.isIndexBuilding || this.isPrefetching) {
|
|
4995
|
+
console.warn("[S3ClipsSupabase] Skipping metadata - operation in progress");
|
|
4996
|
+
return null;
|
|
4997
|
+
}
|
|
4998
|
+
return null;
|
|
4999
|
+
}
|
|
5000
|
+
/**
|
|
5001
|
+
* Get clip counts with optional video index
|
|
5002
|
+
*/
|
|
5003
|
+
async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex = false) {
|
|
5004
|
+
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
5005
|
+
return this.deduplicate(cacheKey, async () => {
|
|
5006
|
+
console.log(`[S3ClipsSupabase] Fetching clip counts from Supabase for:`, {
|
|
5007
|
+
workspaceId,
|
|
5008
|
+
date,
|
|
5009
|
+
shift: shiftId
|
|
5010
|
+
});
|
|
5011
|
+
const response = await this.fetchWithAuth("count", {
|
|
5012
|
+
workspaceId,
|
|
5013
|
+
date,
|
|
5014
|
+
shift: shiftId.toString()
|
|
5015
|
+
});
|
|
5016
|
+
console.log(`[S3ClipsSupabase] Count API response:`, response);
|
|
5017
|
+
const counts = response.counts || {};
|
|
5018
|
+
console.log(`[S3ClipsSupabase] Extracted counts:`, counts);
|
|
5019
|
+
if (buildIndex) {
|
|
5020
|
+
const videoIndex = {
|
|
5021
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
5022
|
+
allVideos: [],
|
|
5023
|
+
counts,
|
|
5024
|
+
workspaceId,
|
|
5025
|
+
date,
|
|
5026
|
+
shiftId: shiftId.toString(),
|
|
5027
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
5028
|
+
_debugId: `supabase_${Date.now()}_${Math.random().toString(36).substring(7)}`
|
|
5029
|
+
};
|
|
5030
|
+
if (buildIndex) {
|
|
5031
|
+
const categories = Object.keys(counts).filter((k) => k !== "total");
|
|
5032
|
+
for (const category of categories) {
|
|
5033
|
+
if (counts[category] > 0) {
|
|
5034
|
+
const categoryResponse = await this.fetchWithAuth("list", {
|
|
5035
|
+
workspaceId,
|
|
5036
|
+
date,
|
|
5037
|
+
shift: shiftId.toString(),
|
|
5038
|
+
category,
|
|
5039
|
+
sopCategories: this.config.s3Config?.sopCategories?.default
|
|
5040
|
+
});
|
|
5041
|
+
const entries = categoryResponse.clips.map((clip) => ({
|
|
5042
|
+
uri: clip.originalUri || `clips:${clip.id}`,
|
|
5043
|
+
category,
|
|
5044
|
+
timestamp: clip.timestamp,
|
|
5045
|
+
videoId: clip.id,
|
|
5046
|
+
workspaceId,
|
|
5047
|
+
date,
|
|
5048
|
+
shiftId: shiftId.toString()
|
|
5049
|
+
}));
|
|
5050
|
+
videoIndex.byCategory.set(category, entries);
|
|
5051
|
+
videoIndex.allVideos.push(...entries);
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
return {
|
|
5056
|
+
counts,
|
|
5057
|
+
videoIndex
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
5060
|
+
return counts;
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
/**
|
|
5064
|
+
* Get clip counts (simplified version)
|
|
5065
|
+
*/
|
|
5066
|
+
async getClipCounts(workspaceId, date, shiftId) {
|
|
5067
|
+
const result = await this.getClipCountsCacheFirst(workspaceId, date, shiftId, false);
|
|
5068
|
+
if (typeof result === "object" && "counts" in result) {
|
|
5069
|
+
return result.counts;
|
|
5070
|
+
}
|
|
5071
|
+
return result;
|
|
5072
|
+
}
|
|
5073
|
+
/**
|
|
5074
|
+
* Get clip by index
|
|
5075
|
+
*/
|
|
5076
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index) {
|
|
5077
|
+
const cacheKey = `clip:${workspaceId}:${date}:${shiftId}:${category}:${index}`;
|
|
5078
|
+
return this.deduplicate(cacheKey, async () => {
|
|
5079
|
+
console.log(`[S3ClipsSupabase] Fetching clip by index from Supabase for category: ${category}, index: ${index}`);
|
|
5080
|
+
const response = await this.fetchWithAuth("by-index", {
|
|
5081
|
+
workspaceId,
|
|
5082
|
+
date,
|
|
5083
|
+
shift: shiftId.toString(),
|
|
5084
|
+
category,
|
|
5085
|
+
index,
|
|
5086
|
+
sopCategories: this.config.s3Config?.sopCategories?.default
|
|
5087
|
+
});
|
|
5088
|
+
const video = response.video;
|
|
5089
|
+
console.log("[S3ClipsSupabase] getClipByIndex response:", {
|
|
5090
|
+
hasVideo: !!video,
|
|
5091
|
+
hasSrc: !!video?.src,
|
|
5092
|
+
srcType: typeof video?.src,
|
|
5093
|
+
srcPreview: video?.src ? video.src.substring(0, 50) : "no src",
|
|
5094
|
+
isProxyUrl: video?.src?.includes("/api/clips/stream/")
|
|
5095
|
+
});
|
|
5096
|
+
return video || null;
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
5099
|
+
/**
|
|
5100
|
+
* Get first clip for all categories
|
|
5101
|
+
*/
|
|
5102
|
+
async getFirstClipsForAllCategories(workspaceId, date, shiftId) {
|
|
5103
|
+
console.log(`[S3ClipsSupabase] Getting first clips for all categories`);
|
|
5104
|
+
const counts = await this.getClipCounts(workspaceId, date, shiftId);
|
|
5105
|
+
const categories = Object.keys(counts).filter((k) => k !== "total" && counts[k] > 0);
|
|
5106
|
+
const promises = categories.map(async (category) => {
|
|
5107
|
+
const clip = await this.getClipByIndex(workspaceId, date, shiftId, category, 0);
|
|
5108
|
+
return { category, clip };
|
|
5109
|
+
});
|
|
5110
|
+
const results = await Promise.all(promises);
|
|
5111
|
+
const firstClips = {};
|
|
5112
|
+
for (const { category, clip } of results) {
|
|
5113
|
+
firstClips[category] = clip;
|
|
5114
|
+
}
|
|
5115
|
+
return firstClips;
|
|
5116
|
+
}
|
|
5117
|
+
/**
|
|
5118
|
+
* Get first clip for a specific category
|
|
5119
|
+
*/
|
|
5120
|
+
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
5121
|
+
return this.getClipByIndex(workspaceId, date, shiftId, category, 0);
|
|
5122
|
+
}
|
|
5123
|
+
/**
|
|
5124
|
+
* Batch fetch videos (alias for batchFetchClips for compatibility)
|
|
5125
|
+
*/
|
|
5126
|
+
async batchFetchVideos(workspaceId, date, shiftId, requests) {
|
|
5127
|
+
return this.batchFetchClips(workspaceId, date, shiftId, requests);
|
|
5128
|
+
}
|
|
5129
|
+
/**
|
|
5130
|
+
* Batch fetch clips
|
|
5131
|
+
*/
|
|
5132
|
+
async batchFetchClips(workspaceId, date, shiftId, requests) {
|
|
5133
|
+
const cacheKey = `batch:${workspaceId}:${date}:${shiftId}:${JSON.stringify(requests)}`;
|
|
5134
|
+
return this.deduplicate(cacheKey, async () => {
|
|
5135
|
+
console.log(`[S3ClipsSupabase] Batch fetching ${requests.length} clips from Supabase`);
|
|
5136
|
+
const response = await this.fetchWithAuth("batch", {
|
|
5137
|
+
workspaceId,
|
|
5138
|
+
date,
|
|
5139
|
+
shift: shiftId.toString(),
|
|
5140
|
+
requests,
|
|
5141
|
+
sopCategories: this.config.s3Config?.sopCategories?.default
|
|
5142
|
+
});
|
|
5143
|
+
console.log("[S3ClipsSupabase] batchFetchClips response:", {
|
|
5144
|
+
videoCount: response.videos?.length,
|
|
5145
|
+
firstVideo: response.videos?.[0],
|
|
5146
|
+
hasProxyUrls: response.videos?.[0]?.video?.src?.includes("/api/clips/stream/")
|
|
5147
|
+
});
|
|
5148
|
+
return response.videos.map((v) => v.video);
|
|
5149
|
+
});
|
|
5150
|
+
}
|
|
5151
|
+
/**
|
|
5152
|
+
* Get all clip types from Supabase
|
|
5153
|
+
*/
|
|
5154
|
+
async getClipTypes() {
|
|
5155
|
+
const cacheKey = "clip-types:all";
|
|
5156
|
+
return this.deduplicate(cacheKey, async () => {
|
|
5157
|
+
console.log(`[S3ClipsSupabase] Fetching clip types from Supabase`);
|
|
5158
|
+
const response = await this.fetchWithAuth("clip-types", {});
|
|
5159
|
+
console.log(`[S3ClipsSupabase] Fetched ${response.clipTypes?.length || 0} clip types:`, response.clipTypes);
|
|
5160
|
+
return response.clipTypes || [];
|
|
5161
|
+
});
|
|
5162
|
+
}
|
|
5163
|
+
/**
|
|
5164
|
+
* Ensure videos are loaded for navigation
|
|
5165
|
+
*/
|
|
5166
|
+
async ensureVideosLoaded(workspaceId, date, shiftId, category, currentIndex) {
|
|
5167
|
+
const rangeBefore = 1;
|
|
5168
|
+
const rangeAfter = 3;
|
|
5169
|
+
const requests = [];
|
|
5170
|
+
for (let i = Math.max(0, currentIndex - rangeBefore); i < currentIndex; i++) {
|
|
5171
|
+
requests.push({ category, index: i });
|
|
5172
|
+
}
|
|
5173
|
+
for (let i = currentIndex; i <= currentIndex + rangeAfter; i++) {
|
|
5174
|
+
requests.push({ category, index: i });
|
|
5175
|
+
}
|
|
5176
|
+
if (requests.length > 0) {
|
|
5177
|
+
await this.batchFetchClips(workspaceId, date, shiftId, requests);
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
};
|
|
5181
|
+
var S3ClipsService2 = S3ClipsSupabaseService ;
|
|
4842
5182
|
var VideoPrefetchManager = class extends events.EventEmitter {
|
|
4843
5183
|
constructor() {
|
|
4844
5184
|
super();
|
|
@@ -4867,7 +5207,7 @@ var VideoPrefetchManager = class extends events.EventEmitter {
|
|
|
4867
5207
|
getS3Service(dashboardConfig) {
|
|
4868
5208
|
const configKey = JSON.stringify(dashboardConfig.s3Config);
|
|
4869
5209
|
if (!this.s3Services.has(configKey)) {
|
|
4870
|
-
this.s3Services.set(configKey, new
|
|
5210
|
+
this.s3Services.set(configKey, new S3ClipsService2(dashboardConfig));
|
|
4871
5211
|
}
|
|
4872
5212
|
return this.s3Services.get(configKey);
|
|
4873
5213
|
}
|
|
@@ -5251,25 +5591,387 @@ if (typeof window !== "undefined") {
|
|
|
5251
5591
|
videoPrefetchManager.destroy();
|
|
5252
5592
|
});
|
|
5253
5593
|
}
|
|
5594
|
+
|
|
5595
|
+
// src/lib/services/linesService.ts
|
|
5596
|
+
var LinesService = class {
|
|
5597
|
+
constructor(supabase) {
|
|
5598
|
+
this.supabase = supabase;
|
|
5599
|
+
}
|
|
5600
|
+
/**
|
|
5601
|
+
* Fetch all active lines for a given company
|
|
5602
|
+
* @param companyId - The company ID to fetch lines for
|
|
5603
|
+
* @returns Promise<Line[]> - Array of lines for the company
|
|
5604
|
+
*/
|
|
5605
|
+
async getLinesByCompanyId(companyId) {
|
|
5606
|
+
if (!companyId) {
|
|
5607
|
+
throw new Error("Company ID is required");
|
|
5608
|
+
}
|
|
5609
|
+
const { data, error } = await this.supabase.from("lines").select("id, company_id, line_name, enable, created_at, factory_id").eq("company_id", companyId).eq("enable", true).order("line_name", { ascending: true });
|
|
5610
|
+
if (error) {
|
|
5611
|
+
console.error("Error fetching lines:", error);
|
|
5612
|
+
throw new Error(`Failed to fetch lines: ${error.message}`);
|
|
5613
|
+
}
|
|
5614
|
+
if (!data) {
|
|
5615
|
+
return [];
|
|
5616
|
+
}
|
|
5617
|
+
return data.map((line) => ({
|
|
5618
|
+
id: line.id,
|
|
5619
|
+
name: line.line_name,
|
|
5620
|
+
companyId: line.company_id,
|
|
5621
|
+
isActive: line.enable,
|
|
5622
|
+
createdAt: line.created_at,
|
|
5623
|
+
factoryId: line.factory_id
|
|
5624
|
+
}));
|
|
5625
|
+
}
|
|
5626
|
+
/**
|
|
5627
|
+
* Fetch all active lines (for all companies)
|
|
5628
|
+
* @returns Promise<Line[]> - Array of all active lines
|
|
5629
|
+
*/
|
|
5630
|
+
async getAllLines() {
|
|
5631
|
+
const { data, error } = await this.supabase.from("lines").select("id, company_id, line_name, enable, created_at, factory_id").eq("enable", true).order("line_name", { ascending: true });
|
|
5632
|
+
if (error) {
|
|
5633
|
+
console.error("Error fetching all lines:", error);
|
|
5634
|
+
throw new Error(`Failed to fetch lines: ${error.message}`);
|
|
5635
|
+
}
|
|
5636
|
+
if (!data) {
|
|
5637
|
+
return [];
|
|
5638
|
+
}
|
|
5639
|
+
return data.map((line) => ({
|
|
5640
|
+
id: line.id,
|
|
5641
|
+
name: line.line_name,
|
|
5642
|
+
companyId: line.company_id,
|
|
5643
|
+
isActive: line.enable,
|
|
5644
|
+
createdAt: line.created_at,
|
|
5645
|
+
factoryId: line.factory_id
|
|
5646
|
+
}));
|
|
5647
|
+
}
|
|
5648
|
+
/**
|
|
5649
|
+
* Fetch a single line by ID
|
|
5650
|
+
* @param lineId - The line ID to fetch
|
|
5651
|
+
* @returns Promise<Line | null> - The line data or null if not found
|
|
5652
|
+
*/
|
|
5653
|
+
async getLineById(lineId) {
|
|
5654
|
+
if (!lineId) {
|
|
5655
|
+
throw new Error("Line ID is required");
|
|
5656
|
+
}
|
|
5657
|
+
const { data, error } = await this.supabase.from("lines").select("id, company_id, line_name, enable, created_at, factory_id").eq("id", lineId).eq("enable", true).single();
|
|
5658
|
+
if (error) {
|
|
5659
|
+
if (error.code === "PGRST116") {
|
|
5660
|
+
return null;
|
|
5661
|
+
}
|
|
5662
|
+
console.error("Error fetching line:", error);
|
|
5663
|
+
throw new Error(`Failed to fetch line: ${error.message}`);
|
|
5664
|
+
}
|
|
5665
|
+
if (!data) {
|
|
5666
|
+
return null;
|
|
5667
|
+
}
|
|
5668
|
+
return {
|
|
5669
|
+
id: data.id,
|
|
5670
|
+
name: data.line_name,
|
|
5671
|
+
companyId: data.company_id,
|
|
5672
|
+
isActive: data.enable,
|
|
5673
|
+
createdAt: data.created_at,
|
|
5674
|
+
factoryId: data.factory_id
|
|
5675
|
+
};
|
|
5676
|
+
}
|
|
5677
|
+
};
|
|
5678
|
+
var createLinesService = (supabase) => {
|
|
5679
|
+
return new LinesService(supabase);
|
|
5680
|
+
};
|
|
5681
|
+
var linesService = {
|
|
5682
|
+
create: createLinesService
|
|
5683
|
+
};
|
|
5684
|
+
|
|
5685
|
+
// src/lib/services/userService.ts
|
|
5686
|
+
var UserService = class {
|
|
5687
|
+
constructor(supabase) {
|
|
5688
|
+
this.supabase = supabase;
|
|
5689
|
+
}
|
|
5690
|
+
/**
|
|
5691
|
+
* Fetch all users mapped to a specific company
|
|
5692
|
+
* @param companyId - The company ID to fetch users for
|
|
5693
|
+
* @returns Promise<CompanyUser[]> - Array of users in the company
|
|
5694
|
+
*/
|
|
5695
|
+
async getUsersByCompanyId(companyId) {
|
|
5696
|
+
if (!companyId) {
|
|
5697
|
+
throw new Error("Company ID is required");
|
|
5698
|
+
}
|
|
5699
|
+
try {
|
|
5700
|
+
console.log(`[UserService] getUsersByCompanyId called with companyId: ${companyId}`);
|
|
5701
|
+
const result = await this.getUsersByCompanyIdFallback(companyId);
|
|
5702
|
+
console.log(`[UserService] getUsersByCompanyId returning ${result.length} users:`, result.map((u) => ({ id: u.id, name: u.name })));
|
|
5703
|
+
return result;
|
|
5704
|
+
} catch (error) {
|
|
5705
|
+
console.error("Error in getUsersByCompanyId:", error);
|
|
5706
|
+
throw error;
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
/**
|
|
5710
|
+
* Fallback method to fetch users by company ID using basic queries
|
|
5711
|
+
* Gets real user emails from the database
|
|
5712
|
+
* @param companyId - The company ID to fetch users for
|
|
5713
|
+
* @returns Promise<CompanyUser[]> - Array of users in the company
|
|
5714
|
+
*/
|
|
5715
|
+
async getUsersByCompanyIdFallback(companyId) {
|
|
5716
|
+
try {
|
|
5717
|
+
console.log(`[UserService] About to query user_company_mapping with companyId: ${companyId}`);
|
|
5718
|
+
const { data: mappings, error: mappingError } = await this.supabase.from("user_company_mapping").select("user_id, created_at, updated_at").eq("company_id", companyId);
|
|
5719
|
+
console.log(`[UserService] Supabase query result:`, {
|
|
5720
|
+
data: mappings,
|
|
5721
|
+
error: mappingError,
|
|
5722
|
+
dataLength: mappings?.length || 0
|
|
5723
|
+
});
|
|
5724
|
+
if (mappingError) {
|
|
5725
|
+
console.error("Error fetching user company mappings:", mappingError);
|
|
5726
|
+
throw new Error(`Failed to fetch user mappings: ${mappingError.message}`);
|
|
5727
|
+
}
|
|
5728
|
+
if (!mappings || mappings.length === 0) {
|
|
5729
|
+
console.log(`[UserService] No mappings found for companyId: ${companyId}`);
|
|
5730
|
+
return [];
|
|
5731
|
+
}
|
|
5732
|
+
console.log(`[UserService] Found ${mappings.length} mappings for companyId: ${companyId}`);
|
|
5733
|
+
console.log(`[UserService] Raw mappings:`, mappings.map((m) => m.user_id));
|
|
5734
|
+
const users = mappings.map((mapping) => {
|
|
5735
|
+
const shortId = mapping.user_id.substring(0, 8);
|
|
5736
|
+
return {
|
|
5737
|
+
id: mapping.user_id,
|
|
5738
|
+
name: mapping.user_id,
|
|
5739
|
+
// Show the full user ID
|
|
5740
|
+
email: `${shortId}@company.com`,
|
|
5741
|
+
// Simple email format
|
|
5742
|
+
isActive: true,
|
|
5743
|
+
createdAt: mapping.created_at,
|
|
5744
|
+
updatedAt: mapping.updated_at
|
|
5745
|
+
};
|
|
5746
|
+
});
|
|
5747
|
+
console.log(`[UserService] Created ${users.length} user objects`);
|
|
5748
|
+
return users;
|
|
5749
|
+
} catch (error) {
|
|
5750
|
+
console.error("Error in getUsersByCompanyIdFallback:", error);
|
|
5751
|
+
throw error;
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
/**
|
|
5755
|
+
* Check if a user is mapped to a specific company
|
|
5756
|
+
* @param userId - The user ID to check
|
|
5757
|
+
* @param companyId - The company ID to check against
|
|
5758
|
+
* @returns Promise<boolean> - Whether the user is mapped to the company
|
|
5759
|
+
*/
|
|
5760
|
+
async isUserInCompany(userId, companyId) {
|
|
5761
|
+
if (!userId || !companyId) {
|
|
5762
|
+
return false;
|
|
5763
|
+
}
|
|
5764
|
+
try {
|
|
5765
|
+
const { data, error } = await this.supabase.from("user_company_mapping").select("id").eq("user_id", userId).eq("company_id", companyId).single();
|
|
5766
|
+
if (error) {
|
|
5767
|
+
if (error.code === "PGRST116") {
|
|
5768
|
+
return false;
|
|
5769
|
+
}
|
|
5770
|
+
console.error("Error checking user company mapping:", error);
|
|
5771
|
+
return false;
|
|
5772
|
+
}
|
|
5773
|
+
return !!data;
|
|
5774
|
+
} catch (error) {
|
|
5775
|
+
console.error("Error in isUserInCompany:", error);
|
|
5776
|
+
return false;
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
/**
|
|
5780
|
+
* Get user details by user ID (if they exist in auth.users)
|
|
5781
|
+
* @param userId - The user ID to fetch
|
|
5782
|
+
* @returns Promise<CompanyUser | null> - The user data or null if not found
|
|
5783
|
+
*/
|
|
5784
|
+
async getUserById(userId) {
|
|
5785
|
+
if (!userId) {
|
|
5786
|
+
return null;
|
|
5787
|
+
}
|
|
5788
|
+
try {
|
|
5789
|
+
const { data, error } = await this.supabase.from("users").select("id, email, raw_user_meta_data").eq("id", userId).single();
|
|
5790
|
+
if (error) {
|
|
5791
|
+
if (error.code === "PGRST116") {
|
|
5792
|
+
return null;
|
|
5793
|
+
}
|
|
5794
|
+
console.error("Error fetching user by ID:", error);
|
|
5795
|
+
return null;
|
|
5796
|
+
}
|
|
5797
|
+
if (!data) {
|
|
5798
|
+
return null;
|
|
5799
|
+
}
|
|
5800
|
+
const fullName = data.raw_user_meta_data?.full_name || data.raw_user_meta_data?.name || data.email?.split("@")[0];
|
|
5801
|
+
return {
|
|
5802
|
+
id: data.id,
|
|
5803
|
+
name: fullName,
|
|
5804
|
+
email: data.email,
|
|
5805
|
+
isActive: true,
|
|
5806
|
+
createdAt: void 0,
|
|
5807
|
+
updatedAt: void 0
|
|
5808
|
+
};
|
|
5809
|
+
} catch (error) {
|
|
5810
|
+
console.error("Error in getUserById:", error);
|
|
5811
|
+
return null;
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
};
|
|
5815
|
+
var createUserService = (supabase) => {
|
|
5816
|
+
return new UserService(supabase);
|
|
5817
|
+
};
|
|
5818
|
+
var userService = {
|
|
5819
|
+
create: createUserService
|
|
5820
|
+
};
|
|
5821
|
+
|
|
5822
|
+
// src/lib/services/supervisorService.ts
|
|
5823
|
+
var SupervisorService = class {
|
|
5824
|
+
constructor(supabase) {
|
|
5825
|
+
this.supabase = supabase;
|
|
5826
|
+
this.linesService = new LinesService(supabase);
|
|
5827
|
+
this.userService = new UserService(supabase);
|
|
5828
|
+
}
|
|
5829
|
+
/**
|
|
5830
|
+
* Get supervisor management data for a specific company
|
|
5831
|
+
* Uses real lines data and real user data from the database
|
|
5832
|
+
* @param companyId - The company ID to fetch data for
|
|
5833
|
+
* @returns Promise<SupervisorManagementData>
|
|
5834
|
+
*/
|
|
5835
|
+
async getSupervisorManagementData(companyId) {
|
|
5836
|
+
try {
|
|
5837
|
+
const lines = await this.linesService.getLinesByCompanyId(companyId);
|
|
5838
|
+
console.log(`[SupervisorService] Fetching users for companyId: ${companyId}`);
|
|
5839
|
+
const companyUsers = await this.userService.getUsersByCompanyId(companyId);
|
|
5840
|
+
console.log(`[SupervisorService] Received ${companyUsers.length} users from UserService`);
|
|
5841
|
+
const availableSupervisors = companyUsers.map((user) => ({
|
|
5842
|
+
id: user.id,
|
|
5843
|
+
name: user.name,
|
|
5844
|
+
email: user.email,
|
|
5845
|
+
isActive: user.isActive,
|
|
5846
|
+
createdAt: user.createdAt,
|
|
5847
|
+
updatedAt: user.updatedAt
|
|
5848
|
+
}));
|
|
5849
|
+
console.log(`[SupervisorService] Created ${availableSupervisors.length} available supervisors`);
|
|
5850
|
+
const assignments = lines.map((line) => {
|
|
5851
|
+
const currentSupervisor = this.getRandomSupervisorForLine(line.id, availableSupervisors);
|
|
5852
|
+
const assignment = {
|
|
5853
|
+
lineId: line.id,
|
|
5854
|
+
lineName: line.name,
|
|
5855
|
+
currentSupervisor,
|
|
5856
|
+
availableSupervisors
|
|
5857
|
+
};
|
|
5858
|
+
console.log(`[SupervisorService] Assignment for line ${line.name}: ${assignment.availableSupervisors.length} available supervisors`);
|
|
5859
|
+
return assignment;
|
|
5860
|
+
});
|
|
5861
|
+
console.log(`[SupervisorService] Final result: ${assignments.length} assignments, ${availableSupervisors.length} total supervisors`);
|
|
5862
|
+
return {
|
|
5863
|
+
assignments,
|
|
5864
|
+
allSupervisors: availableSupervisors,
|
|
5865
|
+
loading: false,
|
|
5866
|
+
error: void 0
|
|
5867
|
+
};
|
|
5868
|
+
} catch (error) {
|
|
5869
|
+
console.error("Error fetching supervisor management data:", error);
|
|
5870
|
+
return {
|
|
5871
|
+
assignments: [],
|
|
5872
|
+
allSupervisors: [],
|
|
5873
|
+
loading: false,
|
|
5874
|
+
error: error instanceof Error ? error.message : "Failed to fetch supervisor management data"
|
|
5875
|
+
};
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
/**
|
|
5879
|
+
* Get all supervisors for a specific company (real data from database)
|
|
5880
|
+
* @param companyId - The company ID to fetch supervisors for
|
|
5881
|
+
* @returns Promise<Supervisor[]>
|
|
5882
|
+
*/
|
|
5883
|
+
async getAllSupervisors(companyId) {
|
|
5884
|
+
try {
|
|
5885
|
+
const companyUsers = await this.userService.getUsersByCompanyId(companyId);
|
|
5886
|
+
return companyUsers.map((user) => ({
|
|
5887
|
+
id: user.id,
|
|
5888
|
+
name: user.name,
|
|
5889
|
+
email: user.email,
|
|
5890
|
+
isActive: user.isActive,
|
|
5891
|
+
createdAt: user.createdAt,
|
|
5892
|
+
updatedAt: user.updatedAt
|
|
5893
|
+
}));
|
|
5894
|
+
} catch (error) {
|
|
5895
|
+
console.error("Error fetching all supervisors:", error);
|
|
5896
|
+
return [];
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
/**
|
|
5900
|
+
* Get active supervisors only for a specific company
|
|
5901
|
+
* @param companyId - The company ID to fetch active supervisors for
|
|
5902
|
+
* @returns Promise<Supervisor[]>
|
|
5903
|
+
*/
|
|
5904
|
+
async getActiveSupervisors(companyId) {
|
|
5905
|
+
const allSupervisors = await this.getAllSupervisors(companyId);
|
|
5906
|
+
return allSupervisors.filter((sup) => sup.isActive);
|
|
5907
|
+
}
|
|
5908
|
+
/**
|
|
5909
|
+
* Assign a supervisor to a line
|
|
5910
|
+
* This is a mock implementation - in production, this would update the database
|
|
5911
|
+
* @param lineId - The line ID
|
|
5912
|
+
* @param supervisorId - The supervisor ID to assign
|
|
5913
|
+
* @returns Promise<boolean> - Success status
|
|
5914
|
+
*/
|
|
5915
|
+
async assignSupervisorToLine(lineId, supervisorId) {
|
|
5916
|
+
try {
|
|
5917
|
+
console.log(`Assigning supervisor ${supervisorId} to line ${lineId}`);
|
|
5918
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
5919
|
+
return true;
|
|
5920
|
+
} catch (error) {
|
|
5921
|
+
console.error("Error assigning supervisor to line:", error);
|
|
5922
|
+
return false;
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
/**
|
|
5926
|
+
* Helper method to simulate supervisor assignments
|
|
5927
|
+
* In production, this would be fetched from the database
|
|
5928
|
+
* @param lineId - The line ID
|
|
5929
|
+
* @param supervisors - Available supervisors
|
|
5930
|
+
* @returns Supervisor or undefined
|
|
5931
|
+
*/
|
|
5932
|
+
getRandomSupervisorForLine(lineId, supervisors) {
|
|
5933
|
+
const hash = lineId.split("").reduce((a, b) => {
|
|
5934
|
+
a = (a << 5) - a + b.charCodeAt(0);
|
|
5935
|
+
return a & a;
|
|
5936
|
+
}, 0);
|
|
5937
|
+
const index = Math.abs(hash) % (supervisors.length + 1);
|
|
5938
|
+
return index < supervisors.length ? supervisors[index] : void 0;
|
|
5939
|
+
}
|
|
5940
|
+
};
|
|
5941
|
+
var createSupervisorService = (supabase) => {
|
|
5942
|
+
return new SupervisorService(supabase);
|
|
5943
|
+
};
|
|
5944
|
+
var simulateApiDelay = (ms = 1e3) => {
|
|
5945
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5946
|
+
};
|
|
5254
5947
|
var AuthContext = React19.createContext({
|
|
5255
5948
|
session: null,
|
|
5256
5949
|
user: null,
|
|
5257
5950
|
loading: true,
|
|
5258
5951
|
error: null,
|
|
5259
5952
|
signOut: async () => {
|
|
5953
|
+
},
|
|
5954
|
+
markFirstLoginCompleted: async () => false,
|
|
5955
|
+
showOnboarding: false,
|
|
5956
|
+
setShowOnboarding: () => {
|
|
5957
|
+
},
|
|
5958
|
+
completeOnboarding: async () => {
|
|
5260
5959
|
}
|
|
5261
5960
|
});
|
|
5262
5961
|
var useAuth = () => React19.useContext(AuthContext);
|
|
5263
5962
|
var AuthProvider = ({ children }) => {
|
|
5264
5963
|
const supabase = useSupabase();
|
|
5265
5964
|
const { authConfig } = useDashboardConfig();
|
|
5965
|
+
const entityConfig = useEntityConfig();
|
|
5266
5966
|
const [session, setSession] = React19.useState(null);
|
|
5267
5967
|
const [user, setUser] = React19.useState(null);
|
|
5268
5968
|
const [loading, setLoading] = React19.useState(true);
|
|
5269
5969
|
const [error, setError] = React19.useState(null);
|
|
5970
|
+
const [showOnboarding, setShowOnboarding] = React19.useState(false);
|
|
5270
5971
|
const router$1 = router.useRouter();
|
|
5271
5972
|
authConfig?.userProfileTable;
|
|
5272
5973
|
authConfig?.roleColumn || "role";
|
|
5974
|
+
const dashboardCompanyId = entityConfig?.companyId;
|
|
5273
5975
|
const fetchUserDetails = React19.useCallback(async (supabaseUser) => {
|
|
5274
5976
|
console.log("[fetchUserDetails] Called for user:", supabaseUser.id, {
|
|
5275
5977
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5288,29 +5990,66 @@ var AuthProvider = ({ children }) => {
|
|
|
5288
5990
|
reject(new Error("Profile fetch timeout"));
|
|
5289
5991
|
}, 2e3)
|
|
5290
5992
|
);
|
|
5291
|
-
const rolePromise =
|
|
5292
|
-
|
|
5293
|
-
|
|
5993
|
+
const [rolePromise, companyPromise, onboardingPromise] = [
|
|
5994
|
+
supabase.from("user_roles").select("role_level").eq("user_id", supabaseUser.id).single(),
|
|
5995
|
+
supabase.from("user_company_mapping").select("company_id").eq("user_id", supabaseUser.id).single(),
|
|
5996
|
+
supabase.from("user_onboarding_tracking").select("first_login_completed").eq("user_id", supabaseUser.id).single()
|
|
5997
|
+
];
|
|
5998
|
+
const [roleResult, companyResult, onboardingResult] = await Promise.race([
|
|
5999
|
+
Promise.all([rolePromise, companyPromise, onboardingPromise]),
|
|
5294
6000
|
timeoutPromise
|
|
5295
|
-
// Fixed: removed .then() which was causing the bug
|
|
5296
6001
|
]);
|
|
5297
6002
|
let roleLevel = void 0;
|
|
5298
6003
|
if (roleResult && !roleResult.error && roleResult.data) {
|
|
5299
6004
|
roleLevel = roleResult.data.role_level;
|
|
5300
6005
|
} else if (roleResult?.error && roleResult.error.code !== "PGRST116") {
|
|
5301
|
-
console.log("Error fetching
|
|
6006
|
+
console.log("Error fetching user_roles data:", roleResult.error.message);
|
|
6007
|
+
}
|
|
6008
|
+
let firstLoginCompleted = false;
|
|
6009
|
+
if (onboardingResult && !onboardingResult.error && onboardingResult.data) {
|
|
6010
|
+
firstLoginCompleted = onboardingResult.data.first_login_completed ?? false;
|
|
6011
|
+
} else if (onboardingResult?.error && onboardingResult.error.code === "PGRST116") {
|
|
6012
|
+
console.log("[fetchUserDetails] Creating onboarding record for new user");
|
|
6013
|
+
const { error: insertError } = await supabase.from("user_onboarding_tracking").insert({
|
|
6014
|
+
user_id: supabaseUser.id,
|
|
6015
|
+
first_login_completed: false
|
|
6016
|
+
});
|
|
6017
|
+
if (insertError) {
|
|
6018
|
+
console.log("Error creating onboarding record:", insertError.message);
|
|
6019
|
+
}
|
|
6020
|
+
} else if (onboardingResult?.error) {
|
|
6021
|
+
console.log("Error fetching onboarding data:", onboardingResult.error.message);
|
|
6022
|
+
}
|
|
6023
|
+
let userCompanyId = void 0;
|
|
6024
|
+
if (companyResult && !companyResult.error && companyResult.data) {
|
|
6025
|
+
userCompanyId = companyResult.data.company_id;
|
|
6026
|
+
} else if (companyResult?.error && companyResult.error.code !== "PGRST116") {
|
|
6027
|
+
console.log("Error fetching company_id:", companyResult.error.message);
|
|
6028
|
+
}
|
|
6029
|
+
if (dashboardCompanyId && userCompanyId && userCompanyId !== dashboardCompanyId) {
|
|
6030
|
+
console.warn("[Auth] Company access denied:", {
|
|
6031
|
+
userCompanyId,
|
|
6032
|
+
dashboardCompanyId,
|
|
6033
|
+
userEmail: supabaseUser.email
|
|
6034
|
+
});
|
|
6035
|
+
throw new Error("COMPANY_ACCESS_DENIED");
|
|
5302
6036
|
}
|
|
5303
6037
|
return {
|
|
5304
6038
|
...basicUser,
|
|
5305
|
-
role_level: roleLevel
|
|
6039
|
+
role_level: roleLevel,
|
|
6040
|
+
company_id: userCompanyId,
|
|
6041
|
+
first_login_completed: firstLoginCompleted
|
|
5306
6042
|
};
|
|
5307
6043
|
} catch (err) {
|
|
6044
|
+
if (err instanceof Error && err.message === "COMPANY_ACCESS_DENIED") {
|
|
6045
|
+
throw err;
|
|
6046
|
+
}
|
|
5308
6047
|
if (err instanceof Error && err.message.includes("timeout")) {
|
|
5309
6048
|
console.warn("Auth fetch timeout - using basic user info");
|
|
5310
6049
|
}
|
|
5311
6050
|
return basicUser;
|
|
5312
6051
|
}
|
|
5313
|
-
}, [supabase]);
|
|
6052
|
+
}, [supabase, dashboardCompanyId]);
|
|
5314
6053
|
React19.useEffect(() => {
|
|
5315
6054
|
if (!supabase) return;
|
|
5316
6055
|
let mounted = true;
|
|
@@ -5351,6 +6090,13 @@ var AuthProvider = ({ children }) => {
|
|
|
5351
6090
|
} catch (err) {
|
|
5352
6091
|
console.error("Error fetching user details during init:", err);
|
|
5353
6092
|
if (mounted) {
|
|
6093
|
+
if (err instanceof Error && err.message === "COMPANY_ACCESS_DENIED") {
|
|
6094
|
+
setError(new Error("You do not have access to this dashboard. Please contact your administrator."));
|
|
6095
|
+
await supabase.auth.signOut();
|
|
6096
|
+
setSession(null);
|
|
6097
|
+
setUser(null);
|
|
6098
|
+
return;
|
|
6099
|
+
}
|
|
5354
6100
|
setUser({
|
|
5355
6101
|
id: initialSession.user.id,
|
|
5356
6102
|
email: initialSession.user.email
|
|
@@ -5431,6 +6177,73 @@ var AuthProvider = ({ children }) => {
|
|
|
5431
6177
|
subscription?.unsubscribe();
|
|
5432
6178
|
};
|
|
5433
6179
|
}, [supabase, fetchUserDetails]);
|
|
6180
|
+
const markFirstLoginCompleted = React19.useCallback(async () => {
|
|
6181
|
+
console.log("[markFirstLoginCompleted] Starting update for user:", user?.id);
|
|
6182
|
+
if (!supabase || !user?.id) {
|
|
6183
|
+
console.error("[markFirstLoginCompleted] Missing requirements:", {
|
|
6184
|
+
hasSupabase: !!supabase,
|
|
6185
|
+
userId: user?.id
|
|
6186
|
+
});
|
|
6187
|
+
return false;
|
|
6188
|
+
}
|
|
6189
|
+
try {
|
|
6190
|
+
console.log("[markFirstLoginCompleted] Updating onboarding tracking for user_id:", user.id);
|
|
6191
|
+
const { data: existingData, error: checkError } = await supabase.from("user_onboarding_tracking").select("id").eq("user_id", user.id).single();
|
|
6192
|
+
let data, error2;
|
|
6193
|
+
if (checkError && checkError.code === "PGRST116") {
|
|
6194
|
+
console.log("[markFirstLoginCompleted] Creating onboarding record with completed status");
|
|
6195
|
+
const result = await supabase.from("user_onboarding_tracking").insert({
|
|
6196
|
+
user_id: user.id,
|
|
6197
|
+
first_login_completed: true
|
|
6198
|
+
}).select();
|
|
6199
|
+
data = result.data;
|
|
6200
|
+
error2 = result.error;
|
|
6201
|
+
} else {
|
|
6202
|
+
console.log("[markFirstLoginCompleted] Updating existing onboarding record");
|
|
6203
|
+
const result = await supabase.from("user_onboarding_tracking").update({
|
|
6204
|
+
first_login_completed: true,
|
|
6205
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6206
|
+
}).eq("user_id", user.id).select();
|
|
6207
|
+
data = result.data;
|
|
6208
|
+
error2 = result.error;
|
|
6209
|
+
}
|
|
6210
|
+
if (error2) {
|
|
6211
|
+
console.error("[markFirstLoginCompleted] Database update error:", {
|
|
6212
|
+
error: error2,
|
|
6213
|
+
message: error2.message,
|
|
6214
|
+
details: error2.details,
|
|
6215
|
+
hint: error2.hint,
|
|
6216
|
+
code: error2.code
|
|
6217
|
+
});
|
|
6218
|
+
return false;
|
|
6219
|
+
}
|
|
6220
|
+
console.log("[markFirstLoginCompleted] Database update successful:", data);
|
|
6221
|
+
setUser((prevUser) => {
|
|
6222
|
+
if (!prevUser) return prevUser;
|
|
6223
|
+
const updatedUser = {
|
|
6224
|
+
...prevUser,
|
|
6225
|
+
first_login_completed: true
|
|
6226
|
+
};
|
|
6227
|
+
console.log("[markFirstLoginCompleted] Local state updated:", updatedUser);
|
|
6228
|
+
return updatedUser;
|
|
6229
|
+
});
|
|
6230
|
+
console.log("[markFirstLoginCompleted] Successfully marked first login as completed");
|
|
6231
|
+
return true;
|
|
6232
|
+
} catch (err) {
|
|
6233
|
+
console.error("[markFirstLoginCompleted] Unexpected error:", err);
|
|
6234
|
+
return false;
|
|
6235
|
+
}
|
|
6236
|
+
}, [supabase, user?.id]);
|
|
6237
|
+
const completeOnboarding = React19.useCallback(async () => {
|
|
6238
|
+
console.log("[completeOnboarding] Completing onboarding tour");
|
|
6239
|
+
const success = await markFirstLoginCompleted();
|
|
6240
|
+
if (success) {
|
|
6241
|
+
setShowOnboarding(false);
|
|
6242
|
+
console.log("[completeOnboarding] Onboarding completed successfully");
|
|
6243
|
+
} else {
|
|
6244
|
+
console.error("[completeOnboarding] Failed to mark first login as completed");
|
|
6245
|
+
}
|
|
6246
|
+
}, [markFirstLoginCompleted]);
|
|
5434
6247
|
const signOut = async () => {
|
|
5435
6248
|
if (!supabase) return;
|
|
5436
6249
|
setLoading(true);
|
|
@@ -5441,7 +6254,17 @@ var AuthProvider = ({ children }) => {
|
|
|
5441
6254
|
router$1.replace(logoutRedirectPath);
|
|
5442
6255
|
}
|
|
5443
6256
|
};
|
|
5444
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value: {
|
|
6257
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value: {
|
|
6258
|
+
session,
|
|
6259
|
+
user,
|
|
6260
|
+
loading,
|
|
6261
|
+
error,
|
|
6262
|
+
signOut,
|
|
6263
|
+
markFirstLoginCompleted,
|
|
6264
|
+
showOnboarding,
|
|
6265
|
+
setShowOnboarding,
|
|
6266
|
+
completeOnboarding
|
|
6267
|
+
}, children });
|
|
5445
6268
|
};
|
|
5446
6269
|
var defaultContextValue = {
|
|
5447
6270
|
components: {},
|
|
@@ -8924,7 +9747,7 @@ var useSKUs = (companyId) => {
|
|
|
8924
9747
|
// src/lib/hooks/useCanSaveTargets.ts
|
|
8925
9748
|
var useCanSaveTargets = () => {
|
|
8926
9749
|
const { user } = useAuth();
|
|
8927
|
-
return
|
|
9750
|
+
return true;
|
|
8928
9751
|
};
|
|
8929
9752
|
function useTicketHistory(companyId) {
|
|
8930
9753
|
const [tickets, setTickets] = React19.useState([]);
|
|
@@ -12498,6 +13321,100 @@ var useHourlyTargetMisses = ({
|
|
|
12498
13321
|
clearMiss
|
|
12499
13322
|
};
|
|
12500
13323
|
};
|
|
13324
|
+
function useClipTypes() {
|
|
13325
|
+
const [clipTypes, setClipTypes] = React19.useState([]);
|
|
13326
|
+
const [isLoading, setIsLoading] = React19.useState(true);
|
|
13327
|
+
const [error, setError] = React19.useState(null);
|
|
13328
|
+
const dashboardConfig = useDashboardConfig();
|
|
13329
|
+
const s3Service = dashboardConfig?.s3Config ? videoPrefetchManager.getS3Service(dashboardConfig) : null;
|
|
13330
|
+
const fetchClipTypes = React19.useCallback(async () => {
|
|
13331
|
+
console.log("[useClipTypes] Starting fetchClipTypes, s3Service:", !!s3Service);
|
|
13332
|
+
if (!s3Service) {
|
|
13333
|
+
console.log("[useClipTypes] S3 service not initialized");
|
|
13334
|
+
setError("S3 service not initialized");
|
|
13335
|
+
setIsLoading(false);
|
|
13336
|
+
return;
|
|
13337
|
+
}
|
|
13338
|
+
setIsLoading(true);
|
|
13339
|
+
setError(null);
|
|
13340
|
+
try {
|
|
13341
|
+
console.log("[useClipTypes] Fetching clip types from S3Service...");
|
|
13342
|
+
const types = await s3Service.getClipTypes();
|
|
13343
|
+
console.log(`[useClipTypes] Fetched ${types?.length || 0} clip types:`, types);
|
|
13344
|
+
setClipTypes(types || []);
|
|
13345
|
+
} catch (err) {
|
|
13346
|
+
console.error("[useClipTypes] Error fetching clip types:", err);
|
|
13347
|
+
setError("Failed to load clip types");
|
|
13348
|
+
const fallbackTypes = [
|
|
13349
|
+
{ id: "idle_time", type: "idle_time", label: "Low Value Moments", color: "purple", description: "Idle time activities" },
|
|
13350
|
+
{ id: "best_cycle_time", type: "best_cycle_time", label: "Best Cycle Time", color: "green", description: "Fastest cycle today" },
|
|
13351
|
+
{ id: "worst_cycle_time", type: "worst_cycle_time", label: "Worst Cycle Time", color: "red", description: "Slowest cycle today" },
|
|
13352
|
+
{ id: "long_cycle_time", type: "long_cycle_time", label: "Long Cycle Time", color: "orange", description: "Above standard cycle times" }
|
|
13353
|
+
];
|
|
13354
|
+
console.log("[useClipTypes] Using fallback clip types:", fallbackTypes);
|
|
13355
|
+
setClipTypes(fallbackTypes);
|
|
13356
|
+
} finally {
|
|
13357
|
+
setIsLoading(false);
|
|
13358
|
+
}
|
|
13359
|
+
}, [s3Service]);
|
|
13360
|
+
React19.useEffect(() => {
|
|
13361
|
+
fetchClipTypes();
|
|
13362
|
+
}, [fetchClipTypes]);
|
|
13363
|
+
return {
|
|
13364
|
+
clipTypes,
|
|
13365
|
+
isLoading,
|
|
13366
|
+
error,
|
|
13367
|
+
refresh: fetchClipTypes
|
|
13368
|
+
};
|
|
13369
|
+
}
|
|
13370
|
+
function useClipTypesWithCounts(workspaceId, date, shiftId) {
|
|
13371
|
+
const { clipTypes, isLoading: typesLoading, error: typesError, refresh } = useClipTypes();
|
|
13372
|
+
const [counts, setCounts] = React19.useState({});
|
|
13373
|
+
const [countsLoading, setCountsLoading] = React19.useState(false);
|
|
13374
|
+
const dashboardConfig = useDashboardConfig();
|
|
13375
|
+
const s3Service = dashboardConfig?.s3Config ? videoPrefetchManager.getS3Service(dashboardConfig) : null;
|
|
13376
|
+
React19.useEffect(() => {
|
|
13377
|
+
console.log("[useClipTypesWithCounts] Dependencies:", {
|
|
13378
|
+
s3Service: !!s3Service,
|
|
13379
|
+
workspaceId,
|
|
13380
|
+
date,
|
|
13381
|
+
shiftId,
|
|
13382
|
+
shiftIdType: typeof shiftId
|
|
13383
|
+
});
|
|
13384
|
+
if (!s3Service || !workspaceId || !date || shiftId === void 0) {
|
|
13385
|
+
console.log("[useClipTypesWithCounts] Skipping counts fetch - missing dependencies");
|
|
13386
|
+
return;
|
|
13387
|
+
}
|
|
13388
|
+
const fetchCounts = async () => {
|
|
13389
|
+
setCountsLoading(true);
|
|
13390
|
+
try {
|
|
13391
|
+
console.log("[useClipTypesWithCounts] Fetching counts...");
|
|
13392
|
+
const clipCounts = await s3Service.getClipCounts(
|
|
13393
|
+
workspaceId,
|
|
13394
|
+
date,
|
|
13395
|
+
shiftId.toString()
|
|
13396
|
+
);
|
|
13397
|
+
console.log("[useClipTypesWithCounts] Received counts:", clipCounts);
|
|
13398
|
+
setCounts(clipCounts);
|
|
13399
|
+
} catch (err) {
|
|
13400
|
+
console.error("[useClipTypesWithCounts] Error fetching counts:", err);
|
|
13401
|
+
} finally {
|
|
13402
|
+
setCountsLoading(false);
|
|
13403
|
+
}
|
|
13404
|
+
};
|
|
13405
|
+
fetchCounts();
|
|
13406
|
+
}, [s3Service, workspaceId, date, shiftId]);
|
|
13407
|
+
return {
|
|
13408
|
+
clipTypes: clipTypes.map((type) => ({
|
|
13409
|
+
...type,
|
|
13410
|
+
count: counts[type.type] || 0
|
|
13411
|
+
})),
|
|
13412
|
+
isLoading: typesLoading || countsLoading,
|
|
13413
|
+
error: typesError,
|
|
13414
|
+
refresh,
|
|
13415
|
+
counts
|
|
13416
|
+
};
|
|
13417
|
+
}
|
|
12501
13418
|
var MAX_RETRIES = 10;
|
|
12502
13419
|
var RETRY_DELAY = 500;
|
|
12503
13420
|
function useNavigation(customNavigate) {
|
|
@@ -12842,6 +13759,57 @@ var useFormatNumber = () => {
|
|
|
12842
13759
|
);
|
|
12843
13760
|
return { formatNumber };
|
|
12844
13761
|
};
|
|
13762
|
+
function useAccessControl() {
|
|
13763
|
+
const { user } = useAuth();
|
|
13764
|
+
const userRole = React19.useMemo(() => {
|
|
13765
|
+
if (!user?.role_level) return null;
|
|
13766
|
+
const roleLevel = user.role_level;
|
|
13767
|
+
if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor") {
|
|
13768
|
+
return roleLevel;
|
|
13769
|
+
}
|
|
13770
|
+
return "supervisor";
|
|
13771
|
+
}, [user?.role_level]);
|
|
13772
|
+
const allPages = [
|
|
13773
|
+
"/",
|
|
13774
|
+
"/leaderboard",
|
|
13775
|
+
"/kpis",
|
|
13776
|
+
"/targets",
|
|
13777
|
+
"/shifts",
|
|
13778
|
+
"/supervisor-management",
|
|
13779
|
+
"/skus",
|
|
13780
|
+
"/ai-agent",
|
|
13781
|
+
"/help",
|
|
13782
|
+
"/health",
|
|
13783
|
+
"/profile",
|
|
13784
|
+
"/workspace",
|
|
13785
|
+
"/factory-view"
|
|
13786
|
+
];
|
|
13787
|
+
const accessiblePages = React19.useMemo(() => {
|
|
13788
|
+
return allPages;
|
|
13789
|
+
}, []);
|
|
13790
|
+
const hasAccess = React19.useMemo(() => {
|
|
13791
|
+
return (path) => {
|
|
13792
|
+
return true;
|
|
13793
|
+
};
|
|
13794
|
+
}, []);
|
|
13795
|
+
const isPageVisible = React19.useMemo(() => {
|
|
13796
|
+
return (path) => {
|
|
13797
|
+
return true;
|
|
13798
|
+
};
|
|
13799
|
+
}, []);
|
|
13800
|
+
const canAccessPage = React19.useMemo(() => {
|
|
13801
|
+
return (path) => {
|
|
13802
|
+
return true;
|
|
13803
|
+
};
|
|
13804
|
+
}, []);
|
|
13805
|
+
return {
|
|
13806
|
+
userRole,
|
|
13807
|
+
hasAccess,
|
|
13808
|
+
accessiblePages,
|
|
13809
|
+
isPageVisible,
|
|
13810
|
+
canAccessPage
|
|
13811
|
+
};
|
|
13812
|
+
}
|
|
12845
13813
|
function useSupabaseClient() {
|
|
12846
13814
|
const { supabaseUrl, supabaseKey } = useDashboardConfig();
|
|
12847
13815
|
const supabase = React19.useMemo(() => supabaseJs.createClient(supabaseUrl, supabaseKey), [supabaseUrl, supabaseKey]);
|
|
@@ -20293,6 +21261,19 @@ var LoadingPage = ({
|
|
|
20293
21261
|
] }) });
|
|
20294
21262
|
};
|
|
20295
21263
|
var LoadingPage_default = LoadingPage;
|
|
21264
|
+
var AccessDeniedPage = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen flex items-center justify-center bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-md w-full bg-white shadow-lg rounded-lg p-6 text-center", children: [
|
|
21265
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "mx-auto h-12 w-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z" }) }) }),
|
|
21266
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold text-gray-900 mb-2", children: "Access Denied" }),
|
|
21267
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-4", children: "You do not have access to this dashboard. Please contact your administrator for assistance." }),
|
|
21268
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
21269
|
+
"button",
|
|
21270
|
+
{
|
|
21271
|
+
onClick: () => window.location.href = "/login",
|
|
21272
|
+
className: "bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors",
|
|
21273
|
+
children: "Return to Login"
|
|
21274
|
+
}
|
|
21275
|
+
)
|
|
21276
|
+
] }) });
|
|
20296
21277
|
var withAuth = (WrappedComponent2, options) => {
|
|
20297
21278
|
const defaultOptions = {
|
|
20298
21279
|
redirectTo: "/login",
|
|
@@ -20300,7 +21281,7 @@ var withAuth = (WrappedComponent2, options) => {
|
|
|
20300
21281
|
...options
|
|
20301
21282
|
};
|
|
20302
21283
|
const WithAuthComponent = React19__namespace.memo(function WithAuthComponent2(props) {
|
|
20303
|
-
const { session, loading } = useAuth();
|
|
21284
|
+
const { session, loading, error } = useAuth();
|
|
20304
21285
|
const router$1 = router.useRouter();
|
|
20305
21286
|
React19__namespace.useEffect(() => {
|
|
20306
21287
|
if (process.env.NODE_ENV === "development" && process.env.DEBUG_AUTH === "true") {
|
|
@@ -20308,14 +21289,17 @@ var withAuth = (WrappedComponent2, options) => {
|
|
|
20308
21289
|
}
|
|
20309
21290
|
}, [session, loading]);
|
|
20310
21291
|
React19__namespace.useEffect(() => {
|
|
20311
|
-
if (!loading && defaultOptions.requireAuth && !session) {
|
|
21292
|
+
if (!loading && defaultOptions.requireAuth && !session && !error) {
|
|
20312
21293
|
console.log("Redirecting to login from withAuth");
|
|
20313
21294
|
router$1.replace(defaultOptions.redirectTo);
|
|
20314
21295
|
}
|
|
20315
|
-
}, [session, loading, router$1]);
|
|
21296
|
+
}, [session, loading, router$1, error]);
|
|
20316
21297
|
if (loading) {
|
|
20317
21298
|
return /* @__PURE__ */ jsxRuntime.jsx(LoadingPage, { message: "Authenticating..." });
|
|
20318
21299
|
}
|
|
21300
|
+
if (error && error.message.includes("You do not have access to this dashboard")) {
|
|
21301
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AccessDeniedPage, {});
|
|
21302
|
+
}
|
|
20319
21303
|
if (defaultOptions.requireAuth && !session) {
|
|
20320
21304
|
return null;
|
|
20321
21305
|
}
|
|
@@ -20324,6 +21308,31 @@ var withAuth = (WrappedComponent2, options) => {
|
|
|
20324
21308
|
WithAuthComponent.displayName = `withAuth(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
|
|
20325
21309
|
return WithAuthComponent;
|
|
20326
21310
|
};
|
|
21311
|
+
function withAccessControl(WrappedComponent2, options = {}) {
|
|
21312
|
+
const {
|
|
21313
|
+
requiredPath,
|
|
21314
|
+
redirectTo = "/",
|
|
21315
|
+
UnauthorizedComponent
|
|
21316
|
+
} = options;
|
|
21317
|
+
const WithAccessControlComponent = (props) => {
|
|
21318
|
+
const router$1 = router.useRouter();
|
|
21319
|
+
const { user, loading: authLoading } = useAuth();
|
|
21320
|
+
const { canAccessPage, userRole } = useAccessControl();
|
|
21321
|
+
requiredPath || router$1.pathname;
|
|
21322
|
+
if (authLoading) {
|
|
21323
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
21324
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4" }),
|
|
21325
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600", children: "Loading..." })
|
|
21326
|
+
] }) });
|
|
21327
|
+
}
|
|
21328
|
+
if (!user) {
|
|
21329
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-4", children: "Please log in to continue" }) }) });
|
|
21330
|
+
}
|
|
21331
|
+
return /* @__PURE__ */ jsxRuntime.jsx(WrappedComponent2, { ...props });
|
|
21332
|
+
};
|
|
21333
|
+
WithAccessControlComponent.displayName = `withAccessControl(${WrappedComponent2.displayName || WrappedComponent2.name || "Component"})`;
|
|
21334
|
+
return WithAccessControlComponent;
|
|
21335
|
+
}
|
|
20327
21336
|
var LoginPage = ({
|
|
20328
21337
|
onRateLimitCheck,
|
|
20329
21338
|
logoSrc = "/optifye-logo.png",
|
|
@@ -21902,7 +22911,7 @@ var HourlyOutputChartComponent = ({
|
|
|
21902
22911
|
ticks.push(maxYValue);
|
|
21903
22912
|
return [...new Set(ticks)].sort((a, b) => a - b);
|
|
21904
22913
|
};
|
|
21905
|
-
const renderLegend = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 absolute
|
|
22914
|
+
const renderLegend = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 absolute bottom-3 left-0 right-0 bg-white py-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
|
|
21906
22915
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
|
|
21907
22916
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
21908
22917
|
"Target: ",
|
|
@@ -22602,6 +23611,89 @@ var VideoGridView = React19__namespace.default.memo(({
|
|
|
22602
23611
|
) }) });
|
|
22603
23612
|
});
|
|
22604
23613
|
VideoGridView.displayName = "VideoGridView";
|
|
23614
|
+
var Card2 = (props) => {
|
|
23615
|
+
const { Card: RegisteredCard } = useRegistry();
|
|
23616
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCard, { ...props });
|
|
23617
|
+
};
|
|
23618
|
+
var CardHeader2 = (props) => {
|
|
23619
|
+
const { CardHeader: RegisteredCardHeader } = useRegistry();
|
|
23620
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardHeader, { ...props });
|
|
23621
|
+
};
|
|
23622
|
+
var CardTitle2 = (props) => {
|
|
23623
|
+
const { CardTitle: RegisteredCardTitle } = useRegistry();
|
|
23624
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardTitle, { ...props });
|
|
23625
|
+
};
|
|
23626
|
+
var CardDescription2 = (props) => {
|
|
23627
|
+
const { CardDescription: RegisteredCardDescription } = useRegistry();
|
|
23628
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardDescription, { ...props });
|
|
23629
|
+
};
|
|
23630
|
+
var CardContent2 = (props) => {
|
|
23631
|
+
const { CardContent: RegisteredCardContent } = useRegistry();
|
|
23632
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardContent, { ...props });
|
|
23633
|
+
};
|
|
23634
|
+
var CardFooter2 = (props) => {
|
|
23635
|
+
const { CardFooter: RegisteredCardFooter } = useRegistry();
|
|
23636
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardFooter, { ...props });
|
|
23637
|
+
};
|
|
23638
|
+
var WorkspaceMetricCardsImpl = ({
|
|
23639
|
+
workspace,
|
|
23640
|
+
className
|
|
23641
|
+
}) => {
|
|
23642
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `grid grid-cols-1 gap-4 sm:gap-3 sm:grid-cols-2 lg:grid-cols-5 w-full h-full ${className || ""}`, children: [
|
|
23643
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
23644
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Pieces Lost" }) }),
|
|
23645
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
23646
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold ${workspace.output_difference >= 0 ? "text-green-500" : "text-red-500"}`, children: workspace.output_difference >= 0 ? `+${workspace.output_difference}` : `-${Math.abs(workspace.output_difference)}` }),
|
|
23647
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: workspace.output_difference >= 0 ? `pieces ahead of ideal output: ${workspace.ideal_output_until_now}` : `pieces behind ideal output: ${workspace.ideal_output_until_now}` })
|
|
23648
|
+
] }) })
|
|
23649
|
+
] }),
|
|
23650
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
23651
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Efficiency" }) }),
|
|
23652
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
23653
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: `text-5xl font-bold ${workspace.avg_efficiency >= 80 ? "text-green-500" : "text-red-500"}`, children: [
|
|
23654
|
+
workspace.avg_efficiency.toFixed(1),
|
|
23655
|
+
"%"
|
|
23656
|
+
] }),
|
|
23657
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Target: 80%" })
|
|
23658
|
+
] }) })
|
|
23659
|
+
] }),
|
|
23660
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
23661
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "PPH" }) }),
|
|
23662
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
23663
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold ${workspace.avg_pph >= workspace.pph_threshold ? "text-green-500" : "text-red-500"}`, children: workspace.avg_pph.toFixed(1) }),
|
|
23664
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
|
|
23665
|
+
"Standard: ",
|
|
23666
|
+
workspace.pph_threshold.toFixed(1)
|
|
23667
|
+
] })
|
|
23668
|
+
] }) })
|
|
23669
|
+
] }),
|
|
23670
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
23671
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
|
|
23672
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
23673
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
|
|
23674
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
|
|
23675
|
+
"Standard: ",
|
|
23676
|
+
workspace.ideal_cycle_time?.toFixed(1) || 0,
|
|
23677
|
+
"s"
|
|
23678
|
+
] })
|
|
23679
|
+
] }) })
|
|
23680
|
+
] }),
|
|
23681
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
23682
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Idle Time" }) }),
|
|
23683
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
23684
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-4xl font-bold ${!workspace.idle_time || workspace.idle_time <= 0 ? "text-green-500" : workspace.idle_time <= 300 ? "text-yellow-500" : (
|
|
23685
|
+
// 5 minutes or less
|
|
23686
|
+
"text-red-500"
|
|
23687
|
+
)}`, children: formatIdleTime(workspace.idle_time) }),
|
|
23688
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Total idle time" })
|
|
23689
|
+
] }) })
|
|
23690
|
+
] })
|
|
23691
|
+
] });
|
|
23692
|
+
};
|
|
23693
|
+
var WorkspaceMetricCards = (props) => {
|
|
23694
|
+
const { WorkspaceMetricCards: RegisteredComponent } = useRegistry();
|
|
23695
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredComponent, { ...props });
|
|
23696
|
+
};
|
|
22605
23697
|
var defaults = {
|
|
22606
23698
|
Card,
|
|
22607
23699
|
CardHeader,
|
|
@@ -22611,7 +23703,8 @@ var defaults = {
|
|
|
22611
23703
|
CardFooter,
|
|
22612
23704
|
Button,
|
|
22613
23705
|
HourlyOutputChart,
|
|
22614
|
-
VideoGridView
|
|
23706
|
+
VideoGridView,
|
|
23707
|
+
WorkspaceMetricCards: WorkspaceMetricCardsImpl
|
|
22615
23708
|
};
|
|
22616
23709
|
var RegistryCtx = React19.createContext(defaults);
|
|
22617
23710
|
var useRegistry = () => {
|
|
@@ -24083,30 +25176,6 @@ var ISTTimer = React19.memo(() => {
|
|
|
24083
25176
|
});
|
|
24084
25177
|
ISTTimer.displayName = "ISTTimer";
|
|
24085
25178
|
var ISTTimer_default = ISTTimer;
|
|
24086
|
-
var Card2 = (props) => {
|
|
24087
|
-
const { Card: RegisteredCard } = useRegistry();
|
|
24088
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCard, { ...props });
|
|
24089
|
-
};
|
|
24090
|
-
var CardHeader2 = (props) => {
|
|
24091
|
-
const { CardHeader: RegisteredCardHeader } = useRegistry();
|
|
24092
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardHeader, { ...props });
|
|
24093
|
-
};
|
|
24094
|
-
var CardTitle2 = (props) => {
|
|
24095
|
-
const { CardTitle: RegisteredCardTitle } = useRegistry();
|
|
24096
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardTitle, { ...props });
|
|
24097
|
-
};
|
|
24098
|
-
var CardDescription2 = (props) => {
|
|
24099
|
-
const { CardDescription: RegisteredCardDescription } = useRegistry();
|
|
24100
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardDescription, { ...props });
|
|
24101
|
-
};
|
|
24102
|
-
var CardContent2 = (props) => {
|
|
24103
|
-
const { CardContent: RegisteredCardContent } = useRegistry();
|
|
24104
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardContent, { ...props });
|
|
24105
|
-
};
|
|
24106
|
-
var CardFooter2 = (props) => {
|
|
24107
|
-
const { CardFooter: RegisteredCardFooter } = useRegistry();
|
|
24108
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RegisteredCardFooter, { ...props });
|
|
24109
|
-
};
|
|
24110
25179
|
var TicketHistory = ({ companyId }) => {
|
|
24111
25180
|
const { tickets, loading, error } = useTicketHistory(companyId);
|
|
24112
25181
|
const [expandedTickets, setExpandedTickets] = React19.useState(/* @__PURE__ */ new Set());
|
|
@@ -26677,61 +27746,6 @@ var WorkspaceMonthlyPdfGenerator = ({
|
|
|
26677
27746
|
}
|
|
26678
27747
|
);
|
|
26679
27748
|
};
|
|
26680
|
-
var WorkspaceMetricCards = ({
|
|
26681
|
-
workspace,
|
|
26682
|
-
className
|
|
26683
|
-
}) => {
|
|
26684
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `grid grid-cols-1 gap-4 sm:gap-3 sm:grid-cols-2 lg:grid-cols-5 w-full h-full ${className || ""}`, children: [
|
|
26685
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
26686
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Pieces Lost" }) }),
|
|
26687
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
26688
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold ${workspace.output_difference >= 0 ? "text-green-500" : "text-red-500"}`, children: workspace.output_difference >= 0 ? `+${workspace.output_difference}` : `-${Math.abs(workspace.output_difference)}` }),
|
|
26689
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: workspace.output_difference >= 0 ? `pieces ahead of ideal output: ${workspace.ideal_output_until_now}` : `pieces behind ideal output: ${workspace.ideal_output_until_now}` })
|
|
26690
|
-
] }) })
|
|
26691
|
-
] }),
|
|
26692
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
26693
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Efficiency" }) }),
|
|
26694
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
26695
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: `text-5xl font-bold ${workspace.avg_efficiency >= 80 ? "text-green-500" : "text-red-500"}`, children: [
|
|
26696
|
-
workspace.avg_efficiency.toFixed(1),
|
|
26697
|
-
"%"
|
|
26698
|
-
] }),
|
|
26699
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Target: 80%" })
|
|
26700
|
-
] }) })
|
|
26701
|
-
] }),
|
|
26702
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
26703
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "PPH" }) }),
|
|
26704
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
26705
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold ${workspace.avg_pph >= workspace.pph_threshold ? "text-green-500" : "text-red-500"}`, children: workspace.avg_pph.toFixed(1) }),
|
|
26706
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
|
|
26707
|
-
"Standard: ",
|
|
26708
|
-
workspace.pph_threshold.toFixed(1)
|
|
26709
|
-
] })
|
|
26710
|
-
] }) })
|
|
26711
|
-
] }),
|
|
26712
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
26713
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
|
|
26714
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
26715
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
|
|
26716
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
|
|
26717
|
-
"Standard: ",
|
|
26718
|
-
workspace.ideal_cycle_time?.toFixed(1) || 0,
|
|
26719
|
-
"s"
|
|
26720
|
-
] })
|
|
26721
|
-
] }) })
|
|
26722
|
-
] }),
|
|
26723
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
|
|
26724
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Idle Time" }) }),
|
|
26725
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
26726
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-4xl font-bold ${!workspace.idle_time || workspace.idle_time <= 0 ? "text-green-500" : workspace.idle_time <= 300 ? "text-yellow-500" : (
|
|
26727
|
-
// 5 minutes or less
|
|
26728
|
-
"text-red-500"
|
|
26729
|
-
)}`, children: formatIdleTime(workspace.idle_time) }),
|
|
26730
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Total idle time" })
|
|
26731
|
-
] }) })
|
|
26732
|
-
] })
|
|
26733
|
-
] });
|
|
26734
|
-
};
|
|
26735
27749
|
var LiveTimer = () => {
|
|
26736
27750
|
const [time2, setTime] = React19.useState(/* @__PURE__ */ new Date());
|
|
26737
27751
|
React19.useEffect(() => {
|
|
@@ -27199,7 +28213,7 @@ var VideoPlayer = React19__namespace.default.forwardRef(({
|
|
|
27199
28213
|
muted,
|
|
27200
28214
|
loop,
|
|
27201
28215
|
poster,
|
|
27202
|
-
//
|
|
28216
|
+
// Optimized HLS configuration for VOD content - PERFORMANCE OPTIMIZED
|
|
27203
28217
|
html5: {
|
|
27204
28218
|
vhs: {
|
|
27205
28219
|
// VHS (Video HTTP Streaming) options for HLS
|
|
@@ -27209,12 +28223,59 @@ var VideoPlayer = React19__namespace.default.forwardRef(({
|
|
|
27209
28223
|
// Use native HLS on Safari
|
|
27210
28224
|
enableLowInitialPlaylist: true,
|
|
27211
28225
|
smoothQualityChange: true,
|
|
27212
|
-
//
|
|
28226
|
+
// Optimized bandwidth and buffering for VOD
|
|
28227
|
+
bandwidth: 5e7,
|
|
28228
|
+
// Start with high bandwidth assumption (50 Mbps)
|
|
28229
|
+
initialBandwidth: 5e7,
|
|
28230
|
+
// Assume good connection initially
|
|
28231
|
+
limitRenditionByPlayerDimensions: true,
|
|
28232
|
+
// Buffer configuration for optimal VOD playback
|
|
28233
|
+
maxBufferLength: 30,
|
|
28234
|
+
// Don't over-buffer (30 seconds)
|
|
28235
|
+
maxMaxBufferLength: 60,
|
|
28236
|
+
// Absolute max buffer (60 seconds)
|
|
28237
|
+
maxBufferSize: 60 * 1e3 * 1e3,
|
|
28238
|
+
// 60MB max buffer size
|
|
28239
|
+
maxBufferHole: 0.5,
|
|
28240
|
+
// Allow 0.5s holes in buffer
|
|
28241
|
+
bufferBasedABR: true,
|
|
28242
|
+
// Segment loading optimization
|
|
27213
28243
|
maxPlaylistRetries: 3,
|
|
28244
|
+
playlistRetryDelay: 500,
|
|
28245
|
+
// 500ms between retries
|
|
27214
28246
|
playlistExclusionDuration: 60,
|
|
27215
|
-
|
|
27216
|
-
|
|
27217
|
-
|
|
28247
|
+
segmentLoadingRetryAttempts: 3,
|
|
28248
|
+
segmentLoadingRetryDelay: 1e3,
|
|
28249
|
+
// 1s between segment retries
|
|
28250
|
+
segmentLoadingTimeOut: 2e4,
|
|
28251
|
+
// 20s timeout for segments
|
|
28252
|
+
manifestLoadingTimeOut: 1e4,
|
|
28253
|
+
// 10s timeout for manifest
|
|
28254
|
+
// Performance optimizations
|
|
28255
|
+
experimentalBufferBasedCodecSwitching: true,
|
|
28256
|
+
experimentalCacheEncryptionKeys: true,
|
|
28257
|
+
handlePartialData: true,
|
|
28258
|
+
allowSeeksWithinUnsafeLiveWindow: false,
|
|
28259
|
+
// VOD content
|
|
28260
|
+
experimentalLLHLS: false,
|
|
28261
|
+
// Disable Low Latency HLS for VOD
|
|
28262
|
+
// Connection settings
|
|
28263
|
+
enableWorker: true,
|
|
28264
|
+
// Use web worker for better performance
|
|
28265
|
+
progressive: true,
|
|
28266
|
+
// Progressive download
|
|
28267
|
+
// Adaptive bitrate settings (if multi-quality available)
|
|
28268
|
+
abrEwmaFastLive: 3,
|
|
28269
|
+
abrEwmaSlowLive: 9,
|
|
28270
|
+
abrBandWidthFactor: 0.95,
|
|
28271
|
+
abrBandWidthUpFactor: 0.7,
|
|
28272
|
+
abrMaxWithRealBitrate: false,
|
|
28273
|
+
// Request options for better network handling
|
|
28274
|
+
requestOptions: {
|
|
28275
|
+
timeout: 3e4,
|
|
28276
|
+
// 30s overall timeout
|
|
28277
|
+
maxRetry: 3
|
|
28278
|
+
}
|
|
27218
28279
|
},
|
|
27219
28280
|
nativeVideoTracks: false,
|
|
27220
28281
|
nativeAudioTracks: false,
|
|
@@ -27279,9 +28340,17 @@ var VideoPlayer = React19__namespace.default.forwardRef(({
|
|
|
27279
28340
|
onError?.(player, error);
|
|
27280
28341
|
});
|
|
27281
28342
|
if (src) {
|
|
28343
|
+
const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
|
|
28344
|
+
let videoSrc = src;
|
|
28345
|
+
if (src.startsWith("#EXTM3U")) {
|
|
28346
|
+
const blob = new Blob([src], { type: "application/x-mpegURL" });
|
|
28347
|
+
videoSrc = URL.createObjectURL(blob);
|
|
28348
|
+
console.log("[VideoPlayer] Created blob URL for HLS playlist content");
|
|
28349
|
+
player._blobUrl = videoSrc;
|
|
28350
|
+
}
|
|
27282
28351
|
player.src({
|
|
27283
|
-
src,
|
|
27284
|
-
type:
|
|
28352
|
+
src: videoSrc,
|
|
28353
|
+
type: isHLS ? "application/x-mpegURL" : "video/mp4"
|
|
27285
28354
|
});
|
|
27286
28355
|
}
|
|
27287
28356
|
}, [
|
|
@@ -27301,16 +28370,34 @@ var VideoPlayer = React19__namespace.default.forwardRef(({
|
|
|
27301
28370
|
]);
|
|
27302
28371
|
React19.useEffect(() => {
|
|
27303
28372
|
if (playerRef.current && src) {
|
|
28373
|
+
const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
|
|
28374
|
+
let videoSrc = src;
|
|
28375
|
+
let blobUrl = null;
|
|
28376
|
+
if (src.startsWith("#EXTM3U")) {
|
|
28377
|
+
const blob = new Blob([src], { type: "application/x-mpegURL" });
|
|
28378
|
+
blobUrl = URL.createObjectURL(blob);
|
|
28379
|
+
videoSrc = blobUrl;
|
|
28380
|
+
console.log("[VideoPlayer] Created blob URL for HLS playlist content (source update)");
|
|
28381
|
+
}
|
|
27304
28382
|
playerRef.current.src({
|
|
27305
|
-
src,
|
|
27306
|
-
type:
|
|
28383
|
+
src: videoSrc,
|
|
28384
|
+
type: isHLS ? "application/x-mpegURL" : "video/mp4"
|
|
27307
28385
|
});
|
|
28386
|
+
return () => {
|
|
28387
|
+
if (blobUrl) {
|
|
28388
|
+
URL.revokeObjectURL(blobUrl);
|
|
28389
|
+
}
|
|
28390
|
+
};
|
|
27308
28391
|
}
|
|
27309
28392
|
}, [src]);
|
|
27310
28393
|
React19.useEffect(() => {
|
|
27311
28394
|
initializePlayer();
|
|
27312
28395
|
return () => {
|
|
27313
28396
|
if (playerRef.current) {
|
|
28397
|
+
const blobUrl = playerRef.current._blobUrl;
|
|
28398
|
+
if (blobUrl) {
|
|
28399
|
+
URL.revokeObjectURL(blobUrl);
|
|
28400
|
+
}
|
|
27314
28401
|
playerRef.current.dispose();
|
|
27315
28402
|
playerRef.current = null;
|
|
27316
28403
|
setIsReady(false);
|
|
@@ -27382,6 +28469,233 @@ var VideoPlayer = React19__namespace.default.forwardRef(({
|
|
|
27382
28469
|
] });
|
|
27383
28470
|
});
|
|
27384
28471
|
VideoPlayer.displayName = "VideoPlayer";
|
|
28472
|
+
var CroppedVideoPlayer = React19.forwardRef(({
|
|
28473
|
+
crop,
|
|
28474
|
+
debug = false,
|
|
28475
|
+
...videoProps
|
|
28476
|
+
}, ref) => {
|
|
28477
|
+
const videoContainerRef = React19.useRef(null);
|
|
28478
|
+
const hiddenVideoRef = React19.useRef(null);
|
|
28479
|
+
const canvasRef = React19.useRef(null);
|
|
28480
|
+
const animationFrameRef = React19.useRef(null);
|
|
28481
|
+
const videoElementRef = React19.useRef(null);
|
|
28482
|
+
const [isVideoReady, setIsVideoReady] = React19.useState(false);
|
|
28483
|
+
const [canvasDimensions, setCanvasDimensions] = React19.useState({ width: 0, height: 0 });
|
|
28484
|
+
const [isProcessing, setIsProcessing] = React19.useState(false);
|
|
28485
|
+
const stopCanvasRendering = React19.useCallback(() => {
|
|
28486
|
+
if (animationFrameRef.current) {
|
|
28487
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
28488
|
+
animationFrameRef.current = null;
|
|
28489
|
+
}
|
|
28490
|
+
}, []);
|
|
28491
|
+
React19.useImperativeHandle(ref, () => ({
|
|
28492
|
+
player: hiddenVideoRef.current?.player || null,
|
|
28493
|
+
play: () => hiddenVideoRef.current?.play() || void 0,
|
|
28494
|
+
pause: () => hiddenVideoRef.current?.pause(),
|
|
28495
|
+
currentTime: (time2) => {
|
|
28496
|
+
if (time2 !== void 0 && hiddenVideoRef.current) {
|
|
28497
|
+
return hiddenVideoRef.current.currentTime(time2);
|
|
28498
|
+
}
|
|
28499
|
+
return hiddenVideoRef.current?.currentTime() || 0;
|
|
28500
|
+
},
|
|
28501
|
+
duration: () => hiddenVideoRef.current?.duration() || 0,
|
|
28502
|
+
paused: () => hiddenVideoRef.current?.paused() || true,
|
|
28503
|
+
mute: (isMuted) => hiddenVideoRef.current?.mute(isMuted) || false,
|
|
28504
|
+
volume: (level) => hiddenVideoRef.current?.volume(level) || 0,
|
|
28505
|
+
dispose: () => {
|
|
28506
|
+
hiddenVideoRef.current?.dispose();
|
|
28507
|
+
stopCanvasRendering();
|
|
28508
|
+
},
|
|
28509
|
+
isReady: hiddenVideoRef.current?.isReady || false
|
|
28510
|
+
}), [stopCanvasRendering]);
|
|
28511
|
+
const calculateCanvasDimensions = React19.useCallback(() => {
|
|
28512
|
+
if (!crop || !videoContainerRef.current) return;
|
|
28513
|
+
const container = videoContainerRef.current;
|
|
28514
|
+
const containerWidth = container.clientWidth;
|
|
28515
|
+
const containerHeight = container.clientHeight;
|
|
28516
|
+
const cropAspectRatio = crop.width / crop.height;
|
|
28517
|
+
let canvasWidth;
|
|
28518
|
+
let canvasHeight;
|
|
28519
|
+
if (containerWidth / containerHeight > cropAspectRatio) {
|
|
28520
|
+
canvasHeight = containerHeight;
|
|
28521
|
+
canvasWidth = canvasHeight * cropAspectRatio;
|
|
28522
|
+
} else {
|
|
28523
|
+
canvasWidth = containerWidth;
|
|
28524
|
+
canvasHeight = canvasWidth / cropAspectRatio;
|
|
28525
|
+
}
|
|
28526
|
+
setCanvasDimensions({
|
|
28527
|
+
width: Math.floor(canvasWidth),
|
|
28528
|
+
height: Math.floor(canvasHeight)
|
|
28529
|
+
});
|
|
28530
|
+
}, [crop]);
|
|
28531
|
+
const renderFrameToCanvas = React19.useCallback(() => {
|
|
28532
|
+
if (!canvasRef.current || !videoElementRef.current || !crop) {
|
|
28533
|
+
return;
|
|
28534
|
+
}
|
|
28535
|
+
const canvas = canvasRef.current;
|
|
28536
|
+
const video = videoElementRef.current;
|
|
28537
|
+
const ctx = canvas.getContext("2d");
|
|
28538
|
+
if (!ctx || video.paused || video.ended) {
|
|
28539
|
+
return;
|
|
28540
|
+
}
|
|
28541
|
+
const videoWidth = video.videoWidth;
|
|
28542
|
+
const videoHeight = video.videoHeight;
|
|
28543
|
+
if (videoWidth === 0 || videoHeight === 0) {
|
|
28544
|
+
animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
|
|
28545
|
+
return;
|
|
28546
|
+
}
|
|
28547
|
+
const cropX = crop.x / 100 * videoWidth;
|
|
28548
|
+
const cropY = crop.y / 100 * videoHeight;
|
|
28549
|
+
const cropWidth = crop.width / 100 * videoWidth;
|
|
28550
|
+
const cropHeight = crop.height / 100 * videoHeight;
|
|
28551
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
28552
|
+
ctx.drawImage(
|
|
28553
|
+
video,
|
|
28554
|
+
cropX,
|
|
28555
|
+
cropY,
|
|
28556
|
+
cropWidth,
|
|
28557
|
+
cropHeight,
|
|
28558
|
+
// Source rectangle (crop area)
|
|
28559
|
+
0,
|
|
28560
|
+
0,
|
|
28561
|
+
canvas.width,
|
|
28562
|
+
canvas.height
|
|
28563
|
+
// Destination (full canvas)
|
|
28564
|
+
);
|
|
28565
|
+
animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
|
|
28566
|
+
}, [crop]);
|
|
28567
|
+
const handleVideoReady = React19.useCallback((player) => {
|
|
28568
|
+
console.log("[CroppedVideoPlayer] Video player ready");
|
|
28569
|
+
const videoEl = player.el().querySelector("video");
|
|
28570
|
+
if (videoEl) {
|
|
28571
|
+
videoElementRef.current = videoEl;
|
|
28572
|
+
setIsVideoReady(true);
|
|
28573
|
+
}
|
|
28574
|
+
videoProps.onReady?.(player);
|
|
28575
|
+
}, [videoProps]);
|
|
28576
|
+
const handleVideoPlay = React19.useCallback((player) => {
|
|
28577
|
+
console.log("[CroppedVideoPlayer] Video playing, starting canvas rendering");
|
|
28578
|
+
if (crop && canvasRef.current) {
|
|
28579
|
+
setIsProcessing(true);
|
|
28580
|
+
renderFrameToCanvas();
|
|
28581
|
+
}
|
|
28582
|
+
videoProps.onPlay?.(player);
|
|
28583
|
+
}, [crop, renderFrameToCanvas, videoProps]);
|
|
28584
|
+
const handleVideoPause = React19.useCallback((player) => {
|
|
28585
|
+
console.log("[CroppedVideoPlayer] Video paused, stopping canvas rendering");
|
|
28586
|
+
stopCanvasRendering();
|
|
28587
|
+
setIsProcessing(false);
|
|
28588
|
+
videoProps.onPause?.(player);
|
|
28589
|
+
}, [stopCanvasRendering, videoProps]);
|
|
28590
|
+
const handleVideoEnded = React19.useCallback((player) => {
|
|
28591
|
+
console.log("[CroppedVideoPlayer] Video ended");
|
|
28592
|
+
stopCanvasRendering();
|
|
28593
|
+
setIsProcessing(false);
|
|
28594
|
+
videoProps.onEnded?.(player);
|
|
28595
|
+
}, [stopCanvasRendering, videoProps]);
|
|
28596
|
+
const handleSeeking = React19.useCallback((player) => {
|
|
28597
|
+
console.log("[CroppedVideoPlayer] Video seeking");
|
|
28598
|
+
if (crop && !player.paused()) {
|
|
28599
|
+
renderFrameToCanvas();
|
|
28600
|
+
}
|
|
28601
|
+
videoProps.onSeeking?.(player);
|
|
28602
|
+
}, [crop, renderFrameToCanvas, videoProps]);
|
|
28603
|
+
const handleSeeked = React19.useCallback((player) => {
|
|
28604
|
+
console.log("[CroppedVideoPlayer] Video seeked");
|
|
28605
|
+
if (crop && !player.paused()) {
|
|
28606
|
+
renderFrameToCanvas();
|
|
28607
|
+
}
|
|
28608
|
+
videoProps.onSeeked?.(player);
|
|
28609
|
+
}, [crop, renderFrameToCanvas, videoProps]);
|
|
28610
|
+
const handleLoadedMetadata = React19.useCallback((player) => {
|
|
28611
|
+
console.log("[CroppedVideoPlayer] Video metadata loaded");
|
|
28612
|
+
calculateCanvasDimensions();
|
|
28613
|
+
videoProps.onLoadedMetadata?.(player);
|
|
28614
|
+
}, [calculateCanvasDimensions, videoProps]);
|
|
28615
|
+
React19.useEffect(() => {
|
|
28616
|
+
calculateCanvasDimensions();
|
|
28617
|
+
const handleResize = () => {
|
|
28618
|
+
calculateCanvasDimensions();
|
|
28619
|
+
};
|
|
28620
|
+
window.addEventListener("resize", handleResize);
|
|
28621
|
+
return () => {
|
|
28622
|
+
window.removeEventListener("resize", handleResize);
|
|
28623
|
+
};
|
|
28624
|
+
}, [calculateCanvasDimensions]);
|
|
28625
|
+
React19.useEffect(() => {
|
|
28626
|
+
return () => {
|
|
28627
|
+
stopCanvasRendering();
|
|
28628
|
+
};
|
|
28629
|
+
}, [stopCanvasRendering]);
|
|
28630
|
+
if (!crop) {
|
|
28631
|
+
return /* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { ref, ...videoProps });
|
|
28632
|
+
}
|
|
28633
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28634
|
+
"div",
|
|
28635
|
+
{
|
|
28636
|
+
ref: videoContainerRef,
|
|
28637
|
+
className: `relative w-full h-full flex items-center justify-center bg-black ${videoProps.className || ""}`,
|
|
28638
|
+
children: [
|
|
28639
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
28640
|
+
VideoPlayer,
|
|
28641
|
+
{
|
|
28642
|
+
ref: hiddenVideoRef,
|
|
28643
|
+
...videoProps,
|
|
28644
|
+
onReady: handleVideoReady,
|
|
28645
|
+
onPlay: handleVideoPlay,
|
|
28646
|
+
onPause: handleVideoPause,
|
|
28647
|
+
onEnded: handleVideoEnded,
|
|
28648
|
+
onSeeking: handleSeeking,
|
|
28649
|
+
onSeeked: handleSeeked,
|
|
28650
|
+
onLoadedMetadata: handleLoadedMetadata
|
|
28651
|
+
}
|
|
28652
|
+
) }),
|
|
28653
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28654
|
+
"canvas",
|
|
28655
|
+
{
|
|
28656
|
+
ref: canvasRef,
|
|
28657
|
+
width: canvasDimensions.width,
|
|
28658
|
+
height: canvasDimensions.height,
|
|
28659
|
+
className: "max-w-full max-h-full",
|
|
28660
|
+
style: {
|
|
28661
|
+
display: isVideoReady ? "block" : "none",
|
|
28662
|
+
width: `${canvasDimensions.width}px`,
|
|
28663
|
+
height: `${canvasDimensions.height}px`,
|
|
28664
|
+
pointerEvents: "none"
|
|
28665
|
+
// Allow clicks to pass through to controls
|
|
28666
|
+
}
|
|
28667
|
+
}
|
|
28668
|
+
),
|
|
28669
|
+
!isVideoReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
|
|
28670
|
+
debug && isVideoReady && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
|
|
28671
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
28672
|
+
"Crop: ",
|
|
28673
|
+
crop.x,
|
|
28674
|
+
",",
|
|
28675
|
+
crop.y,
|
|
28676
|
+
" ",
|
|
28677
|
+
crop.width,
|
|
28678
|
+
"x",
|
|
28679
|
+
crop.height,
|
|
28680
|
+
"%"
|
|
28681
|
+
] }),
|
|
28682
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
28683
|
+
"Canvas: ",
|
|
28684
|
+
canvasDimensions.width,
|
|
28685
|
+
"x",
|
|
28686
|
+
canvasDimensions.height,
|
|
28687
|
+
"px"
|
|
28688
|
+
] }),
|
|
28689
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
28690
|
+
"Processing: ",
|
|
28691
|
+
isProcessing ? "Yes" : "No"
|
|
28692
|
+
] })
|
|
28693
|
+
] })
|
|
28694
|
+
]
|
|
28695
|
+
}
|
|
28696
|
+
);
|
|
28697
|
+
});
|
|
28698
|
+
CroppedVideoPlayer.displayName = "CroppedVideoPlayer";
|
|
27385
28699
|
var BackButton = ({
|
|
27386
28700
|
onClick,
|
|
27387
28701
|
text = "Back",
|
|
@@ -27691,6 +29005,244 @@ var InlineEditableText = ({
|
|
|
27691
29005
|
}
|
|
27692
29006
|
);
|
|
27693
29007
|
};
|
|
29008
|
+
var getSupabaseClient3 = () => {
|
|
29009
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
29010
|
+
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
29011
|
+
if (!url || !key) {
|
|
29012
|
+
throw new Error("Supabase configuration missing");
|
|
29013
|
+
}
|
|
29014
|
+
return supabaseJs.createClient(url, key);
|
|
29015
|
+
};
|
|
29016
|
+
var getAuthToken3 = async () => {
|
|
29017
|
+
try {
|
|
29018
|
+
const supabase = getSupabaseClient3();
|
|
29019
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
29020
|
+
return session?.access_token || null;
|
|
29021
|
+
} catch (error) {
|
|
29022
|
+
console.error("[useWorkspaceCrop] Error getting auth token:", error);
|
|
29023
|
+
return null;
|
|
29024
|
+
}
|
|
29025
|
+
};
|
|
29026
|
+
function useWorkspaceCrop(workspaceId) {
|
|
29027
|
+
const [crop, setCrop] = React19.useState(null);
|
|
29028
|
+
const [isLoading, setIsLoading] = React19.useState(true);
|
|
29029
|
+
const [error, setError] = React19.useState(null);
|
|
29030
|
+
React19.useEffect(() => {
|
|
29031
|
+
if (!workspaceId) {
|
|
29032
|
+
setIsLoading(false);
|
|
29033
|
+
return;
|
|
29034
|
+
}
|
|
29035
|
+
const fetchCrop = async () => {
|
|
29036
|
+
setIsLoading(true);
|
|
29037
|
+
setError(null);
|
|
29038
|
+
try {
|
|
29039
|
+
const token = await getAuthToken3();
|
|
29040
|
+
if (!token) {
|
|
29041
|
+
throw new Error("Authentication required");
|
|
29042
|
+
}
|
|
29043
|
+
const response = await fetch("/api/clips/supabase", {
|
|
29044
|
+
method: "POST",
|
|
29045
|
+
headers: {
|
|
29046
|
+
"Content-Type": "application/json",
|
|
29047
|
+
"Authorization": `Bearer ${token}`
|
|
29048
|
+
},
|
|
29049
|
+
body: JSON.stringify({
|
|
29050
|
+
action: "crop",
|
|
29051
|
+
workspaceId
|
|
29052
|
+
})
|
|
29053
|
+
});
|
|
29054
|
+
if (!response.ok) {
|
|
29055
|
+
throw new Error(`Failed to fetch crop: ${response.statusText}`);
|
|
29056
|
+
}
|
|
29057
|
+
const data = await response.json();
|
|
29058
|
+
console.log(`[useWorkspaceCrop] Fetched crop for workspace ${workspaceId}:`, data.crop);
|
|
29059
|
+
setCrop(data.crop);
|
|
29060
|
+
} catch (err) {
|
|
29061
|
+
console.error("[useWorkspaceCrop] Error fetching crop:", err);
|
|
29062
|
+
setError(err instanceof Error ? err.message : "Failed to fetch crop configuration");
|
|
29063
|
+
setCrop(null);
|
|
29064
|
+
} finally {
|
|
29065
|
+
setIsLoading(false);
|
|
29066
|
+
}
|
|
29067
|
+
};
|
|
29068
|
+
fetchCrop();
|
|
29069
|
+
}, [workspaceId]);
|
|
29070
|
+
return { crop, isLoading, error };
|
|
29071
|
+
}
|
|
29072
|
+
var getSeverityIcon = (severity) => {
|
|
29073
|
+
switch (severity) {
|
|
29074
|
+
case "high":
|
|
29075
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3 w-3 text-red-500" });
|
|
29076
|
+
case "medium":
|
|
29077
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3 w-3 text-yellow-500" });
|
|
29078
|
+
case "low":
|
|
29079
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle, { className: "h-3 w-3 text-green-500" });
|
|
29080
|
+
default:
|
|
29081
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "h-3 w-3 text-gray-400" });
|
|
29082
|
+
}
|
|
29083
|
+
};
|
|
29084
|
+
var getColorClasses = (color2) => {
|
|
29085
|
+
const colorMap = {
|
|
29086
|
+
"red": { bg: "bg-red-50", text: "text-red-700", border: "border-red-200" },
|
|
29087
|
+
"yellow": { bg: "bg-yellow-50", text: "text-yellow-700", border: "border-yellow-200" },
|
|
29088
|
+
"orange": { bg: "bg-orange-50", text: "text-orange-700", border: "border-orange-200" },
|
|
29089
|
+
"green": { bg: "bg-green-50", text: "text-green-700", border: "border-green-200" },
|
|
29090
|
+
"blue": { bg: "bg-blue-50", text: "text-blue-700", border: "border-blue-200" },
|
|
29091
|
+
"purple": { bg: "bg-purple-50", text: "text-purple-700", border: "border-purple-200" },
|
|
29092
|
+
"gray": { bg: "bg-gray-50", text: "text-gray-700", border: "border-gray-200" }
|
|
29093
|
+
};
|
|
29094
|
+
return colorMap[color2] || colorMap["gray"];
|
|
29095
|
+
};
|
|
29096
|
+
var FileManagerFilters = ({
|
|
29097
|
+
categories,
|
|
29098
|
+
videos,
|
|
29099
|
+
activeFilter,
|
|
29100
|
+
currentVideoId,
|
|
29101
|
+
counts,
|
|
29102
|
+
onFilterChange,
|
|
29103
|
+
onVideoSelect,
|
|
29104
|
+
className = ""
|
|
29105
|
+
}) => {
|
|
29106
|
+
const [expandedNodes, setExpandedNodes] = React19.useState(/* @__PURE__ */ new Set());
|
|
29107
|
+
const [searchTerm, setSearchTerm] = React19.useState("");
|
|
29108
|
+
const filterTree = React19.useMemo(() => {
|
|
29109
|
+
const tree = [];
|
|
29110
|
+
categories.forEach((category) => {
|
|
29111
|
+
const categoryCount = counts?.[category.id] || 0;
|
|
29112
|
+
let categoryVideos = videos.filter((video) => video.type === category.id);
|
|
29113
|
+
if (searchTerm.trim()) {
|
|
29114
|
+
categoryVideos = categoryVideos.filter(
|
|
29115
|
+
(video) => video.description.toLowerCase().includes(searchTerm.toLowerCase()) || video.timestamp.includes(searchTerm) || video.severity.toLowerCase().includes(searchTerm.toLowerCase())
|
|
29116
|
+
);
|
|
29117
|
+
}
|
|
29118
|
+
if (categoryCount > 0 || categoryVideos.length > 0) {
|
|
29119
|
+
const colorClasses = getColorClasses(category.color);
|
|
29120
|
+
const videoNodes = categoryVideos.map((video, index) => ({
|
|
29121
|
+
id: video.id,
|
|
29122
|
+
label: `${video.timestamp.substring(11, 19)} - ${video.description}`,
|
|
29123
|
+
type: "video",
|
|
29124
|
+
icon: getSeverityIcon(video.severity),
|
|
29125
|
+
timestamp: video.timestamp,
|
|
29126
|
+
severity: video.severity
|
|
29127
|
+
}));
|
|
29128
|
+
tree.push({
|
|
29129
|
+
id: category.id,
|
|
29130
|
+
label: category.label,
|
|
29131
|
+
type: "category",
|
|
29132
|
+
count: categoryCount || categoryVideos.length,
|
|
29133
|
+
// Use API count if available
|
|
29134
|
+
videos: categoryVideos,
|
|
29135
|
+
children: videoNodes,
|
|
29136
|
+
icon: expandedNodes.has(category.id) ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: `h-4 w-4 ${colorClasses.text}` }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Folder, { className: `h-4 w-4 ${colorClasses.text}` }),
|
|
29137
|
+
color: category.color
|
|
29138
|
+
});
|
|
29139
|
+
}
|
|
29140
|
+
});
|
|
29141
|
+
return tree;
|
|
29142
|
+
}, [categories, videos, expandedNodes, searchTerm, counts]);
|
|
29143
|
+
const toggleExpanded = (nodeId) => {
|
|
29144
|
+
const newExpanded = new Set(expandedNodes);
|
|
29145
|
+
if (newExpanded.has(nodeId)) {
|
|
29146
|
+
newExpanded.delete(nodeId);
|
|
29147
|
+
} else {
|
|
29148
|
+
newExpanded.add(nodeId);
|
|
29149
|
+
}
|
|
29150
|
+
setExpandedNodes(newExpanded);
|
|
29151
|
+
};
|
|
29152
|
+
const handleNodeClick = (node) => {
|
|
29153
|
+
if (node.type === "category") {
|
|
29154
|
+
toggleExpanded(node.id);
|
|
29155
|
+
onFilterChange(node.id);
|
|
29156
|
+
} else if (node.type === "video") {
|
|
29157
|
+
const videoIndex = videos.findIndex((v) => v.id === node.id);
|
|
29158
|
+
if (videoIndex !== -1) {
|
|
29159
|
+
onVideoSelect(videoIndex);
|
|
29160
|
+
}
|
|
29161
|
+
}
|
|
29162
|
+
};
|
|
29163
|
+
const renderNode = (node, depth = 0) => {
|
|
29164
|
+
const isExpanded = expandedNodes.has(node.id);
|
|
29165
|
+
const isActive = activeFilter === node.id;
|
|
29166
|
+
const isCurrentVideo = currentVideoId === node.id;
|
|
29167
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
29168
|
+
const colorClasses = node.color ? getColorClasses(node.color) : null;
|
|
29169
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "select-none animate-in", children: [
|
|
29170
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
29171
|
+
"div",
|
|
29172
|
+
{
|
|
29173
|
+
className: `flex items-center cursor-pointer transition-all duration-300 ease-out group relative overflow-hidden ${node.type === "category" && depth === 0 ? `py-3 px-4 rounded-2xl hover:bg-gradient-to-r hover:from-slate-50 hover:to-blue-50/30 hover:shadow-lg hover:shadow-blue-100/20 hover:scale-[1.02] hover:-translate-y-0.5 ${isActive ? "bg-gradient-to-r from-blue-50 via-blue-50/80 to-indigo-50/60 border border-blue-200/50 shadow-lg shadow-blue-100/30 scale-[1.02]" : "border border-transparent"}` : `py-2 px-3 rounded-xl hover:bg-gradient-to-r hover:from-slate-50 hover:to-slate-50/50 hover:shadow-sm ${isActive ? "bg-gradient-to-r from-blue-50/80 to-blue-50/40 border border-blue-200/30 shadow-sm" : "border border-transparent"} ${isCurrentVideo ? "bg-gradient-to-r from-emerald-50 to-green-50/60 border border-emerald-200/50 shadow-md shadow-emerald-100/20" : ""}`} ${node.type === "video" ? "ml-6" : ""}`,
|
|
29174
|
+
onClick: () => handleNodeClick(node),
|
|
29175
|
+
children: [
|
|
29176
|
+
hasChildren && /* @__PURE__ */ jsxRuntime.jsx(
|
|
29177
|
+
"button",
|
|
29178
|
+
{
|
|
29179
|
+
className: `flex-shrink-0 mr-2 p-1.5 rounded-lg hover:bg-white/80 hover:shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-blue-400/50 focus:bg-white ${node.type === "category" ? "group-hover:scale-110" : "group-hover:scale-105"}`,
|
|
29180
|
+
onClick: (e) => {
|
|
29181
|
+
e.stopPropagation();
|
|
29182
|
+
toggleExpanded(node.id);
|
|
29183
|
+
},
|
|
29184
|
+
children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `text-slate-600 group-hover:text-blue-600 transition-colors duration-200 ${node.type === "category" ? "h-4 w-4" : "h-3.5 w-3.5"}` }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: `text-slate-600 group-hover:text-blue-600 transition-colors duration-200 ${node.type === "category" ? "h-4 w-4" : "h-3.5 w-3.5"}` })
|
|
29185
|
+
}
|
|
29186
|
+
),
|
|
29187
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 mr-3 ${node.type === "category" ? "p-2 rounded-lg shadow-sm group-hover:scale-110 transition-transform duration-200" : "p-0.5"} ${colorClasses && node.type === "category" ? `${colorClasses.bg} border border-white/60` : ""}`, children: node.icon }),
|
|
29188
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex items-center justify-between", children: [
|
|
29189
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
29190
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `font-semibold tracking-tight ${node.type === "category" ? "text-slate-800 text-sm" : "text-slate-700 text-xs"} ${isCurrentVideo ? "text-emerald-700 font-bold" : ""} group-hover:text-slate-900 transition-colors duration-200`, children: node.label }),
|
|
29191
|
+
node.type === "video" && node.severity && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 capitalize mt-0.5 font-medium", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `inline-flex items-center px-1.5 py-0.5 rounded-md text-xs font-medium ${node.severity === "high" ? "bg-red-100 text-red-700" : node.severity === "medium" ? "bg-yellow-100 text-yellow-700" : "bg-green-100 text-green-700"}`, children: [
|
|
29192
|
+
node.severity,
|
|
29193
|
+
" priority"
|
|
29194
|
+
] }) })
|
|
29195
|
+
] }),
|
|
29196
|
+
node.count !== void 0 && node.type === "category" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center ml-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2.5 py-1 text-sm font-bold rounded-lg shadow-sm border backdrop-blur-sm flex-shrink-0 group-hover:scale-105 transition-all duration-200 ${colorClasses ? `${colorClasses.bg} ${colorClasses.text} ${colorClasses.border} bg-opacity-80` : "bg-slate-100/80 text-slate-700 border-slate-200/60"}`, children: node.count }) })
|
|
29197
|
+
] })
|
|
29198
|
+
]
|
|
29199
|
+
}
|
|
29200
|
+
),
|
|
29201
|
+
hasChildren && isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 ml-3 space-y-1 animate-in border-l-2 border-slate-100 pl-3", children: node.children.map((child) => renderNode(child, depth + 1)) })
|
|
29202
|
+
] }, node.id);
|
|
29203
|
+
};
|
|
29204
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `bg-white rounded-2xl shadow-lg border border-gray-100 h-full hover:shadow-xl transition-all duration-300 ease-out backdrop-blur-sm ${className}`, children: [
|
|
29205
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b border-gray-50 bg-gradient-to-br from-slate-50/80 via-white to-blue-50/30", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
|
|
29206
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mr-3", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Folder, { className: "h-5 w-5 text-slate-700" }) }),
|
|
29207
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-slate-900 tracking-tight", children: "Clips Explorer" }) })
|
|
29208
|
+
] }) }),
|
|
29209
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-slate-100/80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
|
|
29210
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "h-5 w-5 text-slate-400 group-focus-within:text-blue-500 transition-colors duration-200" }) }),
|
|
29211
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29212
|
+
"input",
|
|
29213
|
+
{
|
|
29214
|
+
type: "text",
|
|
29215
|
+
placeholder: "Show me idle clips from 10am",
|
|
29216
|
+
value: searchTerm,
|
|
29217
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
29218
|
+
className: "w-full pl-12 pr-4 py-2.5 bg-slate-50/50 border border-slate-200/60 rounded-xl text-sm font-medium placeholder:text-slate-400 placeholder:font-normal focus:outline-none focus:bg-white focus:border-blue-300 focus:ring-4 focus:ring-blue-100/50 transition-all duration-200 hover:border-slate-300 hover:bg-white/80"
|
|
29219
|
+
}
|
|
29220
|
+
),
|
|
29221
|
+
searchTerm && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-0 pr-4 flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 bg-blue-500 rounded-full animate-pulse" }) })
|
|
29222
|
+
] }) }),
|
|
29223
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3 h-[calc(100%-14rem)] overflow-y-auto scrollbar-thin", children: [
|
|
29224
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: filterTree.map((node) => renderNode(node)) }),
|
|
29225
|
+
filterTree.length === 0 && searchTerm && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
|
|
29226
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-300 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "h-12 w-12 mx-auto" }) }),
|
|
29227
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-slate-700 mb-2", children: "No clips found" }),
|
|
29228
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-slate-500 mb-4", children: [
|
|
29229
|
+
'No clips match "',
|
|
29230
|
+
searchTerm,
|
|
29231
|
+
'"'
|
|
29232
|
+
] }),
|
|
29233
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29234
|
+
"button",
|
|
29235
|
+
{
|
|
29236
|
+
onClick: () => setSearchTerm(""),
|
|
29237
|
+
className: "inline-flex items-center px-4 py-2 bg-blue-50 text-blue-600 text-sm font-medium rounded-lg hover:bg-blue-100 transition-colors duration-200",
|
|
29238
|
+
children: "Clear search"
|
|
29239
|
+
}
|
|
29240
|
+
)
|
|
29241
|
+
] })
|
|
29242
|
+
] })
|
|
29243
|
+
] });
|
|
29244
|
+
};
|
|
29245
|
+
var USE_SUPABASE_CLIPS2 = true;
|
|
27694
29246
|
var BottlenecksContent = ({
|
|
27695
29247
|
workspaceId,
|
|
27696
29248
|
workspaceName,
|
|
@@ -27699,17 +29251,9 @@ var BottlenecksContent = ({
|
|
|
27699
29251
|
className
|
|
27700
29252
|
}) => {
|
|
27701
29253
|
const dashboardConfig = useDashboardConfig();
|
|
27702
|
-
const
|
|
27703
|
-
const sopConfig = dashboardConfig?.s3Config?.sopCategories;
|
|
27704
|
-
if (!sopConfig) return null;
|
|
27705
|
-
if (sopConfig.workspaceOverrides && sopConfig.workspaceOverrides[workspaceId]) {
|
|
27706
|
-
return sopConfig.workspaceOverrides[workspaceId];
|
|
27707
|
-
}
|
|
27708
|
-
return sopConfig.default;
|
|
27709
|
-
}, [dashboardConfig, workspaceId]);
|
|
29254
|
+
const { crop: workspaceCrop} = useWorkspaceCrop(workspaceId);
|
|
27710
29255
|
const videoRef = React19.useRef(null);
|
|
27711
|
-
const
|
|
27712
|
-
const initialFilter = sopCategories && sopCategories.length > 0 ? sopCategories[0].id : "low_value";
|
|
29256
|
+
const [initialFilter, setInitialFilter] = React19.useState("");
|
|
27713
29257
|
const currentIndexRef = React19.useRef(0);
|
|
27714
29258
|
const activeFilterRef = React19.useRef(initialFilter);
|
|
27715
29259
|
const isMountedRef = React19.useRef(true);
|
|
@@ -27739,22 +29283,6 @@ var BottlenecksContent = ({
|
|
|
27739
29283
|
React19.useEffect(() => {
|
|
27740
29284
|
currentIndexRef.current = currentIndex;
|
|
27741
29285
|
}, [currentIndex]);
|
|
27742
|
-
const [showTimestampFilter, setShowTimestampFilter] = React19.useState(false);
|
|
27743
|
-
const [timestampStart, setTimestampStart] = React19.useState("");
|
|
27744
|
-
const [timestampEnd, setTimestampEnd] = React19.useState("");
|
|
27745
|
-
React19.useEffect(() => {
|
|
27746
|
-
const handleClickOutside = (event) => {
|
|
27747
|
-
if (timestampFilterRef.current && !timestampFilterRef.current.contains(event.target)) {
|
|
27748
|
-
setShowTimestampFilter(false);
|
|
27749
|
-
}
|
|
27750
|
-
};
|
|
27751
|
-
if (showTimestampFilter) {
|
|
27752
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
27753
|
-
}
|
|
27754
|
-
return () => {
|
|
27755
|
-
document.removeEventListener("mousedown", handleClickOutside);
|
|
27756
|
-
};
|
|
27757
|
-
}, [showTimestampFilter]);
|
|
27758
29286
|
const s3ClipsService = React19.useMemo(() => {
|
|
27759
29287
|
if (!dashboardConfig?.s3Config) {
|
|
27760
29288
|
console.warn("S3 configuration not found in dashboard config");
|
|
@@ -27762,6 +29290,39 @@ var BottlenecksContent = ({
|
|
|
27762
29290
|
}
|
|
27763
29291
|
return videoPrefetchManager.getS3Service(dashboardConfig);
|
|
27764
29292
|
}, [dashboardConfig]);
|
|
29293
|
+
const {
|
|
29294
|
+
clipTypes,
|
|
29295
|
+
isLoading: clipTypesLoading,
|
|
29296
|
+
error: clipTypesError,
|
|
29297
|
+
counts: dynamicCounts
|
|
29298
|
+
} = useClipTypesWithCounts(
|
|
29299
|
+
workspaceId,
|
|
29300
|
+
date || getOperationalDate(),
|
|
29301
|
+
shift || "0"
|
|
29302
|
+
// Use shift || '0' as in the original working code
|
|
29303
|
+
);
|
|
29304
|
+
console.log("[BottlenecksContent] Clip types data:", {
|
|
29305
|
+
clipTypes,
|
|
29306
|
+
clipTypesLength: clipTypes?.length,
|
|
29307
|
+
clipTypesLoading,
|
|
29308
|
+
clipTypesError,
|
|
29309
|
+
dynamicCounts,
|
|
29310
|
+
workspaceId,
|
|
29311
|
+
date: date || getOperationalDate(),
|
|
29312
|
+
shift: shift || "0",
|
|
29313
|
+
USE_SUPABASE_CLIPS: USE_SUPABASE_CLIPS2
|
|
29314
|
+
});
|
|
29315
|
+
React19.useEffect(() => {
|
|
29316
|
+
if (clipTypes.length > 0 && !initialFilter) {
|
|
29317
|
+
const firstWithCounts = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
|
|
29318
|
+
const firstType = firstWithCounts || clipTypes[0];
|
|
29319
|
+
if (firstType) {
|
|
29320
|
+
setInitialFilter(firstType.type);
|
|
29321
|
+
setActiveFilter(firstType.type);
|
|
29322
|
+
activeFilterRef.current = firstType.type;
|
|
29323
|
+
}
|
|
29324
|
+
}
|
|
29325
|
+
}, [clipTypes, dynamicCounts, initialFilter]);
|
|
27765
29326
|
const effectiveShift = React19.useMemo(() => {
|
|
27766
29327
|
if (shift !== void 0 && shift !== null) {
|
|
27767
29328
|
const shiftStr = shift.toString();
|
|
@@ -27780,6 +29341,9 @@ var BottlenecksContent = ({
|
|
|
27780
29341
|
return currentShift.shiftId.toString();
|
|
27781
29342
|
}
|
|
27782
29343
|
}, [shift, date, dashboardConfig]);
|
|
29344
|
+
const mergedCounts = React19.useMemo(() => {
|
|
29345
|
+
return { ...clipCounts, ...dynamicCounts };
|
|
29346
|
+
}, [clipCounts, dynamicCounts]);
|
|
27783
29347
|
const {
|
|
27784
29348
|
data: prefetchData,
|
|
27785
29349
|
isFullyIndexed,
|
|
@@ -27814,17 +29378,22 @@ var BottlenecksContent = ({
|
|
|
27814
29378
|
setHasInitialLoad(true);
|
|
27815
29379
|
return;
|
|
27816
29380
|
}
|
|
27817
|
-
console.log(`[BottlenecksContent] Fetching clip counts
|
|
29381
|
+
console.log(`[BottlenecksContent] Fetching clip counts directly with params:`, {
|
|
29382
|
+
workspaceId,
|
|
29383
|
+
operationalDate,
|
|
29384
|
+
shiftStr
|
|
29385
|
+
});
|
|
27818
29386
|
const fullResult = await s3ClipsService.getClipCounts(
|
|
27819
29387
|
workspaceId,
|
|
27820
29388
|
operationalDate,
|
|
27821
29389
|
shiftStr
|
|
27822
29390
|
// Don't build index - use pagination for cost efficiency
|
|
27823
29391
|
);
|
|
29392
|
+
console.log(`[BottlenecksContent] Direct fetch result:`, fullResult);
|
|
27824
29393
|
if (fullResult) {
|
|
27825
29394
|
const counts = fullResult;
|
|
27826
29395
|
updateClipCounts(counts);
|
|
27827
|
-
console.log(`[BottlenecksContent]
|
|
29396
|
+
console.log(`[BottlenecksContent] Updated clip counts:`, counts);
|
|
27828
29397
|
}
|
|
27829
29398
|
setIsLoading(false);
|
|
27830
29399
|
setHasInitialLoad(true);
|
|
@@ -27845,13 +29414,7 @@ var BottlenecksContent = ({
|
|
|
27845
29414
|
const ensureVideosLoaded = React19.useCallback(async (centerIndex) => {
|
|
27846
29415
|
if (!s3ClipsService || !workspaceId || !isMountedRef.current) return;
|
|
27847
29416
|
const currentFilter = activeFilterRef.current;
|
|
27848
|
-
|
|
27849
|
-
if (sopCategories && sopCategories.length > 0) {
|
|
27850
|
-
const category = sopCategories.find((cat) => cat.id === currentFilter);
|
|
27851
|
-
if (category && category.s3FolderName) {
|
|
27852
|
-
effectiveFilter = category.s3FolderName;
|
|
27853
|
-
}
|
|
27854
|
-
}
|
|
29417
|
+
const effectiveFilter = currentFilter;
|
|
27855
29418
|
const cacheKey = `${effectiveFilter}:${date}:${shift}`;
|
|
27856
29419
|
if (!loadedVideosMapRef.current.has(cacheKey)) {
|
|
27857
29420
|
loadedVideosMapRef.current.set(cacheKey, /* @__PURE__ */ new Set());
|
|
@@ -27860,7 +29423,7 @@ var BottlenecksContent = ({
|
|
|
27860
29423
|
const indicesToLoad = [];
|
|
27861
29424
|
const rangeBefore = 1;
|
|
27862
29425
|
const rangeAfter = 3;
|
|
27863
|
-
for (let i = Math.max(0, centerIndex - rangeBefore); i <= Math.min(
|
|
29426
|
+
for (let i = Math.max(0, centerIndex - rangeBefore); i <= Math.min(mergedCounts[effectiveFilter] - 1, centerIndex + rangeAfter); i++) {
|
|
27864
29427
|
if (!loadedIndices.has(i) && !loadingVideosRef.current.has(i)) {
|
|
27865
29428
|
indicesToLoad.push(i);
|
|
27866
29429
|
}
|
|
@@ -27935,7 +29498,7 @@ var BottlenecksContent = ({
|
|
|
27935
29498
|
} finally {
|
|
27936
29499
|
indicesToLoad.forEach((idx) => loadingVideosRef.current.delete(idx));
|
|
27937
29500
|
}
|
|
27938
|
-
}, [s3ClipsService, workspaceId, clipCounts,
|
|
29501
|
+
}, [s3ClipsService, workspaceId, clipCounts, date, effectiveShift]);
|
|
27939
29502
|
const loadFirstVideoForCategory = React19.useCallback(async (category) => {
|
|
27940
29503
|
if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
|
|
27941
29504
|
const targetCategory = category || activeFilterRef.current;
|
|
@@ -27952,7 +29515,7 @@ var BottlenecksContent = ({
|
|
|
27952
29515
|
try {
|
|
27953
29516
|
const operationalDate = date || getOperationalDate();
|
|
27954
29517
|
const shiftStr = effectiveShift;
|
|
27955
|
-
if (!
|
|
29518
|
+
if (!mergedCounts[targetCategory]) {
|
|
27956
29519
|
const cacheKey = `clip-counts:${workspaceId}:${operationalDate}:${shiftStr}`;
|
|
27957
29520
|
const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
|
|
27958
29521
|
if (cachedResult && cachedResult.counts[targetCategory] > 0) {
|
|
@@ -27988,7 +29551,7 @@ var BottlenecksContent = ({
|
|
|
27988
29551
|
} catch (directError) {
|
|
27989
29552
|
console.warn(`[BottlenecksContent] Direct S3 loading failed, trying video index:`, directError);
|
|
27990
29553
|
}
|
|
27991
|
-
if (
|
|
29554
|
+
if (mergedCounts[targetCategory] > 0) {
|
|
27992
29555
|
try {
|
|
27993
29556
|
const operationalDate2 = date || getOperationalDate();
|
|
27994
29557
|
const shiftStr2 = effectiveShift;
|
|
@@ -28042,47 +29605,27 @@ var BottlenecksContent = ({
|
|
|
28042
29605
|
}
|
|
28043
29606
|
}, [workspaceId, date, effectiveShift, s3ClipsService, fetchClipCounts, updateClipCounts, prefetchData]);
|
|
28044
29607
|
React19.useEffect(() => {
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
29608
|
+
console.log(`[BottlenecksContent] prefetchData update:`, {
|
|
29609
|
+
hasPrefetchData: !!prefetchData,
|
|
29610
|
+
prefetchStatus,
|
|
29611
|
+
prefetchData
|
|
29612
|
+
});
|
|
29613
|
+
if (prefetchData) {
|
|
29614
|
+
const counts = prefetchData.counts || prefetchData;
|
|
29615
|
+
console.log(`[BottlenecksContent] Extracted counts from prefetch:`, counts);
|
|
29616
|
+
if (counts && Object.keys(counts).length > 0) {
|
|
29617
|
+
updateClipCounts(counts);
|
|
29618
|
+
if (!hasInitialLoad) {
|
|
29619
|
+
setIsLoading(false);
|
|
29620
|
+
setHasInitialLoad(true);
|
|
29621
|
+
}
|
|
28051
29622
|
}
|
|
28052
29623
|
}
|
|
28053
29624
|
}, [prefetchData, prefetchStatus, updateClipCounts, hasInitialLoad]);
|
|
28054
29625
|
React19.useEffect(() => {
|
|
28055
|
-
if (s3ClipsService &&
|
|
29626
|
+
if (s3ClipsService && (mergedCounts[activeFilter] || 0) > 0) {
|
|
28056
29627
|
const hasVideosForCurrentFilter = allVideos.some((video) => {
|
|
28057
|
-
|
|
28058
|
-
const selectedCategory = sopCategories.find((cat) => cat.id === activeFilter);
|
|
28059
|
-
if (selectedCategory) {
|
|
28060
|
-
return video.type === selectedCategory.id;
|
|
28061
|
-
}
|
|
28062
|
-
}
|
|
28063
|
-
if (activeFilter === "all") return true;
|
|
28064
|
-
if (activeFilter === "low_value") {
|
|
28065
|
-
return video.type === "low_value";
|
|
28066
|
-
}
|
|
28067
|
-
if (activeFilter === "idle_time") {
|
|
28068
|
-
return video.type === "idle_time";
|
|
28069
|
-
}
|
|
28070
|
-
if (activeFilter === "sop_deviations") {
|
|
28071
|
-
return video.type === "missing_quality_check";
|
|
28072
|
-
}
|
|
28073
|
-
if (activeFilter === "best_cycle_time") {
|
|
28074
|
-
return video.type === "best_cycle_time";
|
|
28075
|
-
}
|
|
28076
|
-
if (activeFilter === "worst_cycle_time") {
|
|
28077
|
-
return video.type === "worst_cycle_time";
|
|
28078
|
-
}
|
|
28079
|
-
if (activeFilter === "cycle_completion") {
|
|
28080
|
-
return video.type === "cycle_completion";
|
|
28081
|
-
}
|
|
28082
|
-
if (activeFilter === "long_cycle_time") {
|
|
28083
|
-
return video.type === "long_cycle_time";
|
|
28084
|
-
}
|
|
28085
|
-
return video.type === "bottleneck" && video.severity === activeFilter;
|
|
29628
|
+
return video.type === activeFilter;
|
|
28086
29629
|
});
|
|
28087
29630
|
if (!hasVideosForCurrentFilter) {
|
|
28088
29631
|
loadFirstVideoForCategory(activeFilter);
|
|
@@ -28090,7 +29633,7 @@ var BottlenecksContent = ({
|
|
|
28090
29633
|
setIsCategoryLoading(false);
|
|
28091
29634
|
}
|
|
28092
29635
|
}
|
|
28093
|
-
}, [activeFilter, s3ClipsService,
|
|
29636
|
+
}, [activeFilter, s3ClipsService, mergedCounts, loadFirstVideoForCategory, allVideos]);
|
|
28094
29637
|
React19.useEffect(() => {
|
|
28095
29638
|
if (previousFilterRef.current !== activeFilter) {
|
|
28096
29639
|
console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
|
|
@@ -28102,15 +29645,7 @@ var BottlenecksContent = ({
|
|
|
28102
29645
|
previousFilterRef.current = activeFilter;
|
|
28103
29646
|
const filtered = allVideos.filter((video) => {
|
|
28104
29647
|
if (activeFilter === "all") return true;
|
|
28105
|
-
|
|
28106
|
-
if (activeFilter === "sop_deviations") return video.type === "missing_quality_check";
|
|
28107
|
-
if (activeFilter === "best_cycle_time") return video.type === "best_cycle_time";
|
|
28108
|
-
if (activeFilter === "worst_cycle_time") return video.type === "worst_cycle_time";
|
|
28109
|
-
if (activeFilter === "cycle_completion") return video.type === "cycle_completion";
|
|
28110
|
-
if (activeFilter === "long_cycle_time") {
|
|
28111
|
-
return video.type === "long_cycle_time";
|
|
28112
|
-
}
|
|
28113
|
-
return video.type === "bottleneck" && video.severity === activeFilter;
|
|
29648
|
+
return video.type === activeFilter;
|
|
28114
29649
|
});
|
|
28115
29650
|
if (filtered.length > 0) {
|
|
28116
29651
|
preloadVideoUrl(filtered[0].src);
|
|
@@ -28119,46 +29654,25 @@ var BottlenecksContent = ({
|
|
|
28119
29654
|
setIsCategoryLoading(false);
|
|
28120
29655
|
}
|
|
28121
29656
|
}, 150);
|
|
28122
|
-
} else if (
|
|
29657
|
+
} else if ((mergedCounts[activeFilter] || 0) === 0) {
|
|
28123
29658
|
if (isMountedRef.current) {
|
|
28124
29659
|
setIsCategoryLoading(false);
|
|
28125
29660
|
}
|
|
28126
29661
|
}
|
|
28127
29662
|
}
|
|
28128
|
-
}, [activeFilter, allVideos,
|
|
29663
|
+
}, [activeFilter, allVideos, mergedCounts]);
|
|
28129
29664
|
const filteredVideos = React19.useMemo(() => {
|
|
28130
29665
|
if (!allVideos) return [];
|
|
28131
29666
|
let filtered = [];
|
|
28132
29667
|
if (activeFilter === "all") {
|
|
28133
29668
|
filtered = [...allVideos];
|
|
28134
29669
|
} else {
|
|
28135
|
-
|
|
28136
|
-
const selectedCategory = sopCategories.find((cat) => cat.id === activeFilter);
|
|
28137
|
-
if (selectedCategory) {
|
|
28138
|
-
filtered = allVideos.filter((video) => video.type === selectedCategory.id);
|
|
28139
|
-
}
|
|
28140
|
-
} else {
|
|
28141
|
-
if (activeFilter === "low_value") {
|
|
28142
|
-
filtered = allVideos.filter((video) => video.type === "low_value");
|
|
28143
|
-
} else if (activeFilter === "sop_deviations") {
|
|
28144
|
-
filtered = allVideos.filter((video) => video.type === "missing_quality_check");
|
|
28145
|
-
} else if (activeFilter === "best_cycle_time") {
|
|
28146
|
-
filtered = allVideos.filter((video) => video.type === "best_cycle_time");
|
|
28147
|
-
} else if (activeFilter === "worst_cycle_time") {
|
|
28148
|
-
filtered = allVideos.filter((video) => video.type === "worst_cycle_time");
|
|
28149
|
-
} else if (activeFilter === "cycle_completion") {
|
|
28150
|
-
filtered = allVideos.filter((video) => video.type === "cycle_completion");
|
|
28151
|
-
} else if (activeFilter === "long_cycle_time") {
|
|
28152
|
-
filtered = allVideos.filter((video) => video.type === "long_cycle_time");
|
|
28153
|
-
} else {
|
|
28154
|
-
filtered = allVideos.filter((video) => video.type === "bottleneck" && video.severity === activeFilter);
|
|
28155
|
-
}
|
|
28156
|
-
}
|
|
29670
|
+
filtered = allVideos.filter((video) => video.type === activeFilter);
|
|
28157
29671
|
}
|
|
28158
29672
|
return filtered.sort((a, b) => {
|
|
28159
29673
|
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
28160
29674
|
});
|
|
28161
|
-
}, [activeFilter, allVideos
|
|
29675
|
+
}, [activeFilter, allVideos]);
|
|
28162
29676
|
React19.useEffect(() => {
|
|
28163
29677
|
if (isNavigating && currentIndex < filteredVideos.length) {
|
|
28164
29678
|
setIsNavigating(false);
|
|
@@ -28183,15 +29697,9 @@ var BottlenecksContent = ({
|
|
|
28183
29697
|
const currentIdx = currentIndexRef.current;
|
|
28184
29698
|
const currentFilter = activeFilterRef.current;
|
|
28185
29699
|
const nextIndex = currentIdx + 1;
|
|
28186
|
-
|
|
28187
|
-
|
|
28188
|
-
|
|
28189
|
-
if (category && category.s3FolderName) {
|
|
28190
|
-
effectiveFilter = category.s3FolderName;
|
|
28191
|
-
}
|
|
28192
|
-
}
|
|
28193
|
-
console.log(`[handleNext] Navigation check: nextIndex=${nextIndex}, currentFilter='${currentFilter}', effectiveFilter='${effectiveFilter}', clipCounts[effectiveFilter]=${clipCounts[effectiveFilter]}, clipCounts keys:`, Object.keys(clipCounts));
|
|
28194
|
-
if (nextIndex < clipCounts[effectiveFilter]) {
|
|
29700
|
+
const effectiveFilter = currentFilter;
|
|
29701
|
+
console.log(`[handleNext] Navigation check: nextIndex=${nextIndex}, currentFilter='${currentFilter}', effectiveFilter='${effectiveFilter}', mergedCounts[effectiveFilter]=${mergedCounts[effectiveFilter]}, mergedCounts keys:`, Object.keys(mergedCounts));
|
|
29702
|
+
if (nextIndex < mergedCounts[effectiveFilter]) {
|
|
28195
29703
|
if (isMountedRef.current) {
|
|
28196
29704
|
setCurrentIndex(nextIndex);
|
|
28197
29705
|
setError(null);
|
|
@@ -28345,17 +29853,6 @@ var BottlenecksContent = ({
|
|
|
28345
29853
|
player.pause();
|
|
28346
29854
|
}
|
|
28347
29855
|
};
|
|
28348
|
-
const mappedCounts = React19.useMemo(() => {
|
|
28349
|
-
const counts = { ...clipCounts };
|
|
28350
|
-
counts.lowValue = counts.low_value || counts.lowValue || 0;
|
|
28351
|
-
counts.bestCycleTimes = counts.best_cycle_time || counts.bestCycleTimes || 0;
|
|
28352
|
-
counts.worstCycleTimes = counts.worst_cycle_time || counts.worstCycleTimes || 0;
|
|
28353
|
-
counts.longCycleTimes = counts.long_cycle_time || counts.longCycleTimes || 0;
|
|
28354
|
-
counts.cycleCompletions = counts.cycle_completion || counts.cycleCompletion || 0;
|
|
28355
|
-
counts.sopDeviations = counts.missing_quality_check || counts.sopDeviations || 0;
|
|
28356
|
-
counts.bottlenecks = counts.bottleneck || counts.bottlenecks || 0;
|
|
28357
|
-
return counts;
|
|
28358
|
-
}, [clipCounts]);
|
|
28359
29856
|
const getClipTypeLabel = (video) => {
|
|
28360
29857
|
if (!video) return "";
|
|
28361
29858
|
switch (video.type) {
|
|
@@ -28378,33 +29875,6 @@ var BottlenecksContent = ({
|
|
|
28378
29875
|
return "";
|
|
28379
29876
|
}
|
|
28380
29877
|
};
|
|
28381
|
-
const getColorClasses = (color2) => {
|
|
28382
|
-
const colorMap = {
|
|
28383
|
-
purple: { text: "text-purple-500", bg: "bg-purple-500", dot: "bg-purple-500" },
|
|
28384
|
-
green: { text: "text-green-600", bg: "bg-green-600", dot: "bg-green-600" },
|
|
28385
|
-
red: { text: "text-red-700", bg: "bg-red-700", dot: "bg-red-700" },
|
|
28386
|
-
"red-dark": { text: "text-red-500", bg: "bg-red-500", dot: "bg-red-500" },
|
|
28387
|
-
blue: { text: "text-blue-600", bg: "bg-blue-600", dot: "bg-blue-600" },
|
|
28388
|
-
orange: { text: "text-orange-600", bg: "bg-orange-600", dot: "bg-orange-600" },
|
|
28389
|
-
yellow: { text: "text-yellow-600", bg: "bg-yellow-600", dot: "bg-yellow-600" },
|
|
28390
|
-
teal: { text: "text-teal-600", bg: "bg-teal-600", dot: "bg-teal-600" },
|
|
28391
|
-
amber: { text: "text-amber-600", bg: "bg-amber-600", dot: "bg-amber-600" },
|
|
28392
|
-
gray: { text: "text-gray-600", bg: "bg-gray-600", dot: "bg-gray-600" }
|
|
28393
|
-
};
|
|
28394
|
-
return colorMap[color2] || colorMap.gray;
|
|
28395
|
-
};
|
|
28396
|
-
const formatTimeOnly = (time2) => {
|
|
28397
|
-
if (!time2) return "";
|
|
28398
|
-
try {
|
|
28399
|
-
const [hours, minutes] = time2.split(":");
|
|
28400
|
-
const hour = parseInt(hours);
|
|
28401
|
-
const ampm = hour >= 12 ? "PM" : "AM";
|
|
28402
|
-
const displayHour = hour % 12 || 12;
|
|
28403
|
-
return `${displayHour}:${minutes} ${ampm}`;
|
|
28404
|
-
} catch {
|
|
28405
|
-
return time2;
|
|
28406
|
-
}
|
|
28407
|
-
};
|
|
28408
29878
|
if (!dashboardConfig?.s3Config) {
|
|
28409
29879
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
|
|
28410
29880
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
|
|
@@ -28412,202 +29882,80 @@ var BottlenecksContent = ({
|
|
|
28412
29882
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: "S3 configuration is required to load video clips. Please check your dashboard configuration." })
|
|
28413
29883
|
] });
|
|
28414
29884
|
}
|
|
28415
|
-
if (isLoading && allVideos.length === 0 && Object.keys(
|
|
29885
|
+
if ((isLoading || clipTypesLoading) && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
|
|
28416
29886
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-grow p-4 flex items-center justify-center h-[calc(100vh-12rem)]", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading clips..." }) });
|
|
28417
29887
|
}
|
|
28418
|
-
if (error) {
|
|
29888
|
+
if (error || clipTypesError) {
|
|
28419
29889
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
|
|
28420
29890
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
|
|
28421
29891
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-red-700 mb-1", children: "Error Loading Clips" }),
|
|
28422
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: error })
|
|
29892
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: error || clipTypesError })
|
|
28423
29893
|
] });
|
|
28424
29894
|
}
|
|
28425
|
-
const categoriesToShow =
|
|
28426
|
-
|
|
28427
|
-
|
|
28428
|
-
|
|
28429
|
-
|
|
28430
|
-
|
|
28431
|
-
|
|
28432
|
-
|
|
28433
|
-
|
|
28434
|
-
|
|
28435
|
-
|
|
28436
|
-
const count = mappedCounts[category.id] || 0;
|
|
28437
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28438
|
-
Card2,
|
|
28439
|
-
{
|
|
28440
|
-
onClick: () => {
|
|
28441
|
-
updateActiveFilter(category.id);
|
|
28442
|
-
trackCoreEvent(`${category.label} Filter Clicked`, {
|
|
28443
|
-
workspaceId,
|
|
28444
|
-
workspaceName,
|
|
28445
|
-
date,
|
|
28446
|
-
filterType: category.id,
|
|
28447
|
-
clipCount: count
|
|
28448
|
-
});
|
|
28449
|
-
},
|
|
28450
|
-
className: `bg-white shadow-sm cursor-pointer transition-all duration-200 hover:bg-gray-50 ${activeFilter === category.id ? "bg-blue-50 shadow-md ring-1 ring-blue-200" : ""}`,
|
|
28451
|
-
"aria-label": `Filter by ${category.label} (${count} clips)`,
|
|
28452
|
-
role: "button",
|
|
28453
|
-
tabIndex: 0,
|
|
28454
|
-
onKeyDown: (e) => e.key === "Enter" && updateActiveFilter(category.id),
|
|
28455
|
-
children: [
|
|
28456
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: `text-lg ${activeFilter === category.id ? "text-blue-600" : ""}`, children: category.label }) }),
|
|
28457
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col justify-center", children: [
|
|
28458
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-3xl font-bold ${colorClasses.text}`, children: count }),
|
|
28459
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center text-sm text-gray-500 mt-1", children: [
|
|
28460
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `h-2 w-2 rounded-full ${colorClasses.dot} mr-1.5` }),
|
|
28461
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: category.subtitle || category.description || category.label })
|
|
28462
|
-
] })
|
|
28463
|
-
] }) })
|
|
28464
|
-
]
|
|
28465
|
-
},
|
|
28466
|
-
category.id
|
|
28467
|
-
);
|
|
28468
|
-
}) }),
|
|
28469
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg shadow-sm overflow-hidden", style: { height: "calc(100% - 8.5rem)" }, children: [
|
|
29895
|
+
const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
|
|
29896
|
+
console.log("[BottlenecksContent] Categories to show:", {
|
|
29897
|
+
categoriesToShow,
|
|
29898
|
+
categoriesToShowLength: categoriesToShow?.length,
|
|
29899
|
+
firstCategory: categoriesToShow?.[0],
|
|
29900
|
+
clipTypesLength: clipTypes?.length,
|
|
29901
|
+
clipTypesError,
|
|
29902
|
+
mergedCounts
|
|
29903
|
+
});
|
|
29904
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-grow p-1.5 sm:p-2 lg:p-4 h-[calc(100vh-12rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col lg:flex-row gap-4 h-full", children: [
|
|
29905
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 lg:flex-[3]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg shadow-sm overflow-hidden h-full", children: [
|
|
28470
29906
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-gray-100", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
28471
29907
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-800", children: (() => {
|
|
28472
29908
|
if (activeFilter === "all") {
|
|
28473
|
-
return `All Clips (${
|
|
29909
|
+
return `All Clips (${mergedCounts.total || 0})`;
|
|
28474
29910
|
}
|
|
28475
|
-
const activeCategory = categoriesToShow.find((cat) => cat.
|
|
29911
|
+
const activeCategory = categoriesToShow.find((cat) => cat.type === activeFilter);
|
|
28476
29912
|
if (activeCategory) {
|
|
28477
|
-
return `${activeCategory.label} (${
|
|
29913
|
+
return `${activeCategory.label} (${mergedCounts[activeCategory.type] || 0})`;
|
|
28478
29914
|
}
|
|
28479
|
-
return activeFilter
|
|
29915
|
+
return `${activeFilter.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} (${mergedCounts[activeFilter] || 0})`;
|
|
28480
29916
|
})() }),
|
|
28481
29917
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
28482
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", ref: timestampFilterRef, children: [
|
|
28483
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28484
|
-
"button",
|
|
28485
|
-
{
|
|
28486
|
-
onClick: () => setShowTimestampFilter(!showTimestampFilter),
|
|
28487
|
-
className: `p-2 rounded-lg transition-all ${timestampStart || timestampEnd ? "bg-blue-50 text-blue-600 hover:bg-blue-100" : "text-gray-600 hover:bg-gray-100"}`,
|
|
28488
|
-
"aria-label": "Filter by time",
|
|
28489
|
-
title: "Filter by time",
|
|
28490
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-5 w-5" })
|
|
28491
|
-
}
|
|
28492
|
-
),
|
|
28493
|
-
showTimestampFilter && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 mt-2 w-80 bg-white rounded-lg shadow-lg border border-gray-200 p-4 z-20", children: [
|
|
28494
|
-
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Filter by Time" }),
|
|
28495
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
28496
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
28497
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-600 mb-1", children: "Start Time" }),
|
|
28498
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28499
|
-
TimePickerDropdown,
|
|
28500
|
-
{
|
|
28501
|
-
value: timestampStart,
|
|
28502
|
-
onChange: (value) => setTimestampStart(value),
|
|
28503
|
-
placeholder: "Select start time",
|
|
28504
|
-
className: "w-full text-sm"
|
|
28505
|
-
}
|
|
28506
|
-
)
|
|
28507
|
-
] }),
|
|
28508
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
28509
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-600 mb-1", children: "End Time" }),
|
|
28510
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28511
|
-
TimePickerDropdown,
|
|
28512
|
-
{
|
|
28513
|
-
value: timestampEnd,
|
|
28514
|
-
onChange: (value) => setTimestampEnd(value),
|
|
28515
|
-
placeholder: "Select end time",
|
|
28516
|
-
className: "w-full text-sm"
|
|
28517
|
-
}
|
|
28518
|
-
)
|
|
28519
|
-
] }),
|
|
28520
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between pt-2", children: [
|
|
28521
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28522
|
-
"button",
|
|
28523
|
-
{
|
|
28524
|
-
onClick: () => {
|
|
28525
|
-
setTimestampStart("");
|
|
28526
|
-
setTimestampEnd("");
|
|
28527
|
-
setShowTimestampFilter(false);
|
|
28528
|
-
},
|
|
28529
|
-
className: "px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 transition-colors",
|
|
28530
|
-
children: "Clear"
|
|
28531
|
-
}
|
|
28532
|
-
),
|
|
28533
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28534
|
-
"button",
|
|
28535
|
-
{
|
|
28536
|
-
onClick: () => {
|
|
28537
|
-
setShowTimestampFilter(false);
|
|
28538
|
-
loadFirstVideoForCategory();
|
|
28539
|
-
},
|
|
28540
|
-
className: "px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
|
|
28541
|
-
children: "Apply Filter"
|
|
28542
|
-
}
|
|
28543
|
-
)
|
|
28544
|
-
] })
|
|
28545
|
-
] })
|
|
28546
|
-
] })
|
|
28547
|
-
] }),
|
|
28548
29918
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28549
29919
|
"button",
|
|
28550
29920
|
{
|
|
28551
29921
|
onClick: handlePrevious,
|
|
28552
|
-
disabled: currentIndex === 0 ||
|
|
28553
|
-
className: `p-2 rounded-full transition-colors ${currentIndex === 0 ||
|
|
29922
|
+
disabled: currentIndex === 0 || (mergedCounts[activeFilter] || 0) === 0,
|
|
29923
|
+
className: `p-2 rounded-full transition-colors ${currentIndex === 0 || (mergedCounts[activeFilter] || 0) === 0 ? "text-gray-300 cursor-not-allowed" : "text-gray-600 hover:bg-gray-100"}`,
|
|
28554
29924
|
"aria-label": "Previous video",
|
|
28555
29925
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-5 w-5" })
|
|
28556
29926
|
}
|
|
28557
29927
|
),
|
|
28558
29928
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [
|
|
28559
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm px-2 py-1 bg-blue-50 text-blue-700 rounded-full font-medium tabular-nums", children:
|
|
29929
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm px-2 py-1 bg-blue-50 text-blue-700 rounded-full font-medium tabular-nums", children: (mergedCounts[activeFilter] || 0) > 0 ? `${currentIndex + 1} / ${mergedCounts[activeFilter]}` : "0 / 0" }),
|
|
28560
29930
|
error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-red-600 font-medium", children: error })
|
|
28561
29931
|
] }),
|
|
28562
29932
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28563
29933
|
"button",
|
|
28564
29934
|
{
|
|
28565
29935
|
onClick: handleNext,
|
|
28566
|
-
disabled: currentIndex >=
|
|
28567
|
-
className: `p-2 rounded-full transition-colors ${currentIndex >=
|
|
29936
|
+
disabled: currentIndex >= (mergedCounts[activeFilter] || 0) - 1 || (mergedCounts[activeFilter] || 0) === 0,
|
|
29937
|
+
className: `p-2 rounded-full transition-colors ${currentIndex >= (mergedCounts[activeFilter] || 0) - 1 || (mergedCounts[activeFilter] || 0) === 0 ? "text-gray-300 cursor-not-allowed" : "text-gray-600 hover:bg-gray-100"}`,
|
|
28568
29938
|
"aria-label": "Next video",
|
|
28569
29939
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5" })
|
|
28570
29940
|
}
|
|
28571
29941
|
)
|
|
28572
29942
|
] })
|
|
28573
29943
|
] }) }),
|
|
28574
|
-
(timestampStart || timestampEnd) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-2 bg-blue-50 border-b border-blue-100 flex items-center justify-between", children: [
|
|
28575
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2 text-sm text-blue-700", children: [
|
|
28576
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4" }),
|
|
28577
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28578
|
-
"Filtered by time: ",
|
|
28579
|
-
timestampStart ? formatTimeOnly(timestampStart) : "Any",
|
|
28580
|
-
" - ",
|
|
28581
|
-
timestampEnd ? formatTimeOnly(timestampEnd) : "Any"
|
|
28582
|
-
] })
|
|
28583
|
-
] }),
|
|
28584
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28585
|
-
"button",
|
|
28586
|
-
{
|
|
28587
|
-
onClick: () => {
|
|
28588
|
-
setTimestampStart("");
|
|
28589
|
-
setTimestampEnd("");
|
|
28590
|
-
loadFirstVideoForCategory();
|
|
28591
|
-
},
|
|
28592
|
-
className: "text-sm text-blue-600 hover:text-blue-800 transition-colors",
|
|
28593
|
-
children: "Clear filter"
|
|
28594
|
-
}
|
|
28595
|
-
)
|
|
28596
|
-
] }),
|
|
28597
29944
|
/* Priority 1: Show loading if initial load hasn't completed yet */
|
|
28598
29945
|
isLoading && !hasInitialLoad ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading clips..." }) }) }) }) : (
|
|
28599
29946
|
/* Priority 2: Show loading if category is loading BUT only if no video is available */
|
|
28600
29947
|
isCategoryLoading && (!filteredVideos.length || !currentVideo) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading videos..." }) }) }) }) : (
|
|
28601
29948
|
/* Priority 3: Show loading if navigating and current video not available */
|
|
28602
|
-
isNavigating || currentIndex >= filteredVideos.length && currentIndex <
|
|
29949
|
+
isNavigating || currentIndex >= filteredVideos.length && currentIndex < (mergedCounts[activeFilter] || 0) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }) }) }) : (
|
|
28603
29950
|
/* Priority 4: Show video if we have filtered videos and current video */
|
|
28604
29951
|
filteredVideos.length > 0 && currentVideo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-full group", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900", children: [
|
|
28605
29952
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28606
|
-
|
|
29953
|
+
CroppedVideoPlayer,
|
|
28607
29954
|
{
|
|
28608
29955
|
ref: videoRef,
|
|
28609
29956
|
src: currentVideo.src,
|
|
28610
29957
|
className: "w-full h-full",
|
|
29958
|
+
crop: workspaceCrop?.crop || null,
|
|
28611
29959
|
autoplay: true,
|
|
28612
29960
|
playsInline: true,
|
|
28613
29961
|
loop: false,
|
|
@@ -28661,7 +30009,7 @@ var BottlenecksContent = ({
|
|
|
28661
30009
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${currentVideo.type === "low_value" ? "bg-purple-400" : currentVideo.type === "best_cycle_time" ? "bg-green-600" : currentVideo.type === "worst_cycle_time" ? "bg-red-700" : currentVideo.type === "cycle_completion" ? "bg-blue-600" : "bg-red-500"} mr-2 animate-pulse` }),
|
|
28662
30010
|
(currentVideo.type === "best_cycle_time" || currentVideo.type === "worst_cycle_time" || currentVideo.type === "cycle_completion" || currentVideo.type === "bottleneck" && currentVideo.description.toLowerCase().includes("cycle time")) && currentVideo.cycle_time_seconds ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "opacity-90 font-mono bg-black/30 px-2 py-0.5 rounded", children: [
|
|
28663
30011
|
"Cycle time: ",
|
|
28664
|
-
|
|
30012
|
+
currentVideo.cycle_time_seconds.toFixed(1),
|
|
28665
30013
|
"s"
|
|
28666
30014
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
28667
30015
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
|
|
@@ -28698,9 +30046,8 @@ var BottlenecksContent = ({
|
|
|
28698
30046
|
max: duration || 0,
|
|
28699
30047
|
value: currentTime,
|
|
28700
30048
|
onChange: (e) => {
|
|
28701
|
-
|
|
28702
|
-
|
|
28703
|
-
player.currentTime(Number(e.target.value));
|
|
30049
|
+
if (videoRef.current) {
|
|
30050
|
+
videoRef.current.currentTime(Number(e.target.value));
|
|
28704
30051
|
}
|
|
28705
30052
|
},
|
|
28706
30053
|
className: "flex-grow mx-3 h-2.5 bg-white/30 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-white/50 touch-manipulation",
|
|
@@ -28714,13 +30061,13 @@ var BottlenecksContent = ({
|
|
|
28714
30061
|
] }) })
|
|
28715
30062
|
] }) }) }) : (
|
|
28716
30063
|
/* Priority 5: Show "no clips found" only if we have counts and there are truly no clips for workspace */
|
|
28717
|
-
hasInitialLoad && Object.keys(
|
|
30064
|
+
hasInitialLoad && Object.keys(mergedCounts).length > 0 && Object.values(mergedCounts).every((count) => count === 0) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
|
|
28718
30065
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
|
|
28719
30066
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Clips Found" }),
|
|
28720
30067
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There were no video clips found for this workspace today." })
|
|
28721
30068
|
] }) }) : (
|
|
28722
30069
|
/* Priority 6: Show "no matching clips" only if we have data loaded and specifically no clips for this filter */
|
|
28723
|
-
hasInitialLoad &&
|
|
30070
|
+
hasInitialLoad && (mergedCounts[activeFilter] || 0) === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
|
|
28724
30071
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
|
|
28725
30072
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Matching Clips" }),
|
|
28726
30073
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "There are no clips matching the selected filter." })
|
|
@@ -28733,8 +30080,46 @@ var BottlenecksContent = ({
|
|
|
28733
30080
|
)
|
|
28734
30081
|
)
|
|
28735
30082
|
)
|
|
28736
|
-
] })
|
|
28737
|
-
|
|
30083
|
+
] }) }),
|
|
30084
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 lg:flex-[1] lg:min-w-[280px] lg:max-w-[320px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
30085
|
+
FileManagerFilters,
|
|
30086
|
+
{
|
|
30087
|
+
categories: categoriesToShow.map((cat) => ({
|
|
30088
|
+
...cat,
|
|
30089
|
+
id: cat.type
|
|
30090
|
+
// Map type to id for compatibility with FileManagerFilters
|
|
30091
|
+
})),
|
|
30092
|
+
videos: allVideos || [],
|
|
30093
|
+
activeFilter,
|
|
30094
|
+
currentVideoId: currentVideo?.id,
|
|
30095
|
+
counts: mergedCounts,
|
|
30096
|
+
onFilterChange: (filterId) => {
|
|
30097
|
+
updateActiveFilter(filterId);
|
|
30098
|
+
const category = categoriesToShow.find((cat) => cat.type === filterId);
|
|
30099
|
+
if (category) {
|
|
30100
|
+
trackCoreEvent(`${category.label} Filter Clicked`, {
|
|
30101
|
+
workspaceId,
|
|
30102
|
+
workspaceName,
|
|
30103
|
+
date,
|
|
30104
|
+
filterType: filterId,
|
|
30105
|
+
clipCount: mergedCounts[filterId] || 0
|
|
30106
|
+
});
|
|
30107
|
+
}
|
|
30108
|
+
},
|
|
30109
|
+
onVideoSelect: (videoIndex) => {
|
|
30110
|
+
setCurrentIndex(videoIndex);
|
|
30111
|
+
const selectedVideo = allVideos?.[videoIndex];
|
|
30112
|
+
if (selectedVideo) {
|
|
30113
|
+
const videoCategory = categoriesToShow.find((cat) => cat.type === selectedVideo.type);
|
|
30114
|
+
if (videoCategory && activeFilter !== videoCategory.type) {
|
|
30115
|
+
updateActiveFilter(videoCategory.type);
|
|
30116
|
+
}
|
|
30117
|
+
}
|
|
30118
|
+
},
|
|
30119
|
+
className: "h-full"
|
|
30120
|
+
}
|
|
30121
|
+
) })
|
|
30122
|
+
] }) });
|
|
28738
30123
|
};
|
|
28739
30124
|
var getEfficiencyColor = (efficiency) => {
|
|
28740
30125
|
if (efficiency >= 80) {
|
|
@@ -30363,6 +31748,17 @@ var SideNavBar = React19.memo(({
|
|
|
30363
31748
|
});
|
|
30364
31749
|
onMobileMenuClose?.();
|
|
30365
31750
|
}, [navigate, onMobileMenuClose]);
|
|
31751
|
+
const handleSupervisorManagementClick = React19.useCallback(() => {
|
|
31752
|
+
navigate("/supervisor-management", {
|
|
31753
|
+
trackingEvent: {
|
|
31754
|
+
name: "Supervisor Management Page Clicked",
|
|
31755
|
+
properties: {
|
|
31756
|
+
source: "side_nav"
|
|
31757
|
+
}
|
|
31758
|
+
}
|
|
31759
|
+
});
|
|
31760
|
+
onMobileMenuClose?.();
|
|
31761
|
+
}, [navigate, onMobileMenuClose]);
|
|
30366
31762
|
const handleAIAgentClick = React19.useCallback(() => {
|
|
30367
31763
|
navigate("/ai-agent", {
|
|
30368
31764
|
trackingEvent: {
|
|
@@ -30427,6 +31823,7 @@ var SideNavBar = React19.memo(({
|
|
|
30427
31823
|
const kpisButtonClasses = React19.useMemo(() => getButtonClasses("/kpis"), [getButtonClasses, pathname]);
|
|
30428
31824
|
const targetsButtonClasses = React19.useMemo(() => getButtonClasses("/targets"), [getButtonClasses, pathname]);
|
|
30429
31825
|
const shiftsButtonClasses = React19.useMemo(() => getButtonClasses("/shifts"), [getButtonClasses, pathname]);
|
|
31826
|
+
const supervisorManagementButtonClasses = React19.useMemo(() => getButtonClasses("/supervisor-management"), [getButtonClasses, pathname]);
|
|
30430
31827
|
const aiAgentButtonClasses = React19.useMemo(() => getButtonClasses("/ai-agent"), [getButtonClasses, pathname]);
|
|
30431
31828
|
const profileButtonClasses = React19.useMemo(() => getButtonClasses("/profile"), [getButtonClasses, pathname]);
|
|
30432
31829
|
const helpButtonClasses = React19.useMemo(() => getButtonClasses("/help"), [getButtonClasses, pathname]);
|
|
@@ -30532,7 +31929,22 @@ var SideNavBar = React19.memo(({
|
|
|
30532
31929
|
]
|
|
30533
31930
|
}
|
|
30534
31931
|
),
|
|
30535
|
-
|
|
31932
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
31933
|
+
"button",
|
|
31934
|
+
{
|
|
31935
|
+
onClick: handleSupervisorManagementClick,
|
|
31936
|
+
className: supervisorManagementButtonClasses,
|
|
31937
|
+
"aria-label": "Supervisor Management",
|
|
31938
|
+
tabIndex: 0,
|
|
31939
|
+
role: "tab",
|
|
31940
|
+
"aria-selected": pathname === "/supervisor-management" || pathname.startsWith("/supervisor-management/"),
|
|
31941
|
+
children: [
|
|
31942
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.UsersIcon, { className: "w-5 h-5 mb-1" }),
|
|
31943
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight", children: "Supervisors" })
|
|
31944
|
+
]
|
|
31945
|
+
}
|
|
31946
|
+
),
|
|
31947
|
+
skuEnabled && true && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
30536
31948
|
"button",
|
|
30537
31949
|
{
|
|
30538
31950
|
onClick: handleSKUsClick,
|
|
@@ -30558,7 +31970,10 @@ var SideNavBar = React19.memo(({
|
|
|
30558
31970
|
"aria-selected": pathname === "/ai-agent" || pathname.startsWith("/ai-agent/"),
|
|
30559
31971
|
children: [
|
|
30560
31972
|
/* @__PURE__ */ jsxRuntime.jsx(outline.SparklesIcon, { className: "w-5 h-5 mb-1" }),
|
|
30561
|
-
/* @__PURE__ */ jsxRuntime.
|
|
31973
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center", children: [
|
|
31974
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight", children: "Axel" }),
|
|
31975
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px] px-1.5 py-0.5 bg-orange-100 text-orange-600 rounded-full font-medium leading-none mt-0.5", children: "BETA" })
|
|
31976
|
+
] })
|
|
30562
31977
|
]
|
|
30563
31978
|
}
|
|
30564
31979
|
),
|
|
@@ -30692,7 +32107,19 @@ var SideNavBar = React19.memo(({
|
|
|
30692
32107
|
]
|
|
30693
32108
|
}
|
|
30694
32109
|
),
|
|
30695
|
-
|
|
32110
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
32111
|
+
"button",
|
|
32112
|
+
{
|
|
32113
|
+
onClick: handleMobileNavClick(handleSupervisorManagementClick),
|
|
32114
|
+
className: getMobileButtonClass("/supervisor-management"),
|
|
32115
|
+
"aria-label": "Supervisor Management",
|
|
32116
|
+
children: [
|
|
32117
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.UsersIcon, { className: getIconClass("/supervisor-management") }),
|
|
32118
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-medium", children: "Supervisors" })
|
|
32119
|
+
]
|
|
32120
|
+
}
|
|
32121
|
+
),
|
|
32122
|
+
skuEnabled && true && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
30696
32123
|
"button",
|
|
30697
32124
|
{
|
|
30698
32125
|
onClick: handleMobileNavClick(handleSKUsClick),
|
|
@@ -30712,7 +32139,10 @@ var SideNavBar = React19.memo(({
|
|
|
30712
32139
|
"aria-label": "AI Manufacturing Expert",
|
|
30713
32140
|
children: [
|
|
30714
32141
|
/* @__PURE__ */ jsxRuntime.jsx(outline.SparklesIcon, { className: getIconClass("/ai-agent") }),
|
|
30715
|
-
/* @__PURE__ */ jsxRuntime.
|
|
32142
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
32143
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-medium", children: "Axel AI" }),
|
|
32144
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs px-2 py-0.5 bg-orange-100 text-orange-600 rounded-full font-medium leading-none mt-1 self-start", children: "BETA" })
|
|
32145
|
+
] })
|
|
30716
32146
|
]
|
|
30717
32147
|
}
|
|
30718
32148
|
),
|
|
@@ -31231,6 +32661,1205 @@ var SingleVideoStream = ({
|
|
|
31231
32661
|
);
|
|
31232
32662
|
};
|
|
31233
32663
|
var SingleVideoStream_default = SingleVideoStream;
|
|
32664
|
+
var SupervisorDropdown = ({
|
|
32665
|
+
selectedSupervisor,
|
|
32666
|
+
availableSupervisors,
|
|
32667
|
+
onSelect,
|
|
32668
|
+
className = "",
|
|
32669
|
+
disabled = false,
|
|
32670
|
+
placeholder = "Select Supervisor"
|
|
32671
|
+
}) => {
|
|
32672
|
+
console.log(`[SupervisorDropdown] Rendered with ${availableSupervisors.length} available supervisors:`, availableSupervisors.map((s) => ({ id: s.id, name: s.name })));
|
|
32673
|
+
const [isOpen, setIsOpen] = React19.useState(false);
|
|
32674
|
+
const [searchTerm, setSearchTerm] = React19.useState("");
|
|
32675
|
+
const [dropdownPosition, setDropdownPosition] = React19.useState({ top: 0, left: 0, width: 0 });
|
|
32676
|
+
const dropdownRef = React19.useRef(null);
|
|
32677
|
+
const buttonRef = React19.useRef(null);
|
|
32678
|
+
const inputRef = React19.useRef(null);
|
|
32679
|
+
const filteredSupervisors = availableSupervisors.filter(
|
|
32680
|
+
(supervisor) => supervisor.name.toLowerCase().includes(searchTerm.toLowerCase()) || supervisor.email && supervisor.email.toLowerCase().includes(searchTerm.toLowerCase())
|
|
32681
|
+
);
|
|
32682
|
+
const calculatePosition = () => {
|
|
32683
|
+
if (buttonRef.current) {
|
|
32684
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
32685
|
+
setDropdownPosition({
|
|
32686
|
+
top: rect.bottom + window.scrollY + 4,
|
|
32687
|
+
left: rect.left + window.scrollX,
|
|
32688
|
+
width: rect.width
|
|
32689
|
+
});
|
|
32690
|
+
}
|
|
32691
|
+
};
|
|
32692
|
+
React19.useEffect(() => {
|
|
32693
|
+
const handleClickOutside = (event) => {
|
|
32694
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target) && buttonRef.current && !buttonRef.current.contains(event.target)) {
|
|
32695
|
+
setIsOpen(false);
|
|
32696
|
+
setSearchTerm("");
|
|
32697
|
+
}
|
|
32698
|
+
};
|
|
32699
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
32700
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
32701
|
+
}, []);
|
|
32702
|
+
const handleSelect = (supervisor) => {
|
|
32703
|
+
onSelect(supervisor);
|
|
32704
|
+
setIsOpen(false);
|
|
32705
|
+
setSearchTerm("");
|
|
32706
|
+
};
|
|
32707
|
+
const handleKeyDown = (e) => {
|
|
32708
|
+
if (e.key === "Escape") {
|
|
32709
|
+
setIsOpen(false);
|
|
32710
|
+
setSearchTerm("");
|
|
32711
|
+
}
|
|
32712
|
+
};
|
|
32713
|
+
const handleToggle = () => {
|
|
32714
|
+
if (!disabled) {
|
|
32715
|
+
if (!isOpen) {
|
|
32716
|
+
calculatePosition();
|
|
32717
|
+
}
|
|
32718
|
+
setIsOpen(!isOpen);
|
|
32719
|
+
}
|
|
32720
|
+
};
|
|
32721
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative", className), children: [
|
|
32722
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
32723
|
+
"button",
|
|
32724
|
+
{
|
|
32725
|
+
ref: buttonRef,
|
|
32726
|
+
type: "button",
|
|
32727
|
+
onClick: handleToggle,
|
|
32728
|
+
className: cn(
|
|
32729
|
+
"w-full flex items-center justify-between px-3 py-2 text-left bg-white border rounded-md shadow-sm transition-colors",
|
|
32730
|
+
disabled ? "bg-gray-100 cursor-not-allowed border-gray-200 text-gray-400" : "hover:bg-gray-50 cursor-pointer border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
|
32731
|
+
isOpen && !disabled && "ring-2 ring-blue-500 border-blue-500"
|
|
32732
|
+
),
|
|
32733
|
+
disabled,
|
|
32734
|
+
children: [
|
|
32735
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center min-w-0 flex-1", children: [
|
|
32736
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "w-4 h-4 text-gray-400 mr-2 flex-shrink-0" }),
|
|
32737
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
|
|
32738
|
+
"truncate",
|
|
32739
|
+
selectedSupervisor ? "text-gray-900" : "text-gray-500"
|
|
32740
|
+
), children: selectedSupervisor ? selectedSupervisor.name : placeholder })
|
|
32741
|
+
] }),
|
|
32742
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn(
|
|
32743
|
+
"w-4 h-4 text-gray-400 transition-transform flex-shrink-0 ml-2",
|
|
32744
|
+
isOpen ? "rotate-180" : ""
|
|
32745
|
+
) })
|
|
32746
|
+
]
|
|
32747
|
+
}
|
|
32748
|
+
),
|
|
32749
|
+
isOpen && !disabled && typeof document !== "undefined" && reactDom.createPortal(
|
|
32750
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
32751
|
+
"div",
|
|
32752
|
+
{
|
|
32753
|
+
ref: dropdownRef,
|
|
32754
|
+
className: "fixed z-50 bg-white border border-gray-300 rounded-md shadow-lg",
|
|
32755
|
+
style: {
|
|
32756
|
+
top: dropdownPosition.top,
|
|
32757
|
+
left: dropdownPosition.left,
|
|
32758
|
+
width: dropdownPosition.width,
|
|
32759
|
+
minWidth: "200px"
|
|
32760
|
+
},
|
|
32761
|
+
children: [
|
|
32762
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
32763
|
+
"input",
|
|
32764
|
+
{
|
|
32765
|
+
ref: inputRef,
|
|
32766
|
+
type: "text",
|
|
32767
|
+
placeholder: "Search supervisors...",
|
|
32768
|
+
value: searchTerm,
|
|
32769
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
32770
|
+
onKeyDown: handleKeyDown,
|
|
32771
|
+
className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500",
|
|
32772
|
+
autoFocus: true
|
|
32773
|
+
}
|
|
32774
|
+
) }),
|
|
32775
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-60 overflow-y-auto", children: [
|
|
32776
|
+
selectedSupervisor && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
32777
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
32778
|
+
"button",
|
|
32779
|
+
{
|
|
32780
|
+
type: "button",
|
|
32781
|
+
onClick: () => handleSelect(null),
|
|
32782
|
+
className: "w-full px-3 py-2 text-left text-sm hover:bg-gray-100 focus:outline-none focus:bg-gray-100 flex items-center text-red-600",
|
|
32783
|
+
children: [
|
|
32784
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 mr-2" }),
|
|
32785
|
+
" ",
|
|
32786
|
+
"Clear Assignment"
|
|
32787
|
+
]
|
|
32788
|
+
}
|
|
32789
|
+
),
|
|
32790
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200" })
|
|
32791
|
+
] }),
|
|
32792
|
+
filteredSupervisors.length > 0 ? filteredSupervisors.map((supervisor) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
32793
|
+
"button",
|
|
32794
|
+
{
|
|
32795
|
+
type: "button",
|
|
32796
|
+
onClick: () => handleSelect(supervisor),
|
|
32797
|
+
className: cn(
|
|
32798
|
+
"w-full px-3 py-2 text-left text-sm hover:bg-gray-100 focus:outline-none focus:bg-gray-100 flex items-center justify-between",
|
|
32799
|
+
selectedSupervisor?.id === supervisor.id && "bg-blue-50 text-blue-700"
|
|
32800
|
+
),
|
|
32801
|
+
children: [
|
|
32802
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center min-w-0 flex-1", children: [
|
|
32803
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "w-4 h-4 text-gray-400 mr-2 flex-shrink-0" }),
|
|
32804
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
32805
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium truncate", children: supervisor.name }),
|
|
32806
|
+
supervisor.email && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500 truncate", children: supervisor.email })
|
|
32807
|
+
] })
|
|
32808
|
+
] }),
|
|
32809
|
+
selectedSupervisor?.id === supervisor.id && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
|
|
32810
|
+
]
|
|
32811
|
+
},
|
|
32812
|
+
supervisor.id
|
|
32813
|
+
)) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-gray-500 text-center", children: searchTerm ? "No supervisors found" : "No supervisors available" })
|
|
32814
|
+
] })
|
|
32815
|
+
]
|
|
32816
|
+
}
|
|
32817
|
+
),
|
|
32818
|
+
document.body
|
|
32819
|
+
)
|
|
32820
|
+
] });
|
|
32821
|
+
};
|
|
32822
|
+
var SupervisorDropdown_default = SupervisorDropdown;
|
|
32823
|
+
|
|
32824
|
+
// src/lib/hooks/useFirstTimeLogin.ts
|
|
32825
|
+
var useFirstTimeLogin = () => {
|
|
32826
|
+
const { user, markFirstLoginCompleted } = useAuth();
|
|
32827
|
+
const hasCompletedFirstLogin = user?.first_login_completed ?? false;
|
|
32828
|
+
const needsOnboarding = user ? !hasCompletedFirstLogin : false;
|
|
32829
|
+
const completeFirstLogin = async () => {
|
|
32830
|
+
return await markFirstLoginCompleted();
|
|
32831
|
+
};
|
|
32832
|
+
return {
|
|
32833
|
+
isFirstTimeLogin: !hasCompletedFirstLogin,
|
|
32834
|
+
// Simple boolean check
|
|
32835
|
+
hasCompletedFirstLogin,
|
|
32836
|
+
needsOnboarding,
|
|
32837
|
+
completeFirstLogin,
|
|
32838
|
+
user
|
|
32839
|
+
};
|
|
32840
|
+
};
|
|
32841
|
+
var SimpleOnboardingPopup = ({
|
|
32842
|
+
onComplete,
|
|
32843
|
+
isCompleting = false
|
|
32844
|
+
}) => {
|
|
32845
|
+
const [currentStep, setCurrentStep] = React19.useState(0);
|
|
32846
|
+
const steps = [
|
|
32847
|
+
{
|
|
32848
|
+
title: "Welcome to Optifye Dashboard! \u{1F389}",
|
|
32849
|
+
content: "We're excited to have you here. This dashboard helps you monitor and optimize your factory operations in real-time.",
|
|
32850
|
+
icon: "\u{1F44B}"
|
|
32851
|
+
},
|
|
32852
|
+
{
|
|
32853
|
+
title: "Real-Time Monitoring",
|
|
32854
|
+
content: "Track your production lines, monitor efficiency, and get instant alerts about any issues.",
|
|
32855
|
+
icon: "\u{1F4CA}"
|
|
32856
|
+
},
|
|
32857
|
+
{
|
|
32858
|
+
title: "Data-Driven Insights",
|
|
32859
|
+
content: "Make informed decisions with comprehensive analytics and historical data at your fingertips.",
|
|
32860
|
+
icon: "\u{1F4A1}"
|
|
32861
|
+
}
|
|
32862
|
+
];
|
|
32863
|
+
const handleNext = () => {
|
|
32864
|
+
if (currentStep < steps.length - 1) {
|
|
32865
|
+
setCurrentStep(currentStep + 1);
|
|
32866
|
+
} else {
|
|
32867
|
+
onComplete();
|
|
32868
|
+
}
|
|
32869
|
+
};
|
|
32870
|
+
const handleSkip = () => {
|
|
32871
|
+
onComplete();
|
|
32872
|
+
};
|
|
32873
|
+
const currentStepData = steps[currentStep];
|
|
32874
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 z-[9998] flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg shadow-2xl max-w-md w-full mx-4 relative z-[9999] transform transition-all duration-300 scale-95 animate-pulse", children: [
|
|
32875
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
32876
|
+
"button",
|
|
32877
|
+
{
|
|
32878
|
+
onClick: handleSkip,
|
|
32879
|
+
className: "absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors",
|
|
32880
|
+
disabled: isCompleting,
|
|
32881
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 20 })
|
|
32882
|
+
}
|
|
32883
|
+
),
|
|
32884
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-8", children: [
|
|
32885
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-5xl mb-4 text-center", children: currentStepData.icon }),
|
|
32886
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-2xl font-bold text-gray-900 mb-4 text-center", children: currentStepData.title }),
|
|
32887
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 text-center mb-8", children: currentStepData.content }),
|
|
32888
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center space-x-2 mb-6", children: steps.map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
32889
|
+
"div",
|
|
32890
|
+
{
|
|
32891
|
+
className: `h-2 w-2 rounded-full transition-colors ${index === currentStep ? "bg-blue-600" : "bg-gray-300"}`
|
|
32892
|
+
},
|
|
32893
|
+
index
|
|
32894
|
+
)) }),
|
|
32895
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
32896
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
32897
|
+
"button",
|
|
32898
|
+
{
|
|
32899
|
+
onClick: handleSkip,
|
|
32900
|
+
className: "text-gray-500 hover:text-gray-700 text-sm transition-colors",
|
|
32901
|
+
disabled: isCompleting,
|
|
32902
|
+
children: "Skip tour"
|
|
32903
|
+
}
|
|
32904
|
+
),
|
|
32905
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
32906
|
+
"button",
|
|
32907
|
+
{
|
|
32908
|
+
onClick: handleNext,
|
|
32909
|
+
disabled: isCompleting,
|
|
32910
|
+
className: "bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
32911
|
+
children: isCompleting ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center", children: [
|
|
32912
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "animate-spin -ml-1 mr-2 h-4 w-4 text-white", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
|
|
32913
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
32914
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
|
|
32915
|
+
] }),
|
|
32916
|
+
"Loading..."
|
|
32917
|
+
] }) : currentStep === steps.length - 1 ? "Get Started" : "Next"
|
|
32918
|
+
}
|
|
32919
|
+
)
|
|
32920
|
+
] })
|
|
32921
|
+
] })
|
|
32922
|
+
] }) }) });
|
|
32923
|
+
};
|
|
32924
|
+
var MinimalOnboardingPopup = ({
|
|
32925
|
+
onComplete,
|
|
32926
|
+
isCompleting = false
|
|
32927
|
+
}) => {
|
|
32928
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 z-[9998] flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white rounded-lg shadow-2xl max-w-sm w-full mx-4 relative z-[9999]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 text-center", children: [
|
|
32929
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-5xl mb-4", children: "\u{1F389}" }),
|
|
32930
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-bold text-gray-900 mb-3", children: "Welcome to Optifye!" }),
|
|
32931
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-6", children: "Your dashboard is ready. Let's get started!" }),
|
|
32932
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
32933
|
+
"button",
|
|
32934
|
+
{
|
|
32935
|
+
onClick: onComplete,
|
|
32936
|
+
disabled: isCompleting,
|
|
32937
|
+
className: "w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
32938
|
+
children: isCompleting ? "Loading..." : "Get Started"
|
|
32939
|
+
}
|
|
32940
|
+
)
|
|
32941
|
+
] }) }) }) });
|
|
32942
|
+
};
|
|
32943
|
+
var onboardingSteps = [
|
|
32944
|
+
{
|
|
32945
|
+
id: "welcome",
|
|
32946
|
+
title: "Welcome to Optifye.ai \u{1F44B}",
|
|
32947
|
+
description: "Let's take a quick interactive tour to help you master our intelligent manufacturing monitoring platform. You'll be able to interact with the dashboard as we guide you.",
|
|
32948
|
+
position: "center",
|
|
32949
|
+
nextTrigger: "button"
|
|
32950
|
+
},
|
|
32951
|
+
{
|
|
32952
|
+
id: "live-streams",
|
|
32953
|
+
title: "Live Manufacturing Streams",
|
|
32954
|
+
description: "These are real-time video feeds from your production lines. Each stream shows the current efficiency with color-coded overlays.",
|
|
32955
|
+
target: '.workspace-grid, [class*="workspace-grid"], [class*="grid"], [class*="workspace-container"]',
|
|
32956
|
+
position: "auto",
|
|
32957
|
+
spotlight: true,
|
|
32958
|
+
allowInteraction: false,
|
|
32959
|
+
nextTrigger: "button"
|
|
32960
|
+
},
|
|
32961
|
+
{
|
|
32962
|
+
id: "efficiency-legend",
|
|
32963
|
+
title: "Understanding Efficiency Colors",
|
|
32964
|
+
description: "Green (80-100%): Optimal performance\nYellow (70-79%): Needs attention\nRed (<70%): Critical issues\nFlashing red (<50%): Immediate action required",
|
|
32965
|
+
target: '[class*="efficiency"], [class*="legend"], [class*="indicator"]',
|
|
32966
|
+
position: "auto",
|
|
32967
|
+
spotlight: true,
|
|
32968
|
+
allowInteraction: false,
|
|
32969
|
+
nextTrigger: "button"
|
|
32970
|
+
},
|
|
32971
|
+
{
|
|
32972
|
+
id: "click-workspace",
|
|
32973
|
+
title: "Dive Into a Workspace",
|
|
32974
|
+
description: "Click on any video stream to explore detailed performance metrics for that production line.",
|
|
32975
|
+
target: '.workspace-card, [class*="workspace-item"], [class*="workspace-card"], [class*="video-card"]',
|
|
32976
|
+
actionTarget: '.workspace-card, [class*="workspace-item"], [class*="workspace-card"], [class*="video-card"]',
|
|
32977
|
+
position: "auto",
|
|
32978
|
+
action: "click",
|
|
32979
|
+
spotlight: true,
|
|
32980
|
+
allowInteraction: true,
|
|
32981
|
+
nextTrigger: "action",
|
|
32982
|
+
waitForPath: "/workspace"
|
|
32983
|
+
},
|
|
32984
|
+
{
|
|
32985
|
+
id: "workspace-metrics",
|
|
32986
|
+
title: "Real-Time Performance Dashboard",
|
|
32987
|
+
description: "Excellent! Here you can monitor OEE (Overall Equipment Effectiveness), production rates, quality metrics, and identify bottlenecks in real-time.",
|
|
32988
|
+
target: '[class*="metrics"], [class*="dashboard-content"], [class*="workspace-detail"], main',
|
|
32989
|
+
position: "auto",
|
|
32990
|
+
spotlight: true,
|
|
32991
|
+
allowInteraction: false,
|
|
32992
|
+
nextTrigger: "button"
|
|
32993
|
+
},
|
|
32994
|
+
{
|
|
32995
|
+
id: "navigate-back",
|
|
32996
|
+
title: "Navigate Back Home",
|
|
32997
|
+
description: "Click on the Home tab in the sidebar to return to the main dashboard.",
|
|
32998
|
+
target: '[data-nav-item="home"], [href="/"], nav a:has-text("Home"), nav button:has-text("Home"), tab:has-text("Home")',
|
|
32999
|
+
actionTarget: '[data-nav-item="home"], [href="/"], nav a:has-text("Home"), nav button:has-text("Home"), tab:has-text("Home")',
|
|
33000
|
+
position: "right",
|
|
33001
|
+
action: "click",
|
|
33002
|
+
spotlight: true,
|
|
33003
|
+
allowInteraction: true,
|
|
33004
|
+
nextTrigger: "action",
|
|
33005
|
+
waitForPath: "/"
|
|
33006
|
+
},
|
|
33007
|
+
{
|
|
33008
|
+
id: "complete",
|
|
33009
|
+
title: "\u{1F389} Tour Complete!",
|
|
33010
|
+
description: "You're all set to optimize your manufacturing operations with Optifye.ai. Explore the platform to discover more powerful features like Clips, Targets, and AI insights.",
|
|
33011
|
+
position: "center",
|
|
33012
|
+
nextTrigger: "button"
|
|
33013
|
+
}
|
|
33014
|
+
];
|
|
33015
|
+
var InteractiveOnboardingTour = ({
|
|
33016
|
+
isOpen,
|
|
33017
|
+
onComplete,
|
|
33018
|
+
onSkip
|
|
33019
|
+
}) => {
|
|
33020
|
+
const [currentStep, setCurrentStep] = React19.useState(0);
|
|
33021
|
+
const [isTransitioning, setIsTransitioning] = React19.useState(false);
|
|
33022
|
+
const router$1 = router.useRouter();
|
|
33023
|
+
const step = onboardingSteps[currentStep];
|
|
33024
|
+
const [targetElement, setTargetElement] = React19.useState(null);
|
|
33025
|
+
const [tooltipPosition, setTooltipPosition] = React19.useState({});
|
|
33026
|
+
const observerRef = React19.useRef(null);
|
|
33027
|
+
const handleNext = React19.useCallback(() => {
|
|
33028
|
+
if (isTransitioning) return;
|
|
33029
|
+
setIsTransitioning(true);
|
|
33030
|
+
setTimeout(() => {
|
|
33031
|
+
if (currentStep < onboardingSteps.length - 1) {
|
|
33032
|
+
setCurrentStep(currentStep + 1);
|
|
33033
|
+
} else {
|
|
33034
|
+
onComplete();
|
|
33035
|
+
}
|
|
33036
|
+
setIsTransitioning(false);
|
|
33037
|
+
}, 200);
|
|
33038
|
+
}, [currentStep, isTransitioning, onComplete]);
|
|
33039
|
+
const handlePrevious = React19.useCallback(() => {
|
|
33040
|
+
if (currentStep > 0 && !isTransitioning) {
|
|
33041
|
+
setIsTransitioning(true);
|
|
33042
|
+
setTimeout(() => {
|
|
33043
|
+
setCurrentStep(currentStep - 1);
|
|
33044
|
+
setIsTransitioning(false);
|
|
33045
|
+
}, 200);
|
|
33046
|
+
}
|
|
33047
|
+
}, [currentStep, isTransitioning]);
|
|
33048
|
+
React19.useEffect(() => {
|
|
33049
|
+
if (!step?.target || !isOpen) {
|
|
33050
|
+
setTargetElement(null);
|
|
33051
|
+
return;
|
|
33052
|
+
}
|
|
33053
|
+
const findElement = () => {
|
|
33054
|
+
const selectors = step.target.split(",").map((s) => s.trim());
|
|
33055
|
+
for (const selector of selectors) {
|
|
33056
|
+
try {
|
|
33057
|
+
const element2 = document.querySelector(selector);
|
|
33058
|
+
if (element2) {
|
|
33059
|
+
setTargetElement(element2);
|
|
33060
|
+
return element2;
|
|
33061
|
+
}
|
|
33062
|
+
} catch (e) {
|
|
33063
|
+
}
|
|
33064
|
+
}
|
|
33065
|
+
return null;
|
|
33066
|
+
};
|
|
33067
|
+
const element = findElement();
|
|
33068
|
+
if (!element && step.target) {
|
|
33069
|
+
observerRef.current = new MutationObserver(() => {
|
|
33070
|
+
const found = findElement();
|
|
33071
|
+
if (found) {
|
|
33072
|
+
observerRef.current?.disconnect();
|
|
33073
|
+
}
|
|
33074
|
+
});
|
|
33075
|
+
observerRef.current.observe(document.body, {
|
|
33076
|
+
childList: true,
|
|
33077
|
+
subtree: true
|
|
33078
|
+
});
|
|
33079
|
+
}
|
|
33080
|
+
return () => {
|
|
33081
|
+
observerRef.current?.disconnect();
|
|
33082
|
+
};
|
|
33083
|
+
}, [step, isOpen, router$1.pathname]);
|
|
33084
|
+
React19.useEffect(() => {
|
|
33085
|
+
if (!targetElement || step?.position === "center") {
|
|
33086
|
+
setTooltipPosition({
|
|
33087
|
+
position: "fixed",
|
|
33088
|
+
top: "50%",
|
|
33089
|
+
left: "50%",
|
|
33090
|
+
transform: "translate(-50%, -50%)",
|
|
33091
|
+
zIndex: 10002
|
|
33092
|
+
});
|
|
33093
|
+
return;
|
|
33094
|
+
}
|
|
33095
|
+
const updatePosition = () => {
|
|
33096
|
+
const rect = targetElement.getBoundingClientRect();
|
|
33097
|
+
const tooltipWidth = 420;
|
|
33098
|
+
const tooltipHeight = 200;
|
|
33099
|
+
const offset = 16;
|
|
33100
|
+
const viewportWidth = window.innerWidth;
|
|
33101
|
+
const viewportHeight = window.innerHeight;
|
|
33102
|
+
let position = {
|
|
33103
|
+
position: "fixed",
|
|
33104
|
+
zIndex: 10002,
|
|
33105
|
+
maxWidth: "420px"
|
|
33106
|
+
};
|
|
33107
|
+
const spaceTop = rect.top;
|
|
33108
|
+
const spaceBottom = viewportHeight - rect.bottom;
|
|
33109
|
+
const spaceLeft = rect.left;
|
|
33110
|
+
const spaceRight = viewportWidth - rect.right;
|
|
33111
|
+
if (step.position === "auto") {
|
|
33112
|
+
if (spaceBottom > tooltipHeight + offset && rect.left + tooltipWidth / 2 < viewportWidth) {
|
|
33113
|
+
position.top = rect.bottom + offset;
|
|
33114
|
+
position.left = Math.max(offset, Math.min(rect.left + rect.width / 2 - tooltipWidth / 2, viewportWidth - tooltipWidth - offset));
|
|
33115
|
+
} else if (spaceTop > tooltipHeight + offset) {
|
|
33116
|
+
position.bottom = viewportHeight - rect.top + offset;
|
|
33117
|
+
position.left = Math.max(offset, Math.min(rect.left + rect.width / 2 - tooltipWidth / 2, viewportWidth - tooltipWidth - offset));
|
|
33118
|
+
} else if (spaceRight > tooltipWidth + offset) {
|
|
33119
|
+
position.left = rect.right + offset;
|
|
33120
|
+
position.top = Math.max(offset, Math.min(rect.top + rect.height / 2 - tooltipHeight / 2, viewportHeight - tooltipHeight - offset));
|
|
33121
|
+
} else if (spaceLeft > tooltipWidth + offset) {
|
|
33122
|
+
position.right = viewportWidth - rect.left + offset;
|
|
33123
|
+
position.top = Math.max(offset, Math.min(rect.top + rect.height / 2 - tooltipHeight / 2, viewportHeight - tooltipHeight - offset));
|
|
33124
|
+
} else {
|
|
33125
|
+
position.top = "50%";
|
|
33126
|
+
position.left = "50%";
|
|
33127
|
+
position.transform = "translate(-50%, -50%)";
|
|
33128
|
+
}
|
|
33129
|
+
} else {
|
|
33130
|
+
switch (step.position) {
|
|
33131
|
+
case "top":
|
|
33132
|
+
position.bottom = viewportHeight - rect.top + offset;
|
|
33133
|
+
position.left = rect.left + rect.width / 2 - tooltipWidth / 2;
|
|
33134
|
+
break;
|
|
33135
|
+
case "bottom":
|
|
33136
|
+
position.top = rect.bottom + offset;
|
|
33137
|
+
position.left = rect.left + rect.width / 2 - tooltipWidth / 2;
|
|
33138
|
+
break;
|
|
33139
|
+
case "left":
|
|
33140
|
+
position.right = viewportWidth - rect.left + offset;
|
|
33141
|
+
position.top = rect.top + rect.height / 2 - tooltipHeight / 2;
|
|
33142
|
+
break;
|
|
33143
|
+
case "right":
|
|
33144
|
+
position.left = rect.right + offset;
|
|
33145
|
+
position.top = rect.top + rect.height / 2 - tooltipHeight / 2;
|
|
33146
|
+
break;
|
|
33147
|
+
}
|
|
33148
|
+
}
|
|
33149
|
+
setTooltipPosition(position);
|
|
33150
|
+
};
|
|
33151
|
+
updatePosition();
|
|
33152
|
+
window.addEventListener("resize", updatePosition);
|
|
33153
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
33154
|
+
return () => {
|
|
33155
|
+
window.removeEventListener("resize", updatePosition);
|
|
33156
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
33157
|
+
};
|
|
33158
|
+
}, [targetElement, step]);
|
|
33159
|
+
React19.useEffect(() => {
|
|
33160
|
+
if (!step || step.nextTrigger !== "action" || !isOpen) return;
|
|
33161
|
+
let actionTimeout;
|
|
33162
|
+
const handleAction = (e) => {
|
|
33163
|
+
if (!step.actionTarget) return;
|
|
33164
|
+
const target = e.target;
|
|
33165
|
+
const selectors = step.actionTarget.split(",").map((s) => s.trim());
|
|
33166
|
+
for (const selector of selectors) {
|
|
33167
|
+
try {
|
|
33168
|
+
if (target.matches(selector) || target.closest(selector)) {
|
|
33169
|
+
if (step.waitForPath) {
|
|
33170
|
+
actionTimeout = setTimeout(() => {
|
|
33171
|
+
console.log("No navigation detected, progressing to next step");
|
|
33172
|
+
handleNext();
|
|
33173
|
+
}, 2e3);
|
|
33174
|
+
} else {
|
|
33175
|
+
setTimeout(() => handleNext(), 300);
|
|
33176
|
+
}
|
|
33177
|
+
return;
|
|
33178
|
+
}
|
|
33179
|
+
} catch {
|
|
33180
|
+
}
|
|
33181
|
+
}
|
|
33182
|
+
};
|
|
33183
|
+
if (step.action === "click") {
|
|
33184
|
+
document.addEventListener("click", handleAction, true);
|
|
33185
|
+
return () => {
|
|
33186
|
+
document.removeEventListener("click", handleAction, true);
|
|
33187
|
+
if (actionTimeout) clearTimeout(actionTimeout);
|
|
33188
|
+
};
|
|
33189
|
+
}
|
|
33190
|
+
}, [step, isOpen, handleNext]);
|
|
33191
|
+
React19.useEffect(() => {
|
|
33192
|
+
if (!step?.waitForPath || step.nextTrigger !== "action") return;
|
|
33193
|
+
const checkPath = (url) => {
|
|
33194
|
+
const currentPath = url || router$1.asPath || router$1.pathname;
|
|
33195
|
+
if (step.waitForPath === "/workspace" && (currentPath.includes("/workspace") || currentPath.includes("workspace-") || router$1.query?.workspace)) {
|
|
33196
|
+
setTimeout(() => handleNext(), 800);
|
|
33197
|
+
return true;
|
|
33198
|
+
}
|
|
33199
|
+
if (step.waitForPath && (currentPath === step.waitForPath || currentPath.includes(step.waitForPath))) {
|
|
33200
|
+
setTimeout(() => handleNext(), 800);
|
|
33201
|
+
return true;
|
|
33202
|
+
}
|
|
33203
|
+
return false;
|
|
33204
|
+
};
|
|
33205
|
+
if (checkPath()) return;
|
|
33206
|
+
const handleRouteChange = (url) => {
|
|
33207
|
+
checkPath(url);
|
|
33208
|
+
};
|
|
33209
|
+
router$1.events.on("routeChangeComplete", handleRouteChange);
|
|
33210
|
+
router$1.events.on("routeChangeStart", handleRouteChange);
|
|
33211
|
+
return () => {
|
|
33212
|
+
router$1.events.off("routeChangeComplete", handleRouteChange);
|
|
33213
|
+
router$1.events.off("routeChangeStart", handleRouteChange);
|
|
33214
|
+
};
|
|
33215
|
+
}, [step, router$1, handleNext]);
|
|
33216
|
+
if (!isOpen || !step) return null;
|
|
33217
|
+
const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33218
|
+
!step.allowInteraction && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33219
|
+
motion.div,
|
|
33220
|
+
{
|
|
33221
|
+
initial: { opacity: 0 },
|
|
33222
|
+
animate: { opacity: 1 },
|
|
33223
|
+
exit: { opacity: 0 },
|
|
33224
|
+
className: "fixed inset-0 bg-black/20",
|
|
33225
|
+
style: { zIndex: 1e4 },
|
|
33226
|
+
onClick: (e) => e.stopPropagation()
|
|
33227
|
+
}
|
|
33228
|
+
),
|
|
33229
|
+
step.spotlight && targetElement && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33230
|
+
Spotlight,
|
|
33231
|
+
{
|
|
33232
|
+
element: targetElement,
|
|
33233
|
+
allowInteraction: step.allowInteraction,
|
|
33234
|
+
showPulse: step.action === "click"
|
|
33235
|
+
}
|
|
33236
|
+
),
|
|
33237
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33238
|
+
motion.div,
|
|
33239
|
+
{
|
|
33240
|
+
initial: { opacity: 0, scale: 0.95, y: 10 },
|
|
33241
|
+
animate: { opacity: 1, scale: 1, y: 0 },
|
|
33242
|
+
exit: { opacity: 0, scale: 0.95, y: 10 },
|
|
33243
|
+
transition: { duration: 0.2, ease: "easeOut" },
|
|
33244
|
+
style: tooltipPosition,
|
|
33245
|
+
className: "pointer-events-auto",
|
|
33246
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white dark:bg-gray-900 rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-800 overflow-hidden", children: [
|
|
33247
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-gray-800 dark:to-gray-850 border-b border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
33248
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
33249
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: [...Array(onboardingSteps.length)].map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
33250
|
+
motion.div,
|
|
33251
|
+
{
|
|
33252
|
+
initial: false,
|
|
33253
|
+
animate: {
|
|
33254
|
+
width: i === currentStep ? 24 : 6,
|
|
33255
|
+
backgroundColor: i === currentStep ? "#3B82F6" : i < currentStep ? "#93C5FD" : "#E5E7EB"
|
|
33256
|
+
},
|
|
33257
|
+
className: "h-1.5 rounded-full transition-all duration-300"
|
|
33258
|
+
},
|
|
33259
|
+
i
|
|
33260
|
+
)) }),
|
|
33261
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium text-gray-600 dark:text-gray-400", children: [
|
|
33262
|
+
currentStep + 1,
|
|
33263
|
+
" of ",
|
|
33264
|
+
onboardingSteps.length
|
|
33265
|
+
] })
|
|
33266
|
+
] }),
|
|
33267
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33268
|
+
"button",
|
|
33269
|
+
{
|
|
33270
|
+
onClick: onSkip,
|
|
33271
|
+
className: "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors p-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg",
|
|
33272
|
+
"aria-label": "Close tour",
|
|
33273
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-5 h-5" })
|
|
33274
|
+
}
|
|
33275
|
+
)
|
|
33276
|
+
] }) }),
|
|
33277
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-5", children: [
|
|
33278
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-semibold text-gray-900 dark:text-white mb-3", children: step.title }),
|
|
33279
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 dark:text-gray-300 leading-relaxed whitespace-pre-line", children: step.description }),
|
|
33280
|
+
step.action === "click" && step.nextTrigger === "action" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33281
|
+
motion.div,
|
|
33282
|
+
{
|
|
33283
|
+
initial: { opacity: 0, y: 5 },
|
|
33284
|
+
animate: { opacity: 1, y: 0 },
|
|
33285
|
+
transition: { delay: 0.3 },
|
|
33286
|
+
className: "mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-xl border border-blue-200 dark:border-blue-800",
|
|
33287
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-blue-700 dark:text-blue-300 flex items-center gap-2", children: [
|
|
33288
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MousePointer, { className: "w-4 h-4 animate-pulse" }),
|
|
33289
|
+
"Click the highlighted element to continue"
|
|
33290
|
+
] })
|
|
33291
|
+
}
|
|
33292
|
+
)
|
|
33293
|
+
] }),
|
|
33294
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 bg-gray-50 dark:bg-gray-900/50 border-t border-gray-200 dark:border-gray-800 flex items-center justify-between", children: [
|
|
33295
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33296
|
+
"button",
|
|
33297
|
+
{
|
|
33298
|
+
onClick: onSkip,
|
|
33299
|
+
className: "text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
|
|
33300
|
+
children: "Skip tour"
|
|
33301
|
+
}
|
|
33302
|
+
),
|
|
33303
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
33304
|
+
currentStep > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33305
|
+
"button",
|
|
33306
|
+
{
|
|
33307
|
+
onClick: handlePrevious,
|
|
33308
|
+
disabled: isTransitioning,
|
|
33309
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-all disabled:opacity-50",
|
|
33310
|
+
children: "Previous"
|
|
33311
|
+
}
|
|
33312
|
+
),
|
|
33313
|
+
step.nextTrigger === "button" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33314
|
+
"button",
|
|
33315
|
+
{
|
|
33316
|
+
onClick: handleNext,
|
|
33317
|
+
disabled: isTransitioning,
|
|
33318
|
+
className: `px-5 py-2 text-sm font-semibold rounded-xl transition-all flex items-center gap-2 ${currentStep === onboardingSteps.length - 1 ? "bg-gradient-to-r from-green-500 to-emerald-600 text-white hover:from-green-600 hover:to-emerald-700 shadow-lg shadow-green-500/25" : "bg-gradient-to-r from-blue-500 to-indigo-600 text-white hover:from-blue-600 hover:to-indigo-700 shadow-lg shadow-blue-500/25"} disabled:opacity-50`,
|
|
33319
|
+
children: currentStep === onboardingSteps.length - 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33320
|
+
"Complete Tour",
|
|
33321
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4" })
|
|
33322
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33323
|
+
"Next",
|
|
33324
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "w-4 h-4" })
|
|
33325
|
+
] })
|
|
33326
|
+
}
|
|
33327
|
+
)
|
|
33328
|
+
] })
|
|
33329
|
+
] })
|
|
33330
|
+
] })
|
|
33331
|
+
},
|
|
33332
|
+
step.id
|
|
33333
|
+
)
|
|
33334
|
+
] });
|
|
33335
|
+
if (typeof window !== "undefined") {
|
|
33336
|
+
return reactDom.createPortal(
|
|
33337
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { children: isOpen && content }),
|
|
33338
|
+
document.body
|
|
33339
|
+
);
|
|
33340
|
+
}
|
|
33341
|
+
return null;
|
|
33342
|
+
};
|
|
33343
|
+
var Spotlight = ({ element, allowInteraction, showPulse }) => {
|
|
33344
|
+
const [rect, setRect] = React19.useState(null);
|
|
33345
|
+
React19.useEffect(() => {
|
|
33346
|
+
const updateRect = () => {
|
|
33347
|
+
setRect(element.getBoundingClientRect());
|
|
33348
|
+
};
|
|
33349
|
+
updateRect();
|
|
33350
|
+
window.addEventListener("resize", updateRect);
|
|
33351
|
+
window.addEventListener("scroll", updateRect, true);
|
|
33352
|
+
const observer = new ResizeObserver(updateRect);
|
|
33353
|
+
observer.observe(element);
|
|
33354
|
+
return () => {
|
|
33355
|
+
window.removeEventListener("resize", updateRect);
|
|
33356
|
+
window.removeEventListener("scroll", updateRect, true);
|
|
33357
|
+
observer.disconnect();
|
|
33358
|
+
};
|
|
33359
|
+
}, [element]);
|
|
33360
|
+
if (!rect) return null;
|
|
33361
|
+
const padding = 8;
|
|
33362
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33363
|
+
!allowInteraction && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
33364
|
+
"svg",
|
|
33365
|
+
{
|
|
33366
|
+
className: "fixed inset-0",
|
|
33367
|
+
style: { zIndex: 10001, pointerEvents: "auto" },
|
|
33368
|
+
children: [
|
|
33369
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("mask", { id: "spotlight-mask", children: [
|
|
33370
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0", y: "0", width: "100%", height: "100%", fill: "white" }),
|
|
33371
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33372
|
+
"rect",
|
|
33373
|
+
{
|
|
33374
|
+
x: rect.left - padding,
|
|
33375
|
+
y: rect.top - padding,
|
|
33376
|
+
width: rect.width + padding * 2,
|
|
33377
|
+
height: rect.height + padding * 2,
|
|
33378
|
+
rx: "8",
|
|
33379
|
+
fill: "black"
|
|
33380
|
+
}
|
|
33381
|
+
)
|
|
33382
|
+
] }) }),
|
|
33383
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33384
|
+
"rect",
|
|
33385
|
+
{
|
|
33386
|
+
x: "0",
|
|
33387
|
+
y: "0",
|
|
33388
|
+
width: "100%",
|
|
33389
|
+
height: "100%",
|
|
33390
|
+
fill: "black",
|
|
33391
|
+
fillOpacity: "0.2",
|
|
33392
|
+
mask: "url(#spotlight-mask)"
|
|
33393
|
+
}
|
|
33394
|
+
)
|
|
33395
|
+
]
|
|
33396
|
+
}
|
|
33397
|
+
),
|
|
33398
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
33399
|
+
motion.div,
|
|
33400
|
+
{
|
|
33401
|
+
className: "fixed pointer-events-none",
|
|
33402
|
+
style: {
|
|
33403
|
+
left: rect.left - padding,
|
|
33404
|
+
top: rect.top - padding,
|
|
33405
|
+
width: rect.width + padding * 2,
|
|
33406
|
+
height: rect.height + padding * 2,
|
|
33407
|
+
zIndex: 10001
|
|
33408
|
+
},
|
|
33409
|
+
initial: { opacity: 0, scale: 0.95 },
|
|
33410
|
+
animate: { opacity: 1, scale: 1 },
|
|
33411
|
+
exit: { opacity: 0, scale: 0.95 },
|
|
33412
|
+
transition: { duration: 0.3, ease: "easeOut" },
|
|
33413
|
+
children: [
|
|
33414
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg ring-3 ring-blue-500 shadow-lg shadow-blue-500/30" }),
|
|
33415
|
+
showPulse && allowInteraction && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33416
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg ring-4 ring-blue-400 ring-opacity-75 animate-pulse" }),
|
|
33417
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg bg-blue-500/5 animate-pulse" })
|
|
33418
|
+
] })
|
|
33419
|
+
]
|
|
33420
|
+
}
|
|
33421
|
+
)
|
|
33422
|
+
] });
|
|
33423
|
+
};
|
|
33424
|
+
var FirstTimeLoginHandler = ({
|
|
33425
|
+
children,
|
|
33426
|
+
onboardingComponent: OnboardingComponent = SimpleOnboardingPopup,
|
|
33427
|
+
// Default to simple popup
|
|
33428
|
+
enableAutoDetection = true
|
|
33429
|
+
}) => {
|
|
33430
|
+
const {
|
|
33431
|
+
needsOnboarding,
|
|
33432
|
+
completeFirstLogin,
|
|
33433
|
+
user
|
|
33434
|
+
} = useFirstTimeLogin();
|
|
33435
|
+
const {
|
|
33436
|
+
showOnboarding: showTour,
|
|
33437
|
+
setShowOnboarding: setShowTour,
|
|
33438
|
+
completeOnboarding
|
|
33439
|
+
} = useAuth();
|
|
33440
|
+
const [showOnboarding, setShowOnboarding] = React19.useState(false);
|
|
33441
|
+
const [isCompleting, setIsCompleting] = React19.useState(false);
|
|
33442
|
+
const [hasChecked, setHasChecked] = React19.useState(false);
|
|
33443
|
+
React19.useEffect(() => {
|
|
33444
|
+
if (!hasChecked && user && enableAutoDetection) {
|
|
33445
|
+
setHasChecked(true);
|
|
33446
|
+
if (needsOnboarding) {
|
|
33447
|
+
console.log("[FirstTimeLoginHandler] First-time login detected, showing onboarding tour");
|
|
33448
|
+
setShowTour(true);
|
|
33449
|
+
}
|
|
33450
|
+
}
|
|
33451
|
+
}, [user, needsOnboarding, enableAutoDetection, hasChecked, setShowTour]);
|
|
33452
|
+
const handleOnboardingComplete = async () => {
|
|
33453
|
+
setIsCompleting(true);
|
|
33454
|
+
try {
|
|
33455
|
+
const success = await completeFirstLogin();
|
|
33456
|
+
if (success) {
|
|
33457
|
+
console.log("[FirstTimeLoginHandler] First login marked as completed");
|
|
33458
|
+
setShowOnboarding(false);
|
|
33459
|
+
} else {
|
|
33460
|
+
console.error("[FirstTimeLoginHandler] Failed to mark first login as completed");
|
|
33461
|
+
setShowOnboarding(false);
|
|
33462
|
+
}
|
|
33463
|
+
} catch (error) {
|
|
33464
|
+
console.error("[FirstTimeLoginHandler] Error completing first login:", error);
|
|
33465
|
+
setShowOnboarding(false);
|
|
33466
|
+
} finally {
|
|
33467
|
+
setIsCompleting(false);
|
|
33468
|
+
}
|
|
33469
|
+
};
|
|
33470
|
+
const handleTourComplete = async () => {
|
|
33471
|
+
await completeOnboarding();
|
|
33472
|
+
};
|
|
33473
|
+
const handleTourSkip = () => {
|
|
33474
|
+
setShowTour(false);
|
|
33475
|
+
completeFirstLogin();
|
|
33476
|
+
};
|
|
33477
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33478
|
+
children,
|
|
33479
|
+
showTour && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33480
|
+
InteractiveOnboardingTour,
|
|
33481
|
+
{
|
|
33482
|
+
isOpen: showTour,
|
|
33483
|
+
onComplete: handleTourComplete,
|
|
33484
|
+
onSkip: handleTourSkip
|
|
33485
|
+
}
|
|
33486
|
+
),
|
|
33487
|
+
showOnboarding && OnboardingComponent && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33488
|
+
OnboardingComponent,
|
|
33489
|
+
{
|
|
33490
|
+
onComplete: handleOnboardingComplete,
|
|
33491
|
+
isCompleting
|
|
33492
|
+
}
|
|
33493
|
+
)
|
|
33494
|
+
] });
|
|
33495
|
+
};
|
|
33496
|
+
var FirstTimeLoginDebug = () => {
|
|
33497
|
+
const {
|
|
33498
|
+
isFirstTimeLogin,
|
|
33499
|
+
hasCompletedFirstLogin,
|
|
33500
|
+
needsOnboarding,
|
|
33501
|
+
completeFirstLogin,
|
|
33502
|
+
user
|
|
33503
|
+
} = useFirstTimeLogin();
|
|
33504
|
+
if (!user) return null;
|
|
33505
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
|
|
33506
|
+
position: "fixed",
|
|
33507
|
+
top: 10,
|
|
33508
|
+
right: 10,
|
|
33509
|
+
background: "#f0f0f0",
|
|
33510
|
+
padding: "10px",
|
|
33511
|
+
border: "1px solid #ccc",
|
|
33512
|
+
fontSize: "12px",
|
|
33513
|
+
zIndex: 9999
|
|
33514
|
+
}, children: [
|
|
33515
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { children: "First-Time Login Debug" }),
|
|
33516
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
33517
|
+
"User ID: ",
|
|
33518
|
+
user.id
|
|
33519
|
+
] }),
|
|
33520
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
33521
|
+
"Email: ",
|
|
33522
|
+
user.email
|
|
33523
|
+
] }),
|
|
33524
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
33525
|
+
"Is First Time: ",
|
|
33526
|
+
isFirstTimeLogin ? "\u2705" : "\u274C"
|
|
33527
|
+
] }),
|
|
33528
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
33529
|
+
"Completed: ",
|
|
33530
|
+
hasCompletedFirstLogin ? "\u2705" : "\u274C"
|
|
33531
|
+
] }),
|
|
33532
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
33533
|
+
"Needs Onboarding: ",
|
|
33534
|
+
needsOnboarding ? "\u2705" : "\u274C"
|
|
33535
|
+
] }),
|
|
33536
|
+
needsOnboarding && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33537
|
+
"button",
|
|
33538
|
+
{
|
|
33539
|
+
onClick: completeFirstLogin,
|
|
33540
|
+
style: { marginTop: "5px", padding: "5px" },
|
|
33541
|
+
children: "Mark as Completed"
|
|
33542
|
+
}
|
|
33543
|
+
)
|
|
33544
|
+
] });
|
|
33545
|
+
};
|
|
33546
|
+
var onboardingSteps2 = [
|
|
33547
|
+
{
|
|
33548
|
+
id: "welcome",
|
|
33549
|
+
title: "Welcome to Optifye.ai",
|
|
33550
|
+
description: "Let's take a quick tour to help you get started with our intelligent manufacturing monitoring platform.",
|
|
33551
|
+
position: "center"
|
|
33552
|
+
},
|
|
33553
|
+
{
|
|
33554
|
+
id: "live-streams",
|
|
33555
|
+
title: "Live Video Streams",
|
|
33556
|
+
description: "These are live video streams of your manufacturing lines. Monitor your production in real-time from anywhere.",
|
|
33557
|
+
target: ".workspace-grid",
|
|
33558
|
+
position: "top",
|
|
33559
|
+
highlightTarget: true
|
|
33560
|
+
},
|
|
33561
|
+
{
|
|
33562
|
+
id: "efficiency-indicator",
|
|
33563
|
+
title: "Efficiency Indicators",
|
|
33564
|
+
description: "Notice the colored overlays on each video? They change based on real-time efficiency metrics - green for optimal, yellow for warning, and red for critical.",
|
|
33565
|
+
target: ".efficiency-indicator",
|
|
33566
|
+
position: "bottom",
|
|
33567
|
+
highlightTarget: true
|
|
33568
|
+
},
|
|
33569
|
+
{
|
|
33570
|
+
id: "click-workspace",
|
|
33571
|
+
title: "Explore a Workspace",
|
|
33572
|
+
description: "Click on any video stream to dive deeper into that workspace's performance metrics.",
|
|
33573
|
+
target: ".workspace-item",
|
|
33574
|
+
position: "center",
|
|
33575
|
+
highlightTarget: true,
|
|
33576
|
+
waitForNavigation: "/workspace/"
|
|
33577
|
+
},
|
|
33578
|
+
{
|
|
33579
|
+
id: "workspace-metrics",
|
|
33580
|
+
title: "Real-Time Efficiency Metrics",
|
|
33581
|
+
description: "Great! These are the real-time efficiency metrics for the workspace you just selected. Track OEE, performance, and quality in real-time.",
|
|
33582
|
+
target: ".metrics-dashboard",
|
|
33583
|
+
position: "top",
|
|
33584
|
+
highlightTarget: true
|
|
33585
|
+
},
|
|
33586
|
+
{
|
|
33587
|
+
id: "clips-navigation",
|
|
33588
|
+
title: "Explore Clips",
|
|
33589
|
+
description: 'Now, click on "Clips" in the sidebar to see how you can diagnose issues and understand root causes.',
|
|
33590
|
+
target: '[data-nav-item="clips"]',
|
|
33591
|
+
position: "right",
|
|
33592
|
+
highlightTarget: true,
|
|
33593
|
+
waitForNavigation: "/clips"
|
|
33594
|
+
},
|
|
33595
|
+
{
|
|
33596
|
+
id: "clips-explanation",
|
|
33597
|
+
title: "Clips for Root Cause Analysis",
|
|
33598
|
+
description: "Clips help you understand and diagnose the root cause of efficiency issues. Review recorded incidents, analyze patterns, and improve your processes.",
|
|
33599
|
+
target: ".clips-container",
|
|
33600
|
+
position: "top",
|
|
33601
|
+
highlightTarget: true
|
|
33602
|
+
},
|
|
33603
|
+
{
|
|
33604
|
+
id: "complete",
|
|
33605
|
+
title: "You're All Set!",
|
|
33606
|
+
description: "You've completed the tour! Start exploring Optifye.ai to optimize your manufacturing operations.",
|
|
33607
|
+
position: "center"
|
|
33608
|
+
}
|
|
33609
|
+
];
|
|
33610
|
+
var OnboardingTour = ({
|
|
33611
|
+
isOpen,
|
|
33612
|
+
onComplete,
|
|
33613
|
+
onSkip
|
|
33614
|
+
}) => {
|
|
33615
|
+
const [currentStep, setCurrentStep] = React19.useState(0);
|
|
33616
|
+
const [isWaitingForNavigation, setIsWaitingForNavigation] = React19.useState(false);
|
|
33617
|
+
const router$1 = router.useRouter();
|
|
33618
|
+
const step = onboardingSteps2[currentStep];
|
|
33619
|
+
React19.useEffect(() => {
|
|
33620
|
+
if (!step?.waitForNavigation || !isWaitingForNavigation) return;
|
|
33621
|
+
const handleRouteChange = (url) => {
|
|
33622
|
+
if (step.waitForNavigation && url.includes(step.waitForNavigation)) {
|
|
33623
|
+
setIsWaitingForNavigation(false);
|
|
33624
|
+
handleNext();
|
|
33625
|
+
}
|
|
33626
|
+
};
|
|
33627
|
+
router$1.events.on("routeChangeComplete", handleRouteChange);
|
|
33628
|
+
return () => {
|
|
33629
|
+
router$1.events.off("routeChangeComplete", handleRouteChange);
|
|
33630
|
+
};
|
|
33631
|
+
}, [step, isWaitingForNavigation, router$1]);
|
|
33632
|
+
const handleNext = React19.useCallback(() => {
|
|
33633
|
+
const nextStep = onboardingSteps2[currentStep + 1];
|
|
33634
|
+
if (step?.waitForNavigation && !isWaitingForNavigation) {
|
|
33635
|
+
setIsWaitingForNavigation(true);
|
|
33636
|
+
return;
|
|
33637
|
+
}
|
|
33638
|
+
if (currentStep < onboardingSteps2.length - 1) {
|
|
33639
|
+
setCurrentStep(currentStep + 1);
|
|
33640
|
+
if (nextStep?.target) {
|
|
33641
|
+
setTimeout(() => {
|
|
33642
|
+
const element = document.querySelector(nextStep.target);
|
|
33643
|
+
if (element) {
|
|
33644
|
+
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
33645
|
+
}
|
|
33646
|
+
}, 100);
|
|
33647
|
+
}
|
|
33648
|
+
} else {
|
|
33649
|
+
onComplete();
|
|
33650
|
+
}
|
|
33651
|
+
}, [currentStep, step, isWaitingForNavigation, onComplete]);
|
|
33652
|
+
const handlePrevious = React19.useCallback(() => {
|
|
33653
|
+
if (currentStep > 0) {
|
|
33654
|
+
setCurrentStep(currentStep - 1);
|
|
33655
|
+
setIsWaitingForNavigation(false);
|
|
33656
|
+
}
|
|
33657
|
+
}, [currentStep]);
|
|
33658
|
+
const getPositionStyles = React19.useCallback((position) => {
|
|
33659
|
+
const baseStyles = {
|
|
33660
|
+
position: "fixed",
|
|
33661
|
+
zIndex: 1e4
|
|
33662
|
+
};
|
|
33663
|
+
if (step?.target && position !== "center") {
|
|
33664
|
+
const element = document.querySelector(step.target);
|
|
33665
|
+
if (element) {
|
|
33666
|
+
const rect = element.getBoundingClientRect();
|
|
33667
|
+
const tooltipWidth = 400;
|
|
33668
|
+
const tooltipHeight = 200;
|
|
33669
|
+
const offset = 20;
|
|
33670
|
+
switch (position) {
|
|
33671
|
+
case "top":
|
|
33672
|
+
return {
|
|
33673
|
+
...baseStyles,
|
|
33674
|
+
left: `${rect.left + rect.width / 2 - tooltipWidth / 2}px`,
|
|
33675
|
+
bottom: `${window.innerHeight - rect.top + offset}px`
|
|
33676
|
+
};
|
|
33677
|
+
case "bottom":
|
|
33678
|
+
return {
|
|
33679
|
+
...baseStyles,
|
|
33680
|
+
left: `${rect.left + rect.width / 2 - tooltipWidth / 2}px`,
|
|
33681
|
+
top: `${rect.bottom + offset}px`
|
|
33682
|
+
};
|
|
33683
|
+
case "left":
|
|
33684
|
+
return {
|
|
33685
|
+
...baseStyles,
|
|
33686
|
+
right: `${window.innerWidth - rect.left + offset}px`,
|
|
33687
|
+
top: `${rect.top + rect.height / 2 - tooltipHeight / 2}px`
|
|
33688
|
+
};
|
|
33689
|
+
case "right":
|
|
33690
|
+
return {
|
|
33691
|
+
...baseStyles,
|
|
33692
|
+
left: `${rect.right + offset}px`,
|
|
33693
|
+
top: `${rect.top + rect.height / 2 - tooltipHeight / 2}px`
|
|
33694
|
+
};
|
|
33695
|
+
}
|
|
33696
|
+
}
|
|
33697
|
+
}
|
|
33698
|
+
return {
|
|
33699
|
+
...baseStyles,
|
|
33700
|
+
top: "50%",
|
|
33701
|
+
left: "50%",
|
|
33702
|
+
transform: "translate(-50%, -50%)"
|
|
33703
|
+
};
|
|
33704
|
+
}, [step]);
|
|
33705
|
+
if (!isOpen || !step) return null;
|
|
33706
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33707
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33708
|
+
motion.div,
|
|
33709
|
+
{
|
|
33710
|
+
initial: { opacity: 0 },
|
|
33711
|
+
animate: { opacity: 1 },
|
|
33712
|
+
exit: { opacity: 0 },
|
|
33713
|
+
className: "fixed inset-0 bg-black/60 backdrop-blur-sm z-[9998]",
|
|
33714
|
+
onClick: (e) => e.stopPropagation()
|
|
33715
|
+
}
|
|
33716
|
+
),
|
|
33717
|
+
step.highlightTarget && step.target && /* @__PURE__ */ jsxRuntime.jsx(HighlightOverlay, { target: step.target }),
|
|
33718
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33719
|
+
motion.div,
|
|
33720
|
+
{
|
|
33721
|
+
initial: { opacity: 0, scale: 0.9 },
|
|
33722
|
+
animate: { opacity: 1, scale: 1 },
|
|
33723
|
+
exit: { opacity: 0, scale: 0.9 },
|
|
33724
|
+
transition: { duration: 0.3, ease: "easeOut" },
|
|
33725
|
+
style: getPositionStyles(step.position),
|
|
33726
|
+
className: "w-[400px] max-w-[90vw]",
|
|
33727
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
33728
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between", children: [
|
|
33729
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
33730
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: [...Array(onboardingSteps2.length)].map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
33731
|
+
"div",
|
|
33732
|
+
{
|
|
33733
|
+
className: `h-1.5 transition-all duration-300 ${i === currentStep ? "w-6 bg-blue-600" : i < currentStep ? "w-1.5 bg-blue-400" : "w-1.5 bg-gray-300 dark:bg-gray-600"} rounded-full`
|
|
33734
|
+
},
|
|
33735
|
+
i
|
|
33736
|
+
)) }),
|
|
33737
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
|
|
33738
|
+
currentStep + 1,
|
|
33739
|
+
" / ",
|
|
33740
|
+
onboardingSteps2.length
|
|
33741
|
+
] })
|
|
33742
|
+
] }),
|
|
33743
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33744
|
+
"button",
|
|
33745
|
+
{
|
|
33746
|
+
onClick: onSkip,
|
|
33747
|
+
className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors",
|
|
33748
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-5 h-5" })
|
|
33749
|
+
}
|
|
33750
|
+
)
|
|
33751
|
+
] }),
|
|
33752
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-5", children: [
|
|
33753
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-2", children: step.title }),
|
|
33754
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 dark:text-gray-300 leading-relaxed", children: step.description }),
|
|
33755
|
+
isWaitingForNavigation && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-blue-700 dark:text-blue-300 flex items-center gap-2", children: [
|
|
33756
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-2 h-2 bg-blue-600 rounded-full animate-pulse" }),
|
|
33757
|
+
"Waiting for you to click..."
|
|
33758
|
+
] }) })
|
|
33759
|
+
] }),
|
|
33760
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 bg-gray-50 dark:bg-gray-900/50 flex items-center justify-between", children: [
|
|
33761
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33762
|
+
"button",
|
|
33763
|
+
{
|
|
33764
|
+
onClick: onSkip,
|
|
33765
|
+
className: "text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
|
|
33766
|
+
children: "Skip tour"
|
|
33767
|
+
}
|
|
33768
|
+
),
|
|
33769
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
33770
|
+
currentStep > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
33771
|
+
"button",
|
|
33772
|
+
{
|
|
33773
|
+
onClick: handlePrevious,
|
|
33774
|
+
className: "px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",
|
|
33775
|
+
children: "Previous"
|
|
33776
|
+
}
|
|
33777
|
+
),
|
|
33778
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33779
|
+
"button",
|
|
33780
|
+
{
|
|
33781
|
+
onClick: handleNext,
|
|
33782
|
+
disabled: isWaitingForNavigation,
|
|
33783
|
+
className: `px-4 py-2 text-sm font-medium rounded-lg transition-all flex items-center gap-2 ${isWaitingForNavigation ? "bg-gray-100 text-gray-400 cursor-not-allowed" : currentStep === onboardingSteps2.length - 1 ? "bg-green-600 text-white hover:bg-green-700" : "bg-blue-600 text-white hover:bg-blue-700"}`,
|
|
33784
|
+
children: currentStep === onboardingSteps2.length - 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33785
|
+
"Complete",
|
|
33786
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4" })
|
|
33787
|
+
] }) : isWaitingForNavigation ? "Waiting..." : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33788
|
+
"Next",
|
|
33789
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-4 h-4" })
|
|
33790
|
+
] })
|
|
33791
|
+
}
|
|
33792
|
+
)
|
|
33793
|
+
] })
|
|
33794
|
+
] })
|
|
33795
|
+
] })
|
|
33796
|
+
},
|
|
33797
|
+
step.id
|
|
33798
|
+
)
|
|
33799
|
+
] }) });
|
|
33800
|
+
};
|
|
33801
|
+
var HighlightOverlay = ({ target }) => {
|
|
33802
|
+
const [rect, setRect] = React19.useState(null);
|
|
33803
|
+
React19.useEffect(() => {
|
|
33804
|
+
const element = document.querySelector(target);
|
|
33805
|
+
if (element) {
|
|
33806
|
+
setRect(element.getBoundingClientRect());
|
|
33807
|
+
}
|
|
33808
|
+
}, [target]);
|
|
33809
|
+
if (!rect) return null;
|
|
33810
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
33811
|
+
motion.div,
|
|
33812
|
+
{
|
|
33813
|
+
initial: { opacity: 0 },
|
|
33814
|
+
animate: { opacity: 1 },
|
|
33815
|
+
exit: { opacity: 0 },
|
|
33816
|
+
className: "fixed z-[9999] pointer-events-none",
|
|
33817
|
+
style: {
|
|
33818
|
+
left: rect.left - 8,
|
|
33819
|
+
top: rect.top - 8,
|
|
33820
|
+
width: rect.width + 16,
|
|
33821
|
+
height: rect.height + 16
|
|
33822
|
+
},
|
|
33823
|
+
children: [
|
|
33824
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg ring-4 ring-blue-500/50 ring-offset-4 ring-offset-transparent animate-pulse" }),
|
|
33825
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg bg-blue-500/10" })
|
|
33826
|
+
]
|
|
33827
|
+
}
|
|
33828
|
+
);
|
|
33829
|
+
};
|
|
33830
|
+
var OnboardingDemo = () => {
|
|
33831
|
+
const [showTour, setShowTour] = React19.useState(false);
|
|
33832
|
+
const handleComplete = () => {
|
|
33833
|
+
setShowTour(false);
|
|
33834
|
+
console.log("Onboarding tour completed");
|
|
33835
|
+
};
|
|
33836
|
+
const handleSkip = () => {
|
|
33837
|
+
setShowTour(false);
|
|
33838
|
+
console.log("Onboarding tour skipped");
|
|
33839
|
+
};
|
|
33840
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
33841
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
33842
|
+
"button",
|
|
33843
|
+
{
|
|
33844
|
+
onClick: () => setShowTour(true),
|
|
33845
|
+
className: "fixed bottom-4 right-4 z-50 flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg shadow-lg hover:bg-blue-700 transition-colors",
|
|
33846
|
+
title: "Start Onboarding Tour",
|
|
33847
|
+
children: [
|
|
33848
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "w-4 h-4" }),
|
|
33849
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Start Tour" })
|
|
33850
|
+
]
|
|
33851
|
+
}
|
|
33852
|
+
),
|
|
33853
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
33854
|
+
InteractiveOnboardingTour,
|
|
33855
|
+
{
|
|
33856
|
+
isOpen: showTour,
|
|
33857
|
+
onComplete: handleComplete,
|
|
33858
|
+
onSkip: handleSkip
|
|
33859
|
+
}
|
|
33860
|
+
)
|
|
33861
|
+
] });
|
|
33862
|
+
};
|
|
31234
33863
|
var ThreadSidebar = ({
|
|
31235
33864
|
activeThreadId,
|
|
31236
33865
|
onSelectThread,
|
|
@@ -34437,6 +37066,15 @@ var QualityOverview = React19.memo(({ lineInfo }) => {
|
|
|
34437
37066
|
] });
|
|
34438
37067
|
});
|
|
34439
37068
|
QualityOverview.displayName = "QualityOverview";
|
|
37069
|
+
var getSupervisorName = (lineId) => {
|
|
37070
|
+
const supervisorMapping = {
|
|
37071
|
+
// Add more mappings as needed
|
|
37072
|
+
"default": "Vivaan Baid",
|
|
37073
|
+
"factory": "Vivaan Baid"
|
|
37074
|
+
// You can add specific line IDs here when you have real data
|
|
37075
|
+
};
|
|
37076
|
+
return supervisorMapping[lineId || "default"] || "Vivaan Baid";
|
|
37077
|
+
};
|
|
34440
37078
|
var KPIDetailView = ({
|
|
34441
37079
|
lineId,
|
|
34442
37080
|
date: urlDate,
|
|
@@ -34926,9 +37564,15 @@ var KPIDetailView = ({
|
|
|
34926
37564
|
"aria-label": "Navigate back to previous page"
|
|
34927
37565
|
}
|
|
34928
37566
|
) }),
|
|
34929
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center mt-2 sm:mt-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-
|
|
34930
|
-
/* @__PURE__ */ jsxRuntime.
|
|
34931
|
-
|
|
37567
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center mt-2 sm:mt-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [
|
|
37568
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:gap-3", children: [
|
|
37569
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg sm:text-xl md:text-2xl lg:text-3xl font-semibold text-gray-900 text-center truncate", children: lineInfo?.line_name || "Line" }),
|
|
37570
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1.5 w-1.5 sm:h-2 sm:w-2 rounded-full bg-green-500 animate-pulse ring-1 sm:ring-2 ring-green-500/30 ring-offset-1 flex-shrink-0" })
|
|
37571
|
+
] }),
|
|
37572
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-gray-600 font-medium", children: [
|
|
37573
|
+
"Supervisor: ",
|
|
37574
|
+
getSupervisorName(lineInfo?.line_id)
|
|
37575
|
+
] })
|
|
34932
37576
|
] }) })
|
|
34933
37577
|
] }),
|
|
34934
37578
|
(activeTab !== "monthly_history" || urlDate || urlShift) && metrics2 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 sm:mt-3 bg-blue-50 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 sm:gap-3 md:gap-4", children: [
|
|
@@ -35136,8 +37780,11 @@ var LineCard = ({ line, onClick }) => {
|
|
|
35136
37780
|
onClick,
|
|
35137
37781
|
className: "relative bg-white border border-gray-200/80 shadow-sm hover:shadow-lg \n rounded-xl p-4 sm:p-5 md:p-6 transition-all duration-200 cursor-pointer \n hover:scale-[1.01] active:scale-[0.99] group",
|
|
35138
37782
|
children: [
|
|
35139
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-
|
|
35140
|
-
/* @__PURE__ */ jsxRuntime.
|
|
37783
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row sm:items-start gap-2 sm:gap-3 mb-4 sm:mb-5 md:mb-6", children: [
|
|
37784
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
37785
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg sm:text-xl font-semibold text-gray-900 truncate", children: line.line_name }),
|
|
37786
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600 mt-1", children: "Supervisor: Vivaan" })
|
|
37787
|
+
] }),
|
|
35141
37788
|
kpis && isOnTrack !== null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center gap-1.5 px-2.5 sm:px-3 py-1 sm:py-1.5 rounded-full text-xs font-medium self-start sm:self-auto ${isOnTrack ? "bg-emerald-100 text-emerald-700 border border-emerald-200" : "bg-red-100 text-red-700 border border-red-200"}`, children: [
|
|
35142
37789
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-2 h-2 rounded-full ${isOnTrack ? "bg-emerald-500" : "bg-red-500"} animate-pulse` }),
|
|
35143
37790
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: isOnTrack ? "On Track" : "Behind" })
|
|
@@ -35839,9 +38486,6 @@ var ProfileView = () => {
|
|
|
35839
38486
|
id: user.id,
|
|
35840
38487
|
email: user.email,
|
|
35841
38488
|
full_name: profileData.full_name,
|
|
35842
|
-
company: profileData.company,
|
|
35843
|
-
phone: profileData.phone,
|
|
35844
|
-
timezone: profileData.timezone,
|
|
35845
38489
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
35846
38490
|
});
|
|
35847
38491
|
if (error2) throw error2;
|
|
@@ -35990,55 +38634,6 @@ var ProfileView = () => {
|
|
|
35990
38634
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-900", children: profileData.email }),
|
|
35991
38635
|
profileData.email_verified && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCheck, { className: "h-4 w-4 text-green-500" })
|
|
35992
38636
|
] })
|
|
35993
|
-
] }),
|
|
35994
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
35995
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Company" }),
|
|
35996
|
-
isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
35997
|
-
"input",
|
|
35998
|
-
{
|
|
35999
|
-
type: "text",
|
|
36000
|
-
value: profileData.company || "",
|
|
36001
|
-
onChange: (e) => setProfileData((prev) => ({ ...prev, company: e.target.value })),
|
|
36002
|
-
className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
36003
|
-
}
|
|
36004
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-900", children: profileData.company || "Not set" })
|
|
36005
|
-
] }),
|
|
36006
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
36007
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Role" }),
|
|
36008
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-900", children: profileData.role || "Not set" })
|
|
36009
|
-
] }),
|
|
36010
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
36011
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Phone" }),
|
|
36012
|
-
isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
36013
|
-
"input",
|
|
36014
|
-
{
|
|
36015
|
-
type: "tel",
|
|
36016
|
-
value: profileData.phone || "",
|
|
36017
|
-
onChange: (e) => setProfileData((prev) => ({ ...prev, phone: e.target.value })),
|
|
36018
|
-
className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
36019
|
-
}
|
|
36020
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-900", children: profileData.phone || "Not set" })
|
|
36021
|
-
] }),
|
|
36022
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
36023
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Timezone" }),
|
|
36024
|
-
isEditing ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
36025
|
-
"select",
|
|
36026
|
-
{
|
|
36027
|
-
value: profileData.timezone || "",
|
|
36028
|
-
onChange: (e) => setProfileData((prev) => ({ ...prev, timezone: e.target.value })),
|
|
36029
|
-
className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent",
|
|
36030
|
-
children: [
|
|
36031
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select timezone" }),
|
|
36032
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "UTC", children: "UTC" }),
|
|
36033
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "America/New_York", children: "Eastern Time" }),
|
|
36034
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "America/Chicago", children: "Central Time" }),
|
|
36035
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "America/Denver", children: "Mountain Time" }),
|
|
36036
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "America/Los_Angeles", children: "Pacific Time" }),
|
|
36037
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "Asia/Kolkata", children: "India Standard Time" }),
|
|
36038
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "Europe/London", children: "GMT" })
|
|
36039
|
-
]
|
|
36040
|
-
}
|
|
36041
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-900", children: profileData.timezone || "Not set" })
|
|
36042
38637
|
] })
|
|
36043
38638
|
] }),
|
|
36044
38639
|
isEditing && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6 flex gap-3", children: /* @__PURE__ */ jsxRuntime.jsx(Button2, { variant: "outline", onClick: handleSaveProfile, disabled: loading, children: loading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -38621,10 +41216,6 @@ var TargetsView = ({
|
|
|
38621
41216
|
}, [actionIds]);
|
|
38622
41217
|
const handleSaveLine = React19.useCallback(async (lineId) => {
|
|
38623
41218
|
console.log(`[handleSaveLine] Attempting to save line: ${lineId}`);
|
|
38624
|
-
if (!canSaveTargets) {
|
|
38625
|
-
sonner.toast.error("You do not have permission to save targets. Contact your administrator.");
|
|
38626
|
-
return;
|
|
38627
|
-
}
|
|
38628
41219
|
const hardcodedUserId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
|
|
38629
41220
|
const currentEffectiveUserId = hardcodedUserId;
|
|
38630
41221
|
console.log(`[handleSaveLine] effectiveUserId: ${currentEffectiveUserId}`);
|
|
@@ -38901,6 +41492,13 @@ var WorkspaceDetailView = ({
|
|
|
38901
41492
|
const [selectedMonth, setSelectedMonth] = React19.useState(today.getMonth());
|
|
38902
41493
|
const [selectedYear, setSelectedYear] = React19.useState(today.getFullYear());
|
|
38903
41494
|
const [selectedShift, setSelectedShift] = React19.useState("day");
|
|
41495
|
+
React19.useEffect(() => {
|
|
41496
|
+
if (parsedShiftId === 1) {
|
|
41497
|
+
setSelectedShift("night");
|
|
41498
|
+
} else if (parsedShiftId === 0) {
|
|
41499
|
+
setSelectedShift("day");
|
|
41500
|
+
}
|
|
41501
|
+
}, [parsedShiftId]);
|
|
38904
41502
|
const isHistoricView = Boolean(date && parsedShiftId !== void 0);
|
|
38905
41503
|
const initialTab = getInitialTab(sourceType, defaultTab, fromMonthly, date);
|
|
38906
41504
|
const [activeTab, setActiveTab] = React19.useState(initialTab);
|
|
@@ -39150,11 +41748,18 @@ var WorkspaceDetailView = ({
|
|
|
39150
41748
|
}
|
|
39151
41749
|
return;
|
|
39152
41750
|
}
|
|
41751
|
+
if (activeTab === "monthly_history") {
|
|
41752
|
+
if (onNavigate) {
|
|
41753
|
+
onNavigate("/");
|
|
41754
|
+
}
|
|
41755
|
+
return;
|
|
41756
|
+
}
|
|
39153
41757
|
if (date || shift) {
|
|
39154
41758
|
setActiveTab("monthly_history");
|
|
39155
41759
|
if (onNavigate) {
|
|
39156
41760
|
const params = new URLSearchParams();
|
|
39157
41761
|
params.set("fromMonthly", "true");
|
|
41762
|
+
params.set("shift", selectedShift === "night" ? "1" : "0");
|
|
39158
41763
|
if (effectiveLineId) {
|
|
39159
41764
|
params.set("lineId", effectiveLineId);
|
|
39160
41765
|
}
|
|
@@ -39166,6 +41771,7 @@ var WorkspaceDetailView = ({
|
|
|
39166
41771
|
if (onNavigate) {
|
|
39167
41772
|
const params = new URLSearchParams();
|
|
39168
41773
|
params.set("fromMonthly", "true");
|
|
41774
|
+
params.set("shift", selectedShift === "night" ? "1" : "0");
|
|
39169
41775
|
if (effectiveLineId) {
|
|
39170
41776
|
params.set("lineId", effectiveLineId);
|
|
39171
41777
|
}
|
|
@@ -39264,7 +41870,7 @@ var WorkspaceDetailView = ({
|
|
|
39264
41870
|
BackButtonMinimal,
|
|
39265
41871
|
{
|
|
39266
41872
|
onClick: handleBackNavigation,
|
|
39267
|
-
text: previousView === "line_monthly_history" ? "Back to Line History" : returnUrl && returnUrl.includes("monthly_history") ? "Back to Line History" : returnUrl && returnUrl.includes("/kpis/") ? "Back to KPIs" : returnUrl && returnUrl.includes("/leaderboard/") ? "Back to Leaderboard" : date || shift ? "Back to Monthly History" : "Back",
|
|
41873
|
+
text: previousView === "line_monthly_history" ? "Back to Line History" : returnUrl && returnUrl.includes("monthly_history") ? "Back to Line History" : returnUrl && returnUrl.includes("/kpis/") ? "Back to KPIs" : returnUrl && returnUrl.includes("/leaderboard/") ? "Back to Leaderboard" : (date || shift) && activeTab !== "monthly_history" ? "Back to Monthly History" : "Back",
|
|
39268
41874
|
size: "sm",
|
|
39269
41875
|
"aria-label": "Navigate back to previous page"
|
|
39270
41876
|
}
|
|
@@ -39290,7 +41896,7 @@ var WorkspaceDetailView = ({
|
|
|
39290
41896
|
BackButtonMinimal,
|
|
39291
41897
|
{
|
|
39292
41898
|
onClick: handleBackNavigation,
|
|
39293
|
-
text: previousView === "line_monthly_history" ? "Back to Line History" : returnUrl && returnUrl.includes("monthly_history") ? "Back to Line History" : returnUrl && returnUrl.includes("/kpis/") ? "Back to KPIs" : returnUrl && returnUrl.includes("/leaderboard/") ? "Back to Leaderboard" : date || shift ? "Back to Monthly History" : "Back",
|
|
41899
|
+
text: previousView === "line_monthly_history" ? "Back to Line History" : returnUrl && returnUrl.includes("monthly_history") ? "Back to Line History" : returnUrl && returnUrl.includes("/kpis/") ? "Back to KPIs" : returnUrl && returnUrl.includes("/leaderboard/") ? "Back to Leaderboard" : (date || shift) && activeTab !== "monthly_history" ? "Back to Monthly History" : "Back",
|
|
39294
41900
|
size: "default",
|
|
39295
41901
|
"aria-label": "Navigate back to previous page"
|
|
39296
41902
|
}
|
|
@@ -39678,6 +42284,7 @@ var WorkspaceDetailView = ({
|
|
|
39678
42284
|
const params = new URLSearchParams();
|
|
39679
42285
|
params.set("date", selectedDate);
|
|
39680
42286
|
params.set("shift", selectedShift === "day" ? "0" : "1");
|
|
42287
|
+
params.set("fromMonthly", "true");
|
|
39681
42288
|
if (effectiveLineId) {
|
|
39682
42289
|
params.set("lineId", effectiveLineId);
|
|
39683
42290
|
}
|
|
@@ -40161,6 +42768,168 @@ var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
|
|
|
40161
42768
|
requireAuth: true
|
|
40162
42769
|
});
|
|
40163
42770
|
var AuthenticatedWorkspaceHealthView = WorkspaceHealthView;
|
|
42771
|
+
var SupervisorManagementView = ({
|
|
42772
|
+
onNavigate,
|
|
42773
|
+
onBack,
|
|
42774
|
+
className = ""
|
|
42775
|
+
}) => {
|
|
42776
|
+
const supabase = useSupabaseClient();
|
|
42777
|
+
const entityConfig = useEntityConfig();
|
|
42778
|
+
const companyId = entityConfig?.companyId;
|
|
42779
|
+
const [data, setData] = React19.useState({
|
|
42780
|
+
assignments: [],
|
|
42781
|
+
allSupervisors: [],
|
|
42782
|
+
loading: true,
|
|
42783
|
+
error: void 0
|
|
42784
|
+
});
|
|
42785
|
+
const [savingAssignments, setSavingAssignments] = React19.useState(/* @__PURE__ */ new Set());
|
|
42786
|
+
const loadData = React19.useCallback(async () => {
|
|
42787
|
+
try {
|
|
42788
|
+
setData((prev) => ({ ...prev, loading: true, error: void 0 }));
|
|
42789
|
+
if (!companyId) {
|
|
42790
|
+
throw new Error("Company ID is not configured");
|
|
42791
|
+
}
|
|
42792
|
+
if (!supabase) {
|
|
42793
|
+
throw new Error("Supabase client is not available");
|
|
42794
|
+
}
|
|
42795
|
+
console.log(`[SupervisorManagementView] Loading data for companyId: ${companyId}`);
|
|
42796
|
+
const supervisorService = createSupervisorService(supabase);
|
|
42797
|
+
const realData = await supervisorService.getSupervisorManagementData(companyId);
|
|
42798
|
+
console.log(`[SupervisorManagementView] Received data:`, {
|
|
42799
|
+
assignments: realData.assignments.length,
|
|
42800
|
+
allSupervisors: realData.allSupervisors.length,
|
|
42801
|
+
firstAssignmentAvailableSupervisors: realData.assignments[0]?.availableSupervisors?.length || 0,
|
|
42802
|
+
firstAssignmentAvailableSupervisorIds: realData.assignments[0]?.availableSupervisors?.map((s) => s.id) || []
|
|
42803
|
+
});
|
|
42804
|
+
setData(realData);
|
|
42805
|
+
} catch (error) {
|
|
42806
|
+
console.error("Error loading supervisor management data:", error);
|
|
42807
|
+
setData((prev) => ({
|
|
42808
|
+
...prev,
|
|
42809
|
+
loading: false,
|
|
42810
|
+
error: error instanceof Error ? error.message : "Failed to load supervisor management data. Please try again."
|
|
42811
|
+
}));
|
|
42812
|
+
}
|
|
42813
|
+
}, [supabase, companyId]);
|
|
42814
|
+
React19.useEffect(() => {
|
|
42815
|
+
loadData();
|
|
42816
|
+
}, [loadData]);
|
|
42817
|
+
const handleSupervisorChange = React19.useCallback(async (lineId, supervisor) => {
|
|
42818
|
+
try {
|
|
42819
|
+
setSavingAssignments((prev) => /* @__PURE__ */ new Set([...prev, lineId]));
|
|
42820
|
+
if (!supabase) {
|
|
42821
|
+
throw new Error("Supabase client is not available");
|
|
42822
|
+
}
|
|
42823
|
+
const supervisorService = createSupervisorService(supabase);
|
|
42824
|
+
const success = await supervisorService.assignSupervisorToLine(lineId, supervisor?.id);
|
|
42825
|
+
if (!success) {
|
|
42826
|
+
throw new Error("Failed to update supervisor assignment");
|
|
42827
|
+
}
|
|
42828
|
+
setData((prev) => ({
|
|
42829
|
+
...prev,
|
|
42830
|
+
assignments: prev.assignments.map(
|
|
42831
|
+
(assignment) => assignment.lineId === lineId ? { ...assignment, currentSupervisor: supervisor || void 0 } : assignment
|
|
42832
|
+
)
|
|
42833
|
+
}));
|
|
42834
|
+
const lineName = data.assignments.find((a) => a.lineId === lineId)?.lineName || "Line";
|
|
42835
|
+
const message = supervisor ? `${supervisor.name} assigned to ${lineName}` : `Supervisor removed from ${lineName}`;
|
|
42836
|
+
sonner.toast.success(message);
|
|
42837
|
+
} catch (error) {
|
|
42838
|
+
console.error("Error updating supervisor assignment:", error);
|
|
42839
|
+
sonner.toast.error("Failed to update supervisor assignment. Please try again.");
|
|
42840
|
+
} finally {
|
|
42841
|
+
setSavingAssignments((prev) => {
|
|
42842
|
+
const newSet = new Set(prev);
|
|
42843
|
+
newSet.delete(lineId);
|
|
42844
|
+
return newSet;
|
|
42845
|
+
});
|
|
42846
|
+
}
|
|
42847
|
+
}, [data.assignments, supabase]);
|
|
42848
|
+
const handleRefresh = React19.useCallback(() => {
|
|
42849
|
+
loadData();
|
|
42850
|
+
}, [loadData]);
|
|
42851
|
+
const handleBack = React19.useCallback(() => {
|
|
42852
|
+
if (onBack) {
|
|
42853
|
+
onBack();
|
|
42854
|
+
} else if (onNavigate) {
|
|
42855
|
+
onNavigate("/");
|
|
42856
|
+
}
|
|
42857
|
+
}, [onBack, onNavigate]);
|
|
42858
|
+
if (data.loading) {
|
|
42859
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("min-h-screen bg-slate-50", className), children: /* @__PURE__ */ jsxRuntime.jsx(LoadingPage, { message: "Loading supervisor management..." }) });
|
|
42860
|
+
}
|
|
42861
|
+
if (data.error) {
|
|
42862
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("min-h-screen bg-slate-50", className), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42863
|
+
EmptyStateMessage,
|
|
42864
|
+
{
|
|
42865
|
+
iconType: lucideReact.AlertCircle,
|
|
42866
|
+
title: "Failed to Load Data",
|
|
42867
|
+
message: data.error,
|
|
42868
|
+
actionButton: {
|
|
42869
|
+
text: "Try Again",
|
|
42870
|
+
onClick: handleRefresh,
|
|
42871
|
+
className: "bg-blue-600 hover:bg-blue-700"
|
|
42872
|
+
}
|
|
42873
|
+
}
|
|
42874
|
+
) }) });
|
|
42875
|
+
}
|
|
42876
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
42877
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 sm:px-4 md:px-6 lg:px-8 py-3 sm:py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
|
|
42878
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:absolute sm:left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42879
|
+
BackButtonMinimal,
|
|
42880
|
+
{
|
|
42881
|
+
onClick: handleBack,
|
|
42882
|
+
text: "Back",
|
|
42883
|
+
size: "default",
|
|
42884
|
+
"aria-label": "Navigate back to previous page"
|
|
42885
|
+
}
|
|
42886
|
+
) }),
|
|
42887
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center mt-2 sm:mt-0", children: [
|
|
42888
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg sm:text-xl md:text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "Supervisor Management" }),
|
|
42889
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs sm:text-sm text-gray-500 mt-0.5 sm:mt-1 text-center px-2 sm:px-0", children: "Manage supervisor assignments for production lines" })
|
|
42890
|
+
] }),
|
|
42891
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block absolute right-0 w-24", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42892
|
+
"button",
|
|
42893
|
+
{
|
|
42894
|
+
onClick: handleRefresh,
|
|
42895
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
42896
|
+
"aria-label": "Refresh data",
|
|
42897
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
42898
|
+
}
|
|
42899
|
+
) }) })
|
|
42900
|
+
] }) }) }),
|
|
42901
|
+
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 px-3 sm:px-4 md:px-5 lg:px-6 py-4 sm:py-6", children: data.assignments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42902
|
+
EmptyStateMessage,
|
|
42903
|
+
{
|
|
42904
|
+
iconType: lucideReact.Building2,
|
|
42905
|
+
title: "No Lines Found",
|
|
42906
|
+
message: "There are no production lines to manage supervisor assignments for."
|
|
42907
|
+
}
|
|
42908
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full divide-y divide-gray-200", children: [
|
|
42909
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
42910
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Production Line" }),
|
|
42911
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assigned Supervisor" })
|
|
42912
|
+
] }) }),
|
|
42913
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: data.assignments.map((assignment) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "hover:bg-gray-50", children: [
|
|
42914
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
|
|
42915
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "h-4 w-4 text-gray-400 mr-2" }),
|
|
42916
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-900", children: assignment.lineName })
|
|
42917
|
+
] }) }),
|
|
42918
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-xs", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42919
|
+
SupervisorDropdown,
|
|
42920
|
+
{
|
|
42921
|
+
selectedSupervisor: assignment.currentSupervisor,
|
|
42922
|
+
availableSupervisors: assignment.availableSupervisors,
|
|
42923
|
+
onSelect: (supervisor) => handleSupervisorChange(assignment.lineId, supervisor),
|
|
42924
|
+
disabled: savingAssignments.has(assignment.lineId),
|
|
42925
|
+
placeholder: "Select supervisor..."
|
|
42926
|
+
}
|
|
42927
|
+
) }) })
|
|
42928
|
+
] }, assignment.lineId)) })
|
|
42929
|
+
] }) }) }) }) })
|
|
42930
|
+
] });
|
|
42931
|
+
};
|
|
42932
|
+
var SupervisorManagementView_default = SupervisorManagementView;
|
|
40164
42933
|
var S3Service = class {
|
|
40165
42934
|
constructor(config) {
|
|
40166
42935
|
this.s3Client = null;
|
|
@@ -40643,6 +43412,7 @@ exports.CardHeader = CardHeader2;
|
|
|
40643
43412
|
exports.CardTitle = CardTitle2;
|
|
40644
43413
|
exports.CompactWorkspaceHealthCard = CompactWorkspaceHealthCard;
|
|
40645
43414
|
exports.CongratulationsOverlay = CongratulationsOverlay;
|
|
43415
|
+
exports.CroppedVideoPlayer = CroppedVideoPlayer;
|
|
40646
43416
|
exports.CycleTimeChart = CycleTimeChart;
|
|
40647
43417
|
exports.CycleTimeOverTimeChart = CycleTimeOverTimeChart;
|
|
40648
43418
|
exports.DEFAULT_ANALYTICS_CONFIG = DEFAULT_ANALYTICS_CONFIG;
|
|
@@ -40669,6 +43439,8 @@ exports.DetailedHealthStatus = DetailedHealthStatus;
|
|
|
40669
43439
|
exports.EmptyStateMessage = EmptyStateMessage;
|
|
40670
43440
|
exports.EncouragementOverlay = EncouragementOverlay;
|
|
40671
43441
|
exports.FactoryView = FactoryView_default;
|
|
43442
|
+
exports.FirstTimeLoginDebug = FirstTimeLoginDebug;
|
|
43443
|
+
exports.FirstTimeLoginHandler = FirstTimeLoginHandler;
|
|
40672
43444
|
exports.GaugeChart = GaugeChart;
|
|
40673
43445
|
exports.GridComponentsPlaceholder = GridComponentsPlaceholder;
|
|
40674
43446
|
exports.HamburgerButton = HamburgerButton;
|
|
@@ -40680,6 +43452,7 @@ exports.HomeView = HomeView_default;
|
|
|
40680
43452
|
exports.HourlyOutputChart = HourlyOutputChart2;
|
|
40681
43453
|
exports.ISTTimer = ISTTimer_default;
|
|
40682
43454
|
exports.InlineEditableText = InlineEditableText;
|
|
43455
|
+
exports.InteractiveOnboardingTour = InteractiveOnboardingTour;
|
|
40683
43456
|
exports.KPICard = KPICard;
|
|
40684
43457
|
exports.KPIDetailView = KPIDetailView_default;
|
|
40685
43458
|
exports.KPIGrid = KPIGrid;
|
|
@@ -40698,6 +43471,7 @@ exports.LineMonthlyPdfGenerator = LineMonthlyPdfGenerator;
|
|
|
40698
43471
|
exports.LinePdfExportButton = LinePdfExportButton;
|
|
40699
43472
|
exports.LinePdfGenerator = LinePdfGenerator;
|
|
40700
43473
|
exports.LineWhatsAppShareButton = LineWhatsAppShareButton;
|
|
43474
|
+
exports.LinesService = LinesService;
|
|
40701
43475
|
exports.LiveTimer = LiveTimer;
|
|
40702
43476
|
exports.LoadingInline = LoadingInline;
|
|
40703
43477
|
exports.LoadingOverlay = LoadingOverlay_default;
|
|
@@ -40708,7 +43482,10 @@ exports.LoginPage = LoginPage;
|
|
|
40708
43482
|
exports.LoginView = LoginView_default;
|
|
40709
43483
|
exports.MainLayout = MainLayout;
|
|
40710
43484
|
exports.MetricCard = MetricCard_default;
|
|
43485
|
+
exports.MinimalOnboardingPopup = MinimalOnboardingPopup;
|
|
40711
43486
|
exports.NoWorkspaceData = NoWorkspaceData;
|
|
43487
|
+
exports.OnboardingDemo = OnboardingDemo;
|
|
43488
|
+
exports.OnboardingTour = OnboardingTour;
|
|
40712
43489
|
exports.OptifyeAgentClient = OptifyeAgentClient;
|
|
40713
43490
|
exports.OptifyeLogoLoader = OptifyeLogoLoader_default;
|
|
40714
43491
|
exports.OutputProgressChart = OutputProgressChart;
|
|
@@ -40739,11 +43516,15 @@ exports.SelectValue = SelectValue;
|
|
|
40739
43516
|
exports.ShiftDisplay = ShiftDisplay_default;
|
|
40740
43517
|
exports.ShiftsView = ShiftsView_default;
|
|
40741
43518
|
exports.SideNavBar = SideNavBar;
|
|
43519
|
+
exports.SimpleOnboardingPopup = SimpleOnboardingPopup;
|
|
40742
43520
|
exports.SingleVideoStream = SingleVideoStream_default;
|
|
40743
43521
|
exports.Skeleton = Skeleton;
|
|
40744
43522
|
exports.SubscriptionManager = SubscriptionManager;
|
|
40745
43523
|
exports.SubscriptionManagerProvider = SubscriptionManagerProvider;
|
|
40746
43524
|
exports.SupabaseProvider = SupabaseProvider;
|
|
43525
|
+
exports.SupervisorDropdown = SupervisorDropdown_default;
|
|
43526
|
+
exports.SupervisorManagementView = SupervisorManagementView_default;
|
|
43527
|
+
exports.SupervisorService = SupervisorService;
|
|
40747
43528
|
exports.TargetWorkspaceGrid = TargetWorkspaceGrid;
|
|
40748
43529
|
exports.TargetsView = TargetsView_default;
|
|
40749
43530
|
exports.ThreadSidebar = ThreadSidebar;
|
|
@@ -40751,6 +43532,7 @@ exports.TicketHistory = TicketHistory_default;
|
|
|
40751
43532
|
exports.TicketHistoryService = TicketHistoryService;
|
|
40752
43533
|
exports.TimeDisplay = TimeDisplay_default;
|
|
40753
43534
|
exports.TimePickerDropdown = TimePickerDropdown;
|
|
43535
|
+
exports.UserService = UserService;
|
|
40754
43536
|
exports.VideoCard = VideoCard;
|
|
40755
43537
|
exports.VideoGridView = VideoGridView;
|
|
40756
43538
|
exports.VideoPlayer = VideoPlayer;
|
|
@@ -40766,6 +43548,7 @@ exports.WorkspaceHealthCard = WorkspaceHealthCard;
|
|
|
40766
43548
|
exports.WorkspaceHealthView = WorkspaceHealthView_default;
|
|
40767
43549
|
exports.WorkspaceHistoryCalendar = WorkspaceHistoryCalendar;
|
|
40768
43550
|
exports.WorkspaceMetricCards = WorkspaceMetricCards;
|
|
43551
|
+
exports.WorkspaceMetricCardsImpl = WorkspaceMetricCardsImpl;
|
|
40769
43552
|
exports.WorkspaceMonthlyDataFetcher = WorkspaceMonthlyDataFetcher;
|
|
40770
43553
|
exports.WorkspaceMonthlyPdfGenerator = WorkspaceMonthlyPdfGenerator;
|
|
40771
43554
|
exports.WorkspacePdfExportButton = WorkspacePdfExportButton;
|
|
@@ -40783,9 +43566,12 @@ exports.clearS3VideoCache = clearS3VideoCache;
|
|
|
40783
43566
|
exports.clearS3VideoFromCache = clearS3VideoFromCache;
|
|
40784
43567
|
exports.clearWorkspaceDisplayNamesCache = clearWorkspaceDisplayNamesCache;
|
|
40785
43568
|
exports.cn = cn;
|
|
43569
|
+
exports.createLinesService = createLinesService;
|
|
40786
43570
|
exports.createStreamProxyHandler = createStreamProxyHandler;
|
|
40787
43571
|
exports.createSupabaseClient = createSupabaseClient;
|
|
43572
|
+
exports.createSupervisorService = createSupervisorService;
|
|
40788
43573
|
exports.createThrottledReload = createThrottledReload;
|
|
43574
|
+
exports.createUserService = createUserService;
|
|
40789
43575
|
exports.dashboardService = dashboardService;
|
|
40790
43576
|
exports.deleteThread = deleteThread;
|
|
40791
43577
|
exports.forceRefreshWorkspaceDisplayNames = forceRefreshWorkspaceDisplayNames;
|
|
@@ -40845,6 +43631,7 @@ exports.isValidWorkspaceDetailedMetricsPayload = isValidWorkspaceDetailedMetrics
|
|
|
40845
43631
|
exports.isValidWorkspaceMetricsPayload = isValidWorkspaceMetricsPayload;
|
|
40846
43632
|
exports.isWorkspaceDisplayNamesLoaded = isWorkspaceDisplayNamesLoaded;
|
|
40847
43633
|
exports.isWorkspaceDisplayNamesLoading = isWorkspaceDisplayNamesLoading;
|
|
43634
|
+
exports.linesService = linesService;
|
|
40848
43635
|
exports.mergeWithDefaultConfig = mergeWithDefaultConfig;
|
|
40849
43636
|
exports.migrateLegacyConfiguration = migrateLegacyConfiguration;
|
|
40850
43637
|
exports.optifyeAgentClient = optifyeAgentClient;
|
|
@@ -40863,6 +43650,7 @@ exports.resetFailedUrl = resetFailedUrl;
|
|
|
40863
43650
|
exports.resetSubscriptionManager = resetSubscriptionManager;
|
|
40864
43651
|
exports.s3VideoPreloader = s3VideoPreloader;
|
|
40865
43652
|
exports.shuffleArray = shuffleArray;
|
|
43653
|
+
exports.simulateApiDelay = simulateApiDelay;
|
|
40866
43654
|
exports.skuService = skuService;
|
|
40867
43655
|
exports.startCoreSessionRecording = startCoreSessionRecording;
|
|
40868
43656
|
exports.stopCoreSessionRecording = stopCoreSessionRecording;
|
|
@@ -40873,6 +43661,7 @@ exports.toUrlFriendlyName = toUrlFriendlyName;
|
|
|
40873
43661
|
exports.trackCoreEvent = trackCoreEvent;
|
|
40874
43662
|
exports.trackCorePageView = trackCorePageView;
|
|
40875
43663
|
exports.updateThreadTitle = updateThreadTitle;
|
|
43664
|
+
exports.useAccessControl = useAccessControl;
|
|
40876
43665
|
exports.useActiveBreaks = useActiveBreaks;
|
|
40877
43666
|
exports.useAllWorkspaceMetrics = useAllWorkspaceMetrics;
|
|
40878
43667
|
exports.useAnalyticsConfig = useAnalyticsConfig;
|
|
@@ -40880,6 +43669,8 @@ exports.useAudioService = useAudioService;
|
|
|
40880
43669
|
exports.useAuth = useAuth;
|
|
40881
43670
|
exports.useAuthConfig = useAuthConfig;
|
|
40882
43671
|
exports.useCanSaveTargets = useCanSaveTargets;
|
|
43672
|
+
exports.useClipTypes = useClipTypes;
|
|
43673
|
+
exports.useClipTypesWithCounts = useClipTypesWithCounts;
|
|
40883
43674
|
exports.useComponentOverride = useComponentOverride;
|
|
40884
43675
|
exports.useCustomConfig = useCustomConfig;
|
|
40885
43676
|
exports.useDashboardConfig = useDashboardConfig;
|
|
@@ -40935,9 +43726,11 @@ exports.useWorkspaceHealthById = useWorkspaceHealthById;
|
|
|
40935
43726
|
exports.useWorkspaceMetrics = useWorkspaceMetrics;
|
|
40936
43727
|
exports.useWorkspaceNavigation = useWorkspaceNavigation;
|
|
40937
43728
|
exports.useWorkspaceOperators = useWorkspaceOperators;
|
|
43729
|
+
exports.userService = userService;
|
|
40938
43730
|
exports.videoPrefetchManager = videoPrefetchManager;
|
|
40939
43731
|
exports.videoPreloader = videoPreloader;
|
|
40940
43732
|
exports.whatsappService = whatsappService;
|
|
43733
|
+
exports.withAccessControl = withAccessControl;
|
|
40941
43734
|
exports.withAuth = withAuth;
|
|
40942
43735
|
exports.withRegistry = withRegistry;
|
|
40943
43736
|
exports.workspaceHealthService = workspaceHealthService;
|