@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.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
- // src/lib/services/videoPrefetchManager.ts
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 S3ClipsService(dashboardConfig));
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 = supabase.from("user_roles").select("role_level").eq("user_id", supabaseUser.id).single();
5292
- const roleResult = await Promise.race([
5293
- rolePromise,
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 role_level:", roleResult.error.message);
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: { session, user, loading, error, signOut }, children });
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 user?.role_level === "owner";
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 -bottom-1 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: [
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
- // Enhanced HLS configuration
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
- // Error recovery
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
- // Buffer configuration
27216
- maxMaxBufferLength: 30,
27217
- bufferBasedABR: true
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: src.endsWith(".m3u8") ? "application/x-mpegURL" : "video/mp4"
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: src.endsWith(".m3u8") ? "application/x-mpegURL" : "video/mp4"
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 sopCategories = React19__namespace.default.useMemo(() => {
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 timestampFilterRef = React19.useRef(null);
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 from S3`);
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] Fetched and cached clip counts`);
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
- let effectiveFilter = currentFilter;
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(clipCounts[effectiveFilter] - 1, centerIndex + rangeAfter); i++) {
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, sopCategories, date, effectiveShift]);
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 (!clipCounts[targetCategory]) {
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 (clipCounts[targetCategory] > 0) {
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
- if (prefetchData && prefetchData.counts) {
28046
- console.log(`[BottlenecksContent] Received prefetch update - status: ${prefetchStatus}`);
28047
- updateClipCounts(prefetchData.counts);
28048
- if (!hasInitialLoad) {
28049
- setIsLoading(false);
28050
- setHasInitialLoad(true);
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 && clipCounts[activeFilter] > 0) {
29626
+ if (s3ClipsService && (mergedCounts[activeFilter] || 0) > 0) {
28056
29627
  const hasVideosForCurrentFilter = allVideos.some((video) => {
28057
- if (sopCategories && sopCategories.length > 0) {
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, clipCounts, loadFirstVideoForCategory, allVideos, sopCategories]);
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
- if (activeFilter === "low_value") return video.type === "low_value";
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 (clipCounts[activeFilter] === 0) {
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, clipCounts]);
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
- if (sopCategories && sopCategories.length > 0) {
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, sopCategories]);
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
- let effectiveFilter = currentFilter;
28187
- if (sopCategories && sopCategories.length > 0) {
28188
- const category = sopCategories.find((cat) => cat.id === currentFilter);
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(clipCounts).length === 0) {
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 = sopCategories && sopCategories.length > 0 ? sopCategories : [
28426
- // Default hardcoded categories if no configuration
28427
- { id: "low_value", label: "Idle Moments", color: "purple", subtitle: "Idle time detected" },
28428
- { id: "best_cycle_time", label: "Best Cycle Time", color: "green", subtitle: "Fastest cycle today" },
28429
- { id: "worst_cycle_time", label: "Worst Cycle Time", color: "red", subtitle: "Slowest cycle today" },
28430
- { id: "long_cycle_time", label: "Long Cycle Time", color: "red-dark", subtitle: "Above standard cycle times" },
28431
- { id: "cycle_completion", label: "Cycle Completions", color: "blue", subtitle: "Completed production cycles" }
28432
- ];
28433
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-grow p-1.5 sm:p-2 lg:p-4 h-[calc(100vh-12rem)]", children: [
28434
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `grid grid-cols-1 sm:grid-cols-2 ${categoriesToShow.length <= 5 ? "lg:grid-cols-5" : "lg:grid-cols-6"} gap-3 mb-4`, children: categoriesToShow.map((category) => {
28435
- const colorClasses = getColorClasses(category.color);
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 (${mappedCounts.total || 0})`;
29909
+ return `All Clips (${mergedCounts.total || 0})`;
28474
29910
  }
28475
- const activeCategory = categoriesToShow.find((cat) => cat.id === activeFilter);
29911
+ const activeCategory = categoriesToShow.find((cat) => cat.type === activeFilter);
28476
29912
  if (activeCategory) {
28477
- return `${activeCategory.label} (${mappedCounts[activeCategory.id] || 0})`;
29913
+ return `${activeCategory.label} (${mergedCounts[activeCategory.type] || 0})`;
28478
29914
  }
28479
- return activeFilter === "low_value" ? `Idle Moments (${mappedCounts.lowValue || 0})` : activeFilter === "best_cycle_time" ? `Best Cycle Time (${mappedCounts.bestCycleTimes || 0})` : activeFilter === "worst_cycle_time" ? `Worst Cycle Time (${mappedCounts.worstCycleTimes || 0})` : activeFilter === "long_cycle_time" ? `Long Cycle Time (${mappedCounts.longCycleTimes || 0})` : activeFilter === "cycle_completion" ? `Cycle Completions (${mappedCounts.cycleCompletions || 0})` : `All Clips (${mappedCounts.total || 0})`;
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 || clipCounts[activeFilter] === 0,
28553
- className: `p-2 rounded-full transition-colors ${currentIndex === 0 || clipCounts[activeFilter] === 0 ? "text-gray-300 cursor-not-allowed" : "text-gray-600 hover:bg-gray-100"}`,
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: clipCounts[activeFilter] > 0 ? `${currentIndex + 1} / ${clipCounts[activeFilter]}` : "0 / 0" }),
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 >= clipCounts[activeFilter] - 1 || clipCounts[activeFilter] === 0,
28567
- className: `p-2 rounded-full transition-colors ${currentIndex >= clipCounts[activeFilter] - 1 || clipCounts[activeFilter] === 0 ? "text-gray-300 cursor-not-allowed" : "text-gray-600 hover:bg-gray-100"}`,
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 < clipCounts[activeFilter] ? /* @__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..." }) }) }) }) : (
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
- VideoPlayer,
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
- (currentVideo.cycle_time_seconds / 20).toFixed(1),
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
- const player = videoRef.current?.player;
28702
- if (player) {
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(clipCounts).length > 0 && Object.values(clipCounts).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: [
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 && clipCounts[activeFilter] === 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: [
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
- skuEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
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.jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight", children: "Axel" })
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
- skuEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
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.jsx("span", { className: "text-base font-medium", children: "Axel AI" })
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-2 sm:gap-3", children: [
34930
- /* @__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" }),
34931
- /* @__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" })
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-center gap-2 sm:gap-3 mb-4 sm:mb-5 md:mb-6", children: [
35140
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg sm:text-xl font-semibold text-gray-900 truncate", children: line.line_name }),
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;